[C++/MFC] : Use a Thread Delegator for your threads






4.56/5 (6 votes)
Wouldn't be easier if you just pass a simple function to your favourite thread creation function? Read on!
You generally create a thread using
Okay, let me exemplify with an example. Wouldn't this suit you?
Now, let's see how we can link them together. This might seem slightly complicated, but can be understood easily!
First, let
What if you need to pass an argument to your thread function?
Well, this demands some attention and interest from you. This approach will be used with
What if you need to delegate a member-function of a class to be classified as the thread? First let me enlighten this topic. The following example (simulation) has a
Now, you would be interested in thread-delegator, that would create thread in more simple way, like:
First, have a typedef for member-to-function pointer:
Thirdly, call the thread-delegator:
Caveat! You cannot do that! Both thread creation function takes LPVOID as param, and by no conversion style you can covert a member-to-function pointer to void* (or any pointer); and vice versa! Doing this raises C2440. Why? This needs a long discussion, but in short: A class may involve multiple inheritance, and that causes
I found a hack for fooling the compiler for this. Just declare a
And, the thread routine, which should call user specified function, can be coded as:
CreateThread
or AfxBeginThread
, and pass a function which must match the prototype as laid down by thread-creation function.
For example:
// This prototype *must* match with CreateThread // i.e. should be exactly LPTHREAD_START_ROUTINE DWORD __stdcall TheThread(void* pParam) { // Perform the task of this thread // Optionally utilize the pParam return 0; } int _tmain(int argc, _TCHAR* argv[]) { DWORD dwTID; CreateThread(NULL, 0,TheThread, NULL,0, &dwTID); return 0; }For now, let's assume the argument to thread is not required. Wouldn't it be better if you create the thread by just declaring the thread-function that takes
void
and returns void
, and have a delegator that would actually be LPTHREAD_START_ROUTINE
?
Okay, let me exemplify with an example. Wouldn't this suit you?
void ThreadRoutine() { // Your thread routine. // For now, there is no parameter. } int main() { DelegateCreateThread(ThreadRoutine); }The thread-creation delegator would be responsible for creating the thread, and it would schedule the specified function to be acted as thread.
Now, let's see how we can link them together. This might seem slightly complicated, but can be understood easily!
First, let
typedef
a function-pointer that takes void
and returns void
:
// Type: Pointer to function: void(void)Second, have your thread routine:
typedef void (*Pointer2FunctionVV)();
void ThreadRoutine() { // Your thread routine. // For now, there is no parameter. }Thirdly, let me show you the actual delegator!
DWORD __stdcall ThreadDelegator(void* pThread) { Pointer2FunctionVV Func; // Variable Func = (Pointer2FunctionVV)pThread; // Typecast Func(); // CALL! return 0; }The delegator function is put in four lines, for simplicity. Depending on your taste, coding standards and flexibility level, you can reduce the call as short as:
((Pointer2FunctionVV)pThread)(); return 0;And finally, the
DelegateCreateThread
:
void DelegateCreateThread( Pointer2FunctionVV pThread ) { DWORD dwTID; CreateThread(NULL,0, ThreadDelegator, pThread, 0, &dwTID); }And if you dont like this as function, you can #define it as a macro too:
#define DelegateCreateThread(func) { \ DWORD dwTID; \ CreateThread(NULL,0, ThreadDelegator, (func), 0, &dwTID); \ }Now, you just need to call
DelegateCreateThread
, and pass your void(void)
style function that should be executed as a thread.What if you need to pass an argument to your thread function?
Well, this demands some attention and interest from you. This approach will be used with
AfxBeginThread
later.
First, let's have our function typedef
'ed:
typedef void (*Pointer2FunctionVP)(void*); // P : void*If you are understanding it carefully, you are aware that the 'param' parameter of
CreateThread
is already utilized to pass actual function. Therefore, we need to use following approach, which uses our custom struct
to pass data.
(Second,) We declare ThreadDelegatorData
structure:
struct ThreadDelegatorData { Pointer2FunctionVP ThreadFunc; void* Param; };Thirdly, we code another thread-delegator function, which initializes this structure (on heap), and passes it to
CreateThread
:
void DelegateCreateThread(Pointer2FunctionVP pThread, void* pParam) { // Prepare for thread-delegation ThreadDelegatorData* pDelegatorData = new ThreadDelegatorData(); pDelegatorData->Param = pParam; pDelegatorData->ThreadFunc = pThread;I assume you know why this object is allocated on heap, instead of stack. Finally, in our thread-function (
DWORD dwTID; CreateThread(NULL, 0, ThreadDelegatorWithParam, pDelegatorData, 0, &dwTID); }
ThreadDelegatorWithParam
) we call the function which is specified by user:
DWORD __stdcall ThreadDelegatorWithParam(void* pParam) { // We know it is pointer to ThreadDelegatorData // just typecast it. ThreadDelegatorData* pDelegatorData = (ThreadDelegatorData*)pParam; // Call the specified function, passing required param (pDelegatorData->ThreadFunc)(pDelegatorData->Param); // Free up data delete pDelegatorData; return 0; }You can use the same approach with
AfxBeginThread
(or any other thread-creation function), you just need to keep the thread-delegator match with thread-creator's desired prototype.What if you need to delegate a member-function of a class to be classified as the thread? First let me enlighten this topic. The following example (simulation) has a
CDialog
inherited class, it starts a thread when user asks for processing. The thread-function does the processing, and regularly sends (posts) the progress updates (via PostMessage
). Note that this is a simulation, thus only relevant code is given.
class CMyDialog // : public CDialog { int Count; public: void OnOK() { Count = 0; AfxBeginThread(ProcessingThread, this); // Don't dismiss the dialog } static UINT ProcessingThread(void* pParam) { // Get the 'this' pointer CMyDialog *pDialog = (CMyDialog*)pParam; // Use pDialog, ensuring thread safety, as per // your program design. // Do the "long" processing, periodically call // PostMessage, to update the UI // In this case, you must use // pDialog->func, pDialog->data2 // to call function or access data of this class. for (int nIter = 1; nIter <= 100; nIter++) { pDialog->Count += nIter; } return 0; } };You can avoid cumbersome and unreadable '
pDialog->
' stuff by:static UINT ProcessingThread(void* pParam) { // Get the 'this' pointer CMyDialog *pDialog = (CMyDialog*)pParam; // Call it pDialog->ProcessingThreadProc(); return 0; } void ProcessingThreadProc() { // Thread safety, UI update request is // programmer's task! for (int nIter = 1; nIter <= 100; nIter++) { Count += nIter; } }Which reduces some clutter.
Now, you would be interested in thread-delegator, that would create thread in more simple way, like:
DelegateBeginThread(ProcessingThreadProc);Well, that's not that straightforward, and it needs some hack. I would provide a solution that would work for the code given above, but for now, let's start with simple one.
First, have a typedef for member-to-function pointer:
typedef void (CMyDialog::*Pointer2MemFunVV)();Secondly, define the thread-delegator:
void DelegateBeginThread(Pointer2MemFunVV pmf) { // AfxBeginThread(ProcessingThread, (void*)pmf); }(Coming to commented part!)
Thirdly, call the thread-delegator:
DelegateBeginThread(&CMyDialog::ProcessingThreadProc);For now, first let's assume the actual thread doesn't demand '
this
' pointer; object (CMyDialog) is accessible. Thus, we may pass the thread-function to be called to CreateThread
or AfxBeginThread
.
Caveat! You cannot do that! Both thread creation function takes LPVOID as param, and by no conversion style you can covert a member-to-function pointer to void* (or any pointer); and vice versa! Doing this raises C2440. Why? This needs a long discussion, but in short: A class may involve multiple inheritance, and that causes
sizeof
M2F pointer to be of 8 bytes or more, on 32-bit compilation. Compiler cannot be flexible enough to allow the conversion, knowing/feeling that our class doesn't involve MI - A rule is a rule! (Read this article)I found a hack for fooling the compiler for this. Just declare a
union
, have a void pointer and M2F pointer. Assign M2F with a function' address, and pass the void* to thread creator:
// Suggest a good name, my mind doesn't allow union MemFun_Param { Pointer2MemFunVV func; void* param; }; ... void DelegateBeginThread(Pointer2MemFunVV pmf) { MemFun_Param mp; mp.func = pmf; AfxBeginThread(ProcessingThread, mp.param); // Fooled! }Yes, it is important to know that size of M2F and void-pointer is same! We can use static asserts or runtime check to do that. Short of space, cannot discuss that.
And, the thread routine, which should call user specified function, can be coded as:
// As said before this approach assumes the object of type CMyDialog is available. We just made 'dialog' a global object CMyDialog dialog; UINT CMyDialog::ProcessingThread(void* pParam) { MemFun_Param mp; mp.param = pParam; // Valid conversion // CALL! (dialog.*mp.func)(); // func points to valid function! return 0; }Aww! By this time, I realize this Tip/Trick has gone much bigger than I initially thought. I suppose I should delete this, and put it as an article!