Monday, May 17, 2010

Using MsgWaitForMultipleObjects in MFC

One of the problems that I didn't solve in my Multithreading book was how to use MsgWaitForMultipleObjectsEx with MFC. I have always felt guilty about this because it was an important problem, but I simply ran out of time to do the implemenation. Recently I finally had a need to solve the problem. MFC has come a long way since 1996 and it's clear that this problem was planned for. With MFC in Visual Studio 2008 and 2010, I was able to solve the problem in minutes instead of days.

In short, the solution is to replace MFC's call to GetMessage with your own call to MsgWaitForMultipleObjectsEx. This will allow you to dispatch messages, handle signaled objects, and dispatch Completion Routines. Here's the code, which goes in your MFC App object:

// virtual
BOOL CMyApp::PumpMessage()
{    HANDLE hEvent = ...;
    HANDLE handles[] = { hEvent };
    DWORD const res =
      ::MsgWaitForMultipleObjectsEx(
        _countof(handles),
        handles,
        INFINITE,
        QS_ALLINPUT,
        MWMO_INPUTAVAILABLE | MWMO_ALERTABLE);
    switch (res)
    {
        case WAIT_OBJECT_0 + 0:
            // the event object was signaled...
            return true;
        case WAIT_OBJECT_0 + _countof(handles):
            return __super::PumpMessage();
        case WAIT_IO_COMPLETION:
            break;
    }

    return TRUE;
}


There are several obscure points in this code worth mentioning. Getting any of these wrong will cause it to break:
  • The MWMO_INPUTAVAILABLE is required to solve race conditions with the message queue.
  • The MWMO_ALERTABLE is required in order for Completion Routines to be called.
  • WAIT_IO_COMPLETION does not require any action on your part. It just indicates that a completion routine was called.
  • The handle array is empty. You do NOT wait on the directory handle. Doing so will prevent your completion routine from being called.
  • MsgWaitForMultipleObjectsEx indicates that a message is available (as opposed to a signaled object) by returning WAIT_OBJECT_0 plus the handle count.
  • The call to __super::PumpMessage() is for MFC when this is running in your application object. Outside of MFC, you should replace it with your own message loop.
Note that this strategy breaks whenever a dialog box is displayed because dialog boxes use their own message loop.

1 comment: