Thread classes for WTL

Thread classes for WTL

 

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 CThreadHandleCThread 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 OpenGetPrioritySetPriorityGetExitCodeGetThreadTimesIsIOPendingResumeSuspendTerminate, 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:

C++
class CWorkerThread : public CThreadImpl<CWorkerThread>
{
public:
  DWORD Run()
  {
    // Do something useful...

    return 0;
  }
};

//
// In some other function, that is called from your main thread:

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()
  {
    // Perform initialization.
    return TRUE;
  }

  DWORD Run()
  {
    if ( !Initialize() )
      return 1;

    // Do something useful...
    
    return 0;
  }
};

//
// In some other function, that is called from your main thread:

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 CMessageLoops 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 CMessageFilters. 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.

posted @ 2022-12-13 15:21  小风风的博客  阅读(25)  评论(0编辑  收藏  举报