GetMessage ignores posted messages received during the execution of a WinEvent callback

Applies to: Windows Server 2008 EnterpriseWindows 7 EnterpriseWindows 7 Professional

Symptoms


GetMessage appears not to receive messages that are posted while a WinEvent callback function is being executed. If another message enters the queue, both messages will be received and processed.

Cause


There is a known issue with GetMessage and the use of WinEvent callbacks that themselves use Windows messages. GetMessage ignores a message that is received during the execution of the callback. Therefore, in particular, if the callback function calls PostThreadMessage, that message will not register until another message enters the queue (for example, due to a mouse movement); at that point, both the earlier posted message and the new message will register and be processed.

Resolution


To work around this block, we recommend using MsgWaitForMultipleObjects and PeekMessage instead of GetMessage. It is also necessary to create a new event that can be sent from the WinEvent handler to wake up the message loop.

More Information


Here is a code fragment that includes a generic message loop and a WinEvent handler:


// Main message loop of thread that called SetWinEventHook while(GetMessage(&msg, NULL, 0, 0))
{
 ... process message here ...
}

void CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD eventId,
HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread,
DWORD dwmsEventTime)
{
... process winevent here ...
}


GetMessage can block in the above fragment after failing to register a message sent from WinEventProc. Instead, use MsgWaitForMultipleObjects and PeekMessage together with declaring and using a special wake-up event from the handler back to the message loop, as shown in this fragment:

HANDLE ghMessageReadyEvent = NULL; // Event used to wake up the main thread

ghMessageReadyEvent = CreateEvent(NULL, FALSE/*bManualReset*/,
FALSE/*bIntialState*/, NULL);

// Main message loop of thread that called SetWinEventHook
//
// We can't reliably use GetMessage here. If a posted message is received
// while we're in a reentrant call to WinEventProc, when the WinEventProc
// returns, GetMessage does not recognize that a message has been
// received and just blocks. To avoid this we use
// MsgWaitForMultipleObjects + PeekMessage. Code at the end of the
// WinEventProc checks if a posted message was received during the
// callback using PeekMessage; if so, it sets the event which causes
/ MsgWaitForMultipleObjects to wake up.
//
// Note that MsgWaitForMultipleObjects usually only wakes up if a message
// is received during it. Even with QS_ALLPOSTMESSAGE, it only wakes up
// if a message is received between it and the most recent PeekMessage,
// so we have to call PeekMessage *before* calling it in case there are
// multiple queued-up messages.

for(;;)
{
BOOL fGot = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
if(!fGot)
{
MsgWaitForMultipleObjects(1, &ghMessageReadyEvent,
FALSE/*bWaitAll*/, INFINITE,
QS_ALLEVENTS | QS_ALLPOSTMESSAGE);
fGot = PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
}
if(!fGot)
{
continue;
}
... process message here ...
}


void CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook, DWORD eventId,
HWND hwnd, LONG idObject, LONG idChild, DWORD dwEventThread,
DWORD dwmsEventTime)
{
... process winevent here ...

// Before we return, check if posted messages were received during this
// callback. If so, fire the event to ensure that we do process them.

MSG msg;
if(PeekMessage(&msg, (HWND)-1, WM_USER + 1, WM_USER + 2,
PM_NOREMOVE | PM_QS_POSTMESSAGE))
{
SetEvent(ghMessageReadyEvent);
}
}