Click here to Skip to main content
15,881,248 members
Articles / Mobile Apps
Article

Encapsulating Win32 threads in C++

Rate me:
Please Sign up or sign in to vote.
4.63/5 (31 votes)
26 Jul 20014 min read 252.3K   4.8K   43   29
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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiontype conversion Pin
dev/cl0ne29-Nov-11 4:41
dev/cl0ne29-Nov-11 4:41 
QuestionType mismatch while calling CreateThread() Pin
Ahmed Han24-Dec-09 1:10
Ahmed Han24-Dec-09 1:10 
QuestionBorland IDE error Pin
loneaussie1-Aug-07 6:41
loneaussie1-Aug-07 6:41 
GeneralMessage Box disappeared Pin
sudiptam1-May-05 23:58
sudiptam1-May-05 23:58 
Generalundefined behaviour Pin
Anonymous11-May-04 23:29
Anonymous11-May-04 23:29 
Generalwrong virtual function called Pin
chrismc91225-Jan-04 21:59
chrismc91225-Jan-04 21:59 
GeneralMemory leak if CreateThread gives an error Pin
yogieric5-Dec-03 10:35
yogieric5-Dec-03 10:35 
Generalparameter Pin
jakl24-Dec-02 2:55
jakl24-Dec-02 2:55 
Questionwhat would happen ... Pin
CherezZaboro2-Dec-02 10:01
CherezZaboro2-Dec-02 10:01 
Generalno need for static member function Pin
Silvio Iaccarino31-Jul-01 2:17
Silvio Iaccarino31-Jul-01 2:17 
GeneralRe: no need for static member function Pin
Tomasz Sowinski31-Jul-01 3:20
Tomasz Sowinski31-Jul-01 3:20 
GeneralRe: no need for static member function Pin
31-Jul-01 3:38
suss31-Jul-01 3:38 
GeneralRe: no need for static member function Pin
31-Jul-01 14:02
suss31-Jul-01 14:02 
GeneralCreateThread requires an ID pointer in Win9x Pin
28-Jul-01 14:12
suss28-Jul-01 14:12 
GeneralUse _beginthreadex Pin
Daniel Kopitchinski28-Jul-01 0:35
Daniel Kopitchinski28-Jul-01 0:35 
GeneralRe: Use _beginthreadex Pin
28-Jul-01 14:09
suss28-Jul-01 14:09 
GeneralRe: Use _beginthreadex Pin
Michael Dunn28-Jul-01 15:25
sitebuilderMichael Dunn28-Jul-01 15:25 
GeneralRe: Use _beginthreadex Pin
29-Jul-01 6:29
suss29-Jul-01 6:29 
GeneralRe: Use _beginthreadex Pin
Daniel Lohmann29-Jul-01 10:39
Daniel Lohmann29-Jul-01 10:39 
GeneralRe: Use _beginthreadex - EXCEPT... when using MFC Pin
29-Jul-01 13:10
suss29-Jul-01 13:10 
GeneralRe: Use _beginthreadex - EXCEPT... when using MFC Pin
Michael Dunn29-Jul-01 14:48
sitebuilderMichael Dunn29-Jul-01 14:48 
GeneralGood start, but needs some work Pin
Jason Douglas27-Jul-01 2:44
professionalJason Douglas27-Jul-01 2:44 
GeneralRe: Good start, but needs some work Pin
Zoltan Csizmadia27-Jul-01 4:06
Zoltan Csizmadia27-Jul-01 4:06 
GeneralRe: Good start, but needs some work Pin
Jason Douglas27-Jul-01 11:26
professionalJason Douglas27-Jul-01 11:26 
GeneralRe: Good start, but needs some work Pin
Zoltan Csizmadia27-Jul-01 12:14
Zoltan Csizmadia27-Jul-01 12:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.