Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

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

4.56/5 (6 votes)
13 Oct 2010CPOL4 min read 27.2K  
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 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)<br>
typedef void (*Pointer2FunctionVV)();</br>

Second, have your thread routine:
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;<br>
	DWORD dwTID;
	CreateThread(NULL, 0, ThreadDelegatorWithParam, pDelegatorData, 0, &dwTID);
}</br>
I assume you know why this object is allocated on heap, instead of stack.
Finally, in our thread-function (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!










License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)