Introduction
When you want to define a thread function to access the methods and properties of a class, the common way is to define a static member function, pass the address of that function to the CreateThread()
function together with a pointer to a class instance (this
), and use it inside the static member function to access the methods and properties of that instance:
class Base
{
public:
Base(){}
virtual ~Base(){}
virtual VOID MemberFunc(UINT i)
{ fprintf(stderr, "Base::MemberFunc(0x%x)\n", i); }
static UINT ThreadFunc(LPVOID param)
{
Base* This = (Base*)param;
This->MemberFunc(0);
}
VOID StartThreadFunc()
{
::CreateThread(0,0,(LPTHREAD_START_ROUTINE)ThreadFunc,
(LPVOID)this,0,0);
}
};
In this article, I am going to present a new way to call a non-static member function.
Using the code
I have provided a header and a CPP file (named
krunner.h and
krunner.cpp) for the code of the base class which handles calling the non-static member function. You need to include these two files in your project. The header file has the following base class definition in it. The class has a pure virtual function named
Run()
which will be your thread function and which you need to implement in your class derived from
CRunnable
.
#define CallType __stdcall
class CRunnable
{
public:
virtual UINT CallType Run() = 0;
virtual UINT CallType Start();
virtual UINT CallType Terminate();
CRunnable();
virtual ~CRunnable();
};
In your code, you need to include the header file and define your own class derived from CRunnable
.
#include "krunner.h"
class Base
{
public:
Base(){}
virtual ~Base(){}
virtual VOID MemberFunc(UINT i)
{ fprintf(stderr, "Base::MemberFunc(0x%x)\n", i); }
};
class Derived : public CRunnable, Base
{
public:
virtual UINT CallType Run()
{
}
virtual UINT CallType Start()
{
UINT ret = CRunnable::Start();
return ret;
}
virtual UINT CallType Terminate()
{
return CRunnable::Terminate();
}
};
class ModifiedBase : public CRunnable
{
public:
ModifiedBase(){}
virtual ~ModifiedBase(){}
virtual VOID MemberFunc(UINT i)
{ fprintf(stderr, "ModifiedBase::MemberFunc(0x%x)\n", i); }
virtual UINT CallType Run()
{
}
virtual UINT CallType Start()
{
UINT ret = CRunnable::Start();
return ret;
}
virtual UINT CallType Terminate()
{
return CRunnable::Terminate();
}
};
Then, what is required is to define an instance of your derived class, and call the Start()
and Terminate()
member functions.
int main(int argc, char** argv)
{
Derived obj;
obj.Start();
obj.CallMemberFunc();
obj.Terminate();
return 0;
}
or define an instance of your
ModifiedBase
class and call the
Start()
and
Terminate()
member functions.
int main(int argc, char** argv)
{
ModifiedBase obj;
obj.Start();
obj.CallMemberFunc();
obj.Terminate();
return 0;
}
The following is the output of the sample application I have provided in the Zip file:
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
Remarks
- My code assumes the
Run()
function in the virtual function table is the first one and should be so in a derived class as well. If you have a derived class and have already derived from a base class and would like to include my base class, you need to use multiple inheritance and put my class (CRunnable
) in the first place in the list of base classes. On the other hand, you can modify your base class so as to derive from my class (CRunnable
) to avoid multiple inheritance. In that case, you need to add the virtual functions of my class (CRunnable
) into your base class. In the sample application, I have used multiple inheritance.
- You need to handle the synchronisation and communication issues among your threads in your derived classes. You can see an example of how it is done in my sample code.
CreateThread()
is called with three NULL
parameters: CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)ThreadFunc,this,NULL,&Dummy)
.
- You need to link with the multi-threaded version of the CRT library. Go to Project Settings, C/C++ tab, Use run-time library must be Multithreaded or Multithreaded DLL for your release builds.
- There is a warning about the memory leaks when you use CRT functions in your thread functions started by calling the
CreateThread
function, and you are usually warned of using _beginthreadex
instead. My code has been coded to call CreateThread
, and it has to be that way. However, there is still a workaround to cope with the memory leak issues even if you do it.
You may wonder why your Win32-based applications seemed to work over the years even though you've been calling CreateThread
instead of _beginthreadex
. When a thread calls a CRT function that requires the tiddata
structure (which is usually allocated and initialized by _beginthreadex
), here is what happens. First, the CRT function attempts to get the address of the thread's data block (by calling TlsGetValue
). Second, if NULL
is returned as the address of the tiddata
block, then the calling thread doesn't have a tiddata
block associated with it. At this point, the CRT function allocates and initializes a tiddata
block for the calling thread right on the spot. The block is then associated with the thread (via TlsSetValue
), and this block will stay with the thread for as long as the thread continues to run. Third, the CRT function can now use the thread's tiddata
block, and so can any CRT function that is called in the future. This, of course, is fantastic because your thread runs without a hitch (almost). Well, actually there are a few problems here. If the thread uses the CRT's signal function, the entire process will terminate because the structured exception handling frame has not been prepared. Also, if the thread terminates without calling _endthreadex
, the data block cannot be destroyed and a memory leak occurs. So, if you would call _endthreadex
for a thread created with CreateThread
, you should be OK.
History
- 27 Oct 2006 - First release.