Introduction
While ATL and WTL offer useful wrapper classes for most of the various objects a Win32 HANDLE
can refer to, threads still have to be created and controlled using the API functions and handles directly. Admittedly, this is not a hard thing to do, but a clean object-oriented design can often help in avoiding bugs in multi-threaded applications.
The class collection I am going to present has often proved very useful to me. It contains handle wrappers for threads, as well as base classes that help implement worker and GUI threads.
The thread handle wrapper classes
There is not much to say about these classes. In the style usually followed by WTL, CThreadT
is a wrapper template class around a thread handle, which offers most of the Win32 API functions that take a thread handle as methods.
More conveniently, one will usually use the template instantiations CThread
and CThreadHandle
: CThread
will close the thread handle when it is destroyed, while CThreadHandle
won't.
Creating a thread
Thread creation is implemented as the static method CThreadT::Create
, which is a wrapper around _beginthreadex
(or CreateThread
, if the "minimal use of CRT" option is set).
CThread thread = CThread::Create( (LPTHREAD_START_ROUTINE)
MyThreadProc, pParam );
Additional methods of CThread and CThreadHandle
CThreadT(HANDLE=NULL, DWORD=0)
wraps a CThread
instance around the given thread handle and thread ID.
- There is also a copy constructor, which will call
DuplicateHandle
on the given handle.
- The thread handle and ID can be retrieved with
GetHandle()
and GetId()
.
- The methods
Open
, GetPriority
, SetPriority
, GetExitCode
, GetThreadTimes
, IsIOPending
, Resume
, Suspend
, Terminate
, and Exit
are all wrappers around the respective API functions, their names mostly net of the word "thread".
- The method
Join
performs a WaitForSingleObject
on the thread handle.
GUI Threads
GUI threads are different from general worker threads in that they have a message queue. This means that we can post messages to a GUI thread using PostThreadMessage
.
The template class CGuiThreadT
, and its instantiations CGuiThread
and CGuiThreadHandle
, are just like the CThreadT
classes, with an additional method PostThreadMessage
for this end.
Causing a GUI thread to quit is usually a matter of posting the WM_QUIT
message to its queue. This is done by the PostQuitMessage
method. However, this method does not wrap the API of the same name, as the latter posts WM_QUIT
to the calling thread, which is not always the same.
The thread implementation classes
As ATL and WTL often differentiate between a handle to an object and its implementation (compare CWindow
and CWindowImpl
), I have chosen the same design for my thread classes (though the two cases are not perfectly comparable). The class CThreadImpl
provides the skeleton for a thread "implementation" class.
Derive your thread class from CThreadImpl<T>
and implement the Run
method:
class CWorkerThread : public CThreadImpl<CWorkerThread>
{
public:
DWORD Run()
{
return 0;
}
};
CWorkerThread* pThread = new CWorkerThread;
The return value of Run
is the thread's exit code, like in the standard Win32 ThreadProc
.
If you create an instance of CWorkerThread
, it will start running immediately. If you want to start it later, you can pass CREATE_SUSPENDED
to CThreadImpl
's constructor and call Resume
later.
Note that the constructor runs in the creating thread, so some of the thread initialization may have to be moved to the run method.
Example
class CWorkerThread : public CThreadImpl<CWorkerThread>
{
public:
CWorkerThread()
: CThreadImpl<CWorkerThread>(CREATE_SUSPENDED)
{ }
BOOL Initialize()
{
return TRUE;
}
DWORD Run()
{
if ( !Initialize() )
return 1;
return 0;
}
};
CWorkerThread* pThread = new CWorkerThread;
pThread->Resume();
Implementation of GUI threads
The class CGuiThreadImpl
uses the WTL class CMessageLoop
to manage a message loop. However, since CAppModule
wants to know about all CMessageLoop
s in the process, this means you have to pass a pointer to your CAppModule
instance in the constructor.
To implement a GUI thread, derive your class from CGuiThreadImpl
and (optionally) override the following methods:
BOOL InitializeThread()
to perform thread initialization. This is, for example, a good place to create windows. Return FALSE
to stop the thread.
void CleanupThread(DWORD)
to perform cleanup tasks. The DWORD
parameter is the exit code from the message loop.
Handling messages
You can also add a message map to your thread class using the standard macro, BEGIN_MSG_MAP
. However, these will only be called for messages that are not directed to a window, i.e., where the hWnd
parameter is NULL
.
Remember that you can access the thread's message loop using CAppModule::GetMessageLoop()
, so you can, for example, install additional CMessageFilter
s. The place to do this would be the InitializeThread
and CleanupThread
methods.
Example
The following example shows a simple GUI thread class which creates a timer and responds to the WM_TIMER
message.
#include "Thread.h"
class CTimerThread : public CGuiThreadImpl<CTimerThread>
{
BEGIN_MSG_MAP(CTimerThread)
MESSAGE_HANDLER(WM_TIMER, OnTimer)
END_MSG_MAP()
private:
UINT_PTR m_nTimerId;
public:
CTimerThread(CAppModule* pModule)
: CGuiThreadImpl<CTimerThread>(pModule)
{ }
BOOL InitializeThread()
{
m_nTimerId = ::SetTimer(NULL, 0, 1000, NULL);
return (m_nTimerId != 0);
}
void CleanupThread(DWORD)
{
::KillTimer(NULL, m_nTimerId);
}
LRESULT OnTimer(UINT, WPARAM, LPARAM, BOOL&)
{
::MessageBeep(MB_ICONASTERISK);
return 0;
}
};
The "timer thread" has to be created and stopped from the main thread:
class CMainFrame : ...
{
CTimerThread* m_pTimerThread;
LRESULT OnCreate(LPCREATESTRUCT)
{
m_pTimerThread = new CTimerThread(&_Module);
}
void OnDestroy()
{
g_pTimerThread->PostQuitMessage();
g_pTimerThread->Join();
delete g_pTimerThread;
}
};
Using the classes
All the thread classes are contained in a single header file, Thread.h, which you can download using the link given at the start of the article. To facilitate the classes, you just need to include Thread.h in your project.
If you would like to use the classes in a ATL-but-not-WTL project, you will need to remove all the GUI-thread related sections from the code. The other classes (CThreadT
and CThreadImpl
) will work with "pure" ATL as well.
Conclusion
Using the set of classes presented in this article, it is possible to achieve a cleaner, more object-oriented design for multithreaded applications. The template design similar to the one found in other ATL/WTL classes makes it easy to understand and integrate.
Revision history
- 06-30-2006
- 06-31-2006
CThreadT::Create
now calls _beginthreadex
if possible (depending on _ATL_MIN_CRT
).
- Added method
CThreadT::Exit
.