调用非静态成员函数作为线程函数






2.90/5 (6投票s)
2006 年 10 月 27 日
4分钟阅读

97998

642
本文介绍如何将C++类成员函数作为线程函数来调用。
引言
当你想定义一个线程函数来访问类的成员函数和属性时,通常的方法是定义一个静态成员函数,将该函数的地址与类实例的指针(this
)一起传递给CreateThread()
函数,并在静态成员函数中使用它来访问该实例的成员函数和属性。
// // A Base class // class Base { public: Base(){} virtual ~Base(){} virtual VOID MemberFunc(UINT i) // A member function { fprintf(stderr, "Base::MemberFunc(0x%x)\n", i); } static UINT ThreadFunc(LPVOID param) { Base* This = (Base*)param; This->MemberFunc(0); // call a member function } VOID StartThreadFunc() { ::CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadFunc, (LPVOID)this,0,0); } };在本文中,我将介绍一种调用非静态成员函数的新方法。
使用代码
我提供了一个头文件和CPP文件(名为krunner.h和krunner.cpp),其中包含处理调用非静态成员函数的基类代码。你需要将这两个文件包含到你的项目中。头文件中有以下基类定义。该类有一个名为Run()
的纯虚函数,它将是你的线程函数,并且你需要实现它在派生于CRunnable
的类中。#define CallType __stdcall // // Run() function will be the thread function // class CRunnable { public: virtual UINT CallType Run() = 0; virtual UINT CallType Start(); virtual UINT CallType Terminate(); CRunnable(); virtual ~CRunnable(); };
在你的代码中,你需要包含头文件并定义一个派生于CRunnable
的自己的类。
#include "krunner.h" // include the header file in your code // A sample base class class Base { public: Base(){} virtual ~Base(){} virtual VOID MemberFunc(UINT i) // A member function { fprintf(stderr, "Base::MemberFunc(0x%x)\n", i); } }; // A sample derived class class Derived : public CRunnable, Base // CRunnable must be the first { public: // implement CRunnable virtual UINT CallType Run() { // your thread code goes here } virtual UINT CallType Start() { UINT ret = CRunnable::Start(); // should be called first // add your own start code here return ret; } virtual UINT CallType Terminate() { // add your own terminate here return CRunnable::Terminate(); // should be called last } }; // A base class modified so as to derive from CRunnable class ModifiedBase : public CRunnable { public: ModifiedBase(){} virtual ~ModifiedBase(){} virtual VOID MemberFunc(UINT i) // A member function { fprintf(stderr, "ModifiedBase::MemberFunc(0x%x)\n", i); } // implement CRunnable virtual UINT CallType Run() { // your thread code goes here } virtual UINT CallType Start() { UINT ret = CRunnable::Start(); // should be called first // add your own start code here return ret; } virtual UINT CallType Terminate() { // add your own terminate here return CRunnable::Terminate(); // should be called last } };
然后,你需要定义一个派生类的实例,并调用Start()
和Terminate()
成员函数。
int main(int argc, char** argv) { Derived obj; obj.Start(); // do something obj.CallMemberFunc(); obj.Terminate(); return 0; }或者定义一个你修改过的基类实例,并调用
Start()
和Terminate()
成员函数。int main(int argc, char** argv) { ModifiedBase obj; obj.Start(); // do something obj.CallMemberFunc(); obj.Terminate(); return 0; }
下面是我的Zip文件中提供的示例应用程序的输出:
main -- starting...
Derived::Start -- waiting...
Derived::Run-0x90c -- started
Derived::CallMemberFunc-0x250 -- PostThreadMessage(0x90c,
TM__CALLMEMBERFUNC, 0x250, 0)
Derived::Run-0x90c -- TM__CALLMEMBERFUNC
Base::MemberFunc(0x250)
main -- terminating...
Derived::Terminate-0x250 -- PostThreadMessage(0x90c,
TM__EXIT, 0, 0)
Derived::Run-0x90c -- TM__EXIT
Derived::Run-0x90c -- ending
Derived::Terminate-0x250 -- call CRunnable::Terminate()
main -- terminated
备注
- 我的代码假定虚函数表中的
Run()
函数是第一个,并且在派生类中也应该是如此。如果你有一个派生类,并且已经从一个基类派生,并且想包含我的基类,你需要使用多重继承,并将我的类(CRunnable
)放在基类列表的第一个位置。另一方面,你可以修改你的基类,使其派生于我的类(CRunnable
)以避免多重继承。在这种情况下,你需要将我的类(CRunnable
)的虚函数添加到你的基类中。在示例应用程序中,我使用了多重继承。 - 你需要在派生类中处理线程之间的同步和通信问题。你可以在我的示例代码中看到如何做到这一点。
CreateThread()
的调用带有三个NULL
参数:CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadFunc,this,NULL,&Dummy)
。- 你需要链接到CRT库的多线程版本。转到项目设置,C/C++选项卡,在“使用运行时库”下,你的发布版本必须设置为“多线程”或“多线程 DLL”。
- 当你在由
CreateThread
函数启动的线程函数中使用CRT函数时,会有关于内存泄漏的警告,并且通常会建议你使用_beginthreadex
而不是CreateThread
。我的代码是调用CreateThread
编写的,并且必须如此。然而,即使你这样做,仍然有一种变通方法来处理内存泄漏问题。你可能会想,为什么你的基于Win32的应用程序多年来似乎都能正常工作,尽管你一直在调用
CreateThread
而不是_beginthreadex
。当一个线程调用一个需要tiddata
结构的CRT函数(这个结构通常由_beginthreadex
分配和初始化)时,会发生以下情况。首先,CRT函数尝试获取线程数据块的地址(通过调用TlsGetValue
)。其次,如果NULL
作为tiddata
块的地址返回,那么调用线程就没有与之关联的tiddata
块。此时,CRT函数会当场为调用线程分配并初始化一个tiddata
块。然后,该块会与线程关联(通过TlsSetValue
),并且该块会与线程保持关联,直到线程继续运行。第三,CRT函数现在可以使用线程的tiddata
块,未来调用的任何CRT函数也可以使用。当然,这是非常棒的,因为你的线程运行几乎没有问题。好吧,实际上这里有几个问题。如果线程使用CRT的信号函数,整个进程将终止,因为结构化异常处理帧尚未准备好。此外,如果线程在未调用_endthreadex
的情况下终止,数据块将无法被销毁,从而导致内存泄漏。所以,如果你为使用CreateThread
创建的线程调用_endthreadex
,你应该没问题。
历史
- 2006年10月27日 - 首次发布。