65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.90/5 (6投票s)

2006 年 10 月 27 日

4分钟阅读

viewsIcon

97998

downloadIcon

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.hkrunner.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日 - 首次发布。
© . All rights reserved.