Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Encapsulating Win32 threads in C++

0.00/5 (No votes)
26 Jul 2001 1  
This article presents a class to encapsulate threads, leaving the user to focus on project details.

Intent

Encapsulating Win32 threads in a C++ class, easy to subclass and reuse. Hide the details of threads from users so they can focus on the project details.

Motivation

Object oriented languages like C++ have their strength in their ability to encapsulate the representation and implementation of an object, so that programming is focused on a higher level. We say we're programming at interface level rather than at function level. However most OSes haven't been design with C++ in mind, they were usually implemented using non-OO approaches. That's why sometimes it can be tricky to encapsulate some platform dependent resources, like threads for instance. My approach covers Win32 threads.

Win32 threads

To create another thread in the same process, one has in Win32 couple of API functions that handle threads. However they are C and not C++ API. We can easily notice C idioms like callback functions, conversions to and from void* to other types and so on. Let's take a look at how CreateThread, the API function that creates a thread in Win32 looks like. It's prototype it's shown below:

	HANDLE CreateThread(
		LPSECURITY_ATTRIBUTES lpThreadAttributes, 
		DWORD dwStackSize, 
		LPTHREAD_START_ROUTINE lpStartAddress, 
		LPVOID lpParameter, 
		DWORD dwCreationFlags, 
		LPDWORD lpThreadId
	);

lpStartAddress is a pointer to a callback function that will run in the new thread, and lpParameter is a parameter of type void* passed to the new thread. Passing a pointer to a callback function however it's not in the spirit of OOP, and it comes like a serious impediment if we want to encapsulate threads in classes. The callback function required to be passed to CreateThread looks like this:

	DWORD WINAPI ThreadProc(
		LPVOID lpParameter
	);

We'll notice this prototype will keep us from having this callback function as a member of a class, as it's member functions are passed a hidden parameter: this. What's to be done then? Did we fail miserably? Not yet. We cannot use member function and we've seen why, however classes have static methods, which have single instances for all objects. They are connected to classes rather than to objects. That's why they are not passed this as a parameter. So a static function becomes an interesting candidate for a callback function. However there's one small problem, if we have our thread in a static method, then no matter how many objects of that class we'll have, there will be only one thread, as a static method has a single instance per class. This is not what we want. We want our working thread to be a member method, easy to override by subclasses, and all this workaround to be transparent for the clients. Can we do that? Yes we can. If you look at ThreadProc, you'll notice it can be passed one void* parameter. Nothing prevents us from sending it (void*)this, and in ThreadProc we just call our working method, now that we have this pointer. The code for doing that will look like:

//here we create the thread

HANDLE CThread::CreateThread ()
{
	return ::CreateThread ((NULL, 0, (unsigned long (__stdcall *)(void *))this->runProcess, 
				(void *)this, 0, NULL);
}	

//static method

int CThread::runProcess (void* pThis)
{
	return ((CThread*)(pThis))->Process();
}

//our working method, virtual, overridable

int CThread::Process ()
{
	//will work in another thread

}

So far so good. We managed to provide an encapsulation of threading mechanism, so users will simply have to implement their own Process, and then call CreateThread member function. It's even easier to provide reusability, as a user can simply inherit from our class defined above, CThread, and simply implement Process, and then call CreateThread, and they have a thread simple as that. However there's a small issue to note here: Let's say we have a subclass of CThread, named CMyThread. In CreateThread will convert this (which is of type CMyThread*) to void* and pass it to runProcess, where we reconvert this to CThread. C++ standard states that if you convert a type X* to void*, then only a conversion back, to the same type X* is permitted. Other conversions result in an undefined behaviour. That simply means we've done something wrong. How can we fix that? Well, with a small workaround.

struct workAround {
	CThread* this_thread;
};

//we pass a workAround struct instead of this

HANDLE CThread::CreateThread ()
{
	workAround* wA	= new workAround;
	wa->this_thread	= this;
	return ::CreateThread ((NULL, 0, (unsigned long (__stdcall *)(void *))this->runProcess, 
				(void *)wa, 0, NULL);
}	

//static method

int CThread::runProcess (void* pThis)
{
	workAround* wA	= (workAround*)pThis;
	//this will call the appropriate method, as Process is a virtual method

	CThread* thread = wA->this_thread;
	delete wA;
	return thread->Process();
}

This time we're allright as we're converting to and from the same type (struct workAround). Finally to be in the spirit of C++ we'll use C++ conversion, instead of C conversions. Eg: instead of (void*)wA we'll have static_cast<void*>(wA)

Conclusions

Due to the fact most nowadays OSes are not object oriented, as a C++ we usually have to find workarounds when we need to encapsulate platform dependent resources. We have to apply different tricks to achieve that, but once we encapsulated it, it's very simple to use and reusable with considerable less effort. Threads in Win32 are a good examples in that direction. You can further study the source code to get a deeper insight. Happy programming!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here