Creating Threads in MFC



Creating Threads  


Using _beginthreadex
The classic method of creating Win32 threads is to use _beingthreadex() to allocate your thread, like this:

HANDLE hThread = NULL;

DWORD myThread( DWORD arg )
{
  // do something
  _endthreadex(0);
  return 0;
}

void StartThread( void )
{
  SECURITY_ATTRIBUTES sa;
  DWORD threadId = 0;
  DWORD parm = 0;

  sa.nLength = sizeof( sa );
  sa.lpSecurityDescriptor = NULL;


  hThread = (HANDLE)_beginthreadex( (void*)&sa, 0,
  (unsigned (__stdcall *) (void *)) &myThread,
  (void*) &parm, 0, (unsigned int *)&threadId );
}

This works pretty well, and is a clean, non-MFC way of doing it. Even in an MFC application, it will work. Let's say you wanted to use a member of your CDoc as the thread function. This member can take any number (or no) parameters, and can be influenced by other CDoc members. You can do it with a wrapper function, passing this in as your parameter, like this:

DWORD CDoc::myThreadStub( DWORD arg )
{
  return ((CDoc*)arg)->myThread();
}

DWORD CDoc::myThread( void )
{
  // do something
  _endthreadex(0);
  return 0;
}

void CDoc::StartThread( void )
{
  SECURITY_ATTRIBUTES sa;
  DWORD threadId = 0;

  sa.nLength = sizeof( sa );
  sa.lpSecurityDescriptor = NULL;

  // assumes hThread is a member of CDoc
  hThread = (HANDLE)_beginthreadex( (void*)&sa, 0,
  (unsigned (__stdcall *) (void *)) &myThreadStub,
  (void*)this, 0, (unsigned int *)&threadId );
}

Everything is working fine. You make your thread fancier, popping up a system modal message box using AfxMessageBox() at some point. Everything works like a champ. Somewhere along the way, you need to add a more complex dialog. Something that does more than a simple message box will allow. Or maybe it is just a "Click Okay to Continue" message, but you want it to be a fancy dialog with a bitmap and a huge button. So you add your standard MFC dialog code:

CMyFancyDialog dlg;
if( IDOK == dlg.DoModal() )
{
  // do something fancy
}

Except it doesn't work. Maybe while running the release build, you get a GPF instead of your dialog. Or, if you a running in debug mode, you get an assert failure in wincore.cpp. What the heck?!? If you dig into the source of wincore.cpp you will see some comments from the MFC team:

// Note: if either of the above asserts fire and you are
// writing a multithreaded application, it is likely that
// you have passed a C++ object from one thread to another
// and have used that object in a way that was not intended.
// (only simple inline wrapper functions should be used)
//
// In general, CWnd objects should be passed by HWND from
// one thread to another.  The receiving thread can wrap
// the HWND with a CWnd object by using CWnd::FromHandle.
//
// It is dangerous to pass C++ objects from one thread to
// another, unless the objects are designed to be used in
// such a manner.
Okay, you feel a little better... enough people have run into this problem that there were comments in the MFC source. But what it doesn't seem to apply... you are running a multithreaded application, but you weren't passing CWnd objects. Do some searching on CWnd::FromHandle and there isn't much of relevence. This is where the global MFC function AfxBeginThread() comes in.

The assert that is failing is in the debug-mode function CWnd::AssertValid. It takes a given HWND and does a lookup of all threads registered with MFC and all HWNDs registered with MFC, and squawks if there isn't a match. This is done to ensure system integrity: if CWnd::AssertValid fails, there are other assumptions made throughout MFC that will also fail, which could cause your program to crash. As it may have done running the release build (note: I've had mixed results with this... sometimes it crashes in release, sometimes it works fine).

Using AfxBeginThread  


The solution is to make sure MFC knows about your thread, and you accomplish that by using AfxBeginThread() to create it. AfxBeginThread() wraps around _beginthreadex() and but also sets up various MFC things so you can do some basic MFC work with the thread. Note the matching use of AfxEndThread() to end the thread, in place of _endthreadex().

DWORD CDoc::myThreadStub( DWORD arg )
{
  return ((CDoc*)arg)->myThread();
}

DWORD CDoc::myThread( void )
{
  // do something
  AfxEndThread(0);
  return 0;
}

void CDoc::StartThread( void )
{
  // assume pThread is a CWinThread* member of CDoc
  pThread = AfxBeginThread( (AFX_THREADPROC) &myThreadStub, (void *)this );
}
A lot cleaner, and a lot safer, too.

Closing Threads  


One simple way of closing a worker thread is to use a bool flag the thread can monitor. When the value is true, the thread exits, like this:
DWORD CDoc::myThread( void )
{
  // assumes bStopThread is bool member of CDoc
  while( !bStopThread )
  {
    // do something
    ::Sleep( 100 );
  }
  AfxEndThread(0);
  return 0;
}
Then in your code, when you want to shut down the thread, you simply set the value true, and wait.

Using COM in threads  


If you wish to access COM objects in your multi-threaded application, there are are some potential problems. I have found that invoking the Get() methods of a COM object created by another thread of the same application usually works fine. But if you wish to use the Put() methods, errors will often result. It is as if you have read-only access to COM objects created by other threads. If read-only access is good enough, you're set. If not, you'll need to do some simple work. In the startup of your thread, call CoInitializeEx() with COINIT_MULTITHREADED, then create copied instances of the object you wish to use (make sure you release any objects and call CoUninitialize() when you're done). If CoInitializeEx() fails, something has gone wrong, and you'll probably want to exit your thread. For example:
DWORD CDoc::myThread( void )
{
  HRESULT hRes = CoInitializeEx( NULL, COINIT_MULTITHREADED );
  if ( FAILED( hRes ) )
  {
    AfxMessageBox( "CoInitializeEx() Failed for myThread!", MB_OK | MB_ICONSTOP, 0 );
    AfxEndThread(1);
    return 1;
  }

  // assumes bStopThread is bool member of CDoc
  while( !bStopThread )
  {
    // do something
    ::Sleep( 100 );
  }

  CoUninitialize();
  AfxEndThread(0);
  return 0;
}



[ Politics | Religion | Poetry | Music | Programming | Films | Photo Album ]
[ Top | Apostate?!? | Site Map | Search | What's New ]


Click here to receive email
when this page changes
These pages provided exclusively by apostate.com. Unless otherwise noted, all text Copyright 1994-1999 by Joshua J. Ellis (josh@apostate.com). All rights reserved. Use of the information hereon is subject to the terms of Legal Statement. Celtic artwork courtesy of Karen Nicholas.
/programming/mfc-threads.html last modified Mon, 31-Jul-2000
 
Translate to:

Powered by Systran