Pumping messages while waiting for a period of time


We can use the MsgWaitForMultipleObjects function (or its superset MsgWaitForMultipleObjectsEx) to carry out a non-polling "sleep while processing messages".

#define MSGF_SLEEPMSG 0x5300

BOOL SleepMsg(DWORD dwTimeout)
{
 DWORD dwStart = GetTickCount();
 DWORD dwElapsed;
 while ((dwElapsed = GetTickCount() - dwStart) < dwTimeout) {
  DWORD dwStatus = MsgWaitForMultipleObjectsEx(0, NULL,
                    dwTimeout - dwElapsed, QS_ALLINPUT,
                    MWFMO_WAITANY | MWMO_INPUTAVAILABLE);
  if (dwStatus == WAIT_OBJECT_0) {
   MSG msg;
   while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
    if (msg.message == WM_QUIT) {
     PostQuitMessage((int)msg.wParam);
     return FALSE; // abandoned due to WM_QUIT
    }
    if (!CallMsgFilter(&msg, MSGF_SLEEPMSG)) {
     TranslateMessage(&msg);
     DispatchMessage(&msg);
    }
   }
  }
 }
 return TRUE; // timed out
}

This function pumps messages for up to dwTimeout milliseconds. The kernel of the idea is merely to use the MsgWaitForMultipleObjects/Ex function as a surrogate for WaitMessageTimeout, pumping messages until the cumulative timeout has been reached. There are a lot of small details to pay heed to, however. I've linked them to earlier postings that discuss the specific issues, if you need a refresher. The CallMsgFilter you might find gratuitous, but you'll change your mind when you realize that users might press a keyboard accelerator while you're sleeping, and you presumably want it to go through somebody's TranslateAccelerator. The message filter lets you hook into the modal loop and do your accelerator translation.

Extending this function to "wait on a set of handles up to a specified amount of time, while pumping messages" is left as an exercise. (You can do it without changing very many lines of code.)

[Call the right function. -2pm]

Comments (13)
  1. asdf says:

    MWFMO_WAITANY?

  2. BryanK says:

    Well, I must say that the code we wrote to do this seems a lot simpler in VB6 (though I don’t know if it’s entirely correct!):

    Do Until <button was clicked>

    If MsgWaitForMultipleObjects(…) = WAIT_OBJECT_0 Then

    DoEvents

    EndIf

    Loop

    (This used to be a "Do Until <button was clicked> DoEvents" loop, which used a ton of CPU doing exactly nothing. The DoEvents call runs VB6’s message loop on any messages that have come in, but if nothing’s there, it just returns immediately.)

    I don’t know if this is actually 100% correct because I don’t know whether DoEvents makes the CallMessageFilter call, or handles WM_QUIT properly. I would guess that WM_QUIT would be OK, but I don’t know for sure.

    (We also didn’t have a timeout, we were just waiting for the user to click a button. We needed to keep pumping messages because we had several VB Timer objects set up to pacify a hardware watchdog, so if we never called DoEvents, the timer handlers would never fire, and the watchdog would reset the machine.)

  3. Determining the correct value for MWFMO_WAITALL I leave an an exercise to see if you really understand the issue.

    BryanK: If you aren’t interested in timeout behavior, then you can just use WaitMessage.

  4. Mike Dimmick says:

    OK, what you’re doing with MWMO_INPUTAVAILABLE is now completely contrary to MSDN. Is the MSDN documentation incorrect? Should you be using MsgWaitForMultipleObjectsEx instead?

    The value of MWFMO_WAITALL should be TRUE while the value of MWFMO_WAITANY should be FALSE, if the MSDN documentation for MsgWaitForMultipleObjects is correct.

  5. Carlos says:

    MsgWaitForMultipleObjects is a wrapper around MsgWaitForMultipleObjectsEx and from the code it looks like the documentation is correct:

    xor eax,eax

    cmp dword ptr [ebp+10h],eax

    setne al

    push eax

    i.e. the third parameter is treated as a BOOL and 0 or 1 (MWMO_WAITALL) is sent to MsgWaitForMultipleObjectsEx. So the flags Raymond passes are getting eaten.

  6. I called the wrong function, but if you have been reading and understanding you should have been able to fix it yourself.

  7. I would have preferred a way to get an event handle for the message queue, if only because a custom function like MsgWaitForMultipleObjects is difficult (if not impossible) to integrate with existing C++ general framework synchronization wrappers.

  8. BryanK says:

    I went back and looked at the code — it wasn’t using a timeout, but it was waiting for an event to get signaled.

    Basically, we were trying to emulate the VB6 MsgBox function (which doesn’t return until the message-box goes away), while still allowing the watchdog timers to fire (so we had to pump messages somehow). Normally the VB MsgBox function disables timers.

    We brought up a form using vbModeless, and the form had an auto-reset event that it signaled inside its button click handler.

    Then the replacement MsgBox function did the MsgWaitForMultipleObjects(…) loop, with the auto-reset event handle from the form that it brought up.

    I’m still not sure this is safe, but now it’s more because I don’t know whether the auto-reset event was the right kernel object to wait on (since VB6 is single-threaded, and the signalling thread *was* the thread that later re-waited). I believe it was probably OK, because it does work (we didn’t use PulseEvent, but rather SetEvent).

  9. Anthony Wieser says:

    Looks like the flags and timeout are swapped in:

    MsgWaitForMultipleObjectsEx(0, NULL, WFMO_WAITANY | MWMO_INPUTAVAILABLE, dwTimeout – dwElapsed, QS_ALLINPUT);

    Shouldn’t it be:

    MsgWaitForMultipleObjectsEx(0, NULL, dwTimeout – dwElapsed, WFMO_WAITANY | MWMO_INPUTAVAILABLE, QS_ALLINPUT);

    And MWFMO_WAITANY is 0, right?

  10. ghbyrkit says:

    By jove, I think that Anthony is correct about there being an error in Raymond’s code! And I was about to use this method just as incorrectly as Raymond did! Why did it take all of us so long to spot this? And I also think that MWFMO_WAITANY isn’t defined in the system header files (at least the ones that VS6 uses), and that its value should be zero.

  11. ghbyrkit says:

    And upon further reflection (and consulting the Platform SDK documents), the QS_ALLINPUT goes BEFORE the MWMO_INPUTAVAILABLE parameter as well!

    So shouldn’t it be:

    DWORD dwStatus = MsgWaitForMultipleObjectsEx(

    0, // handle count

    NULL, // handle array

    dwTimeout – dwElapsed, // time to wait

    QS_ALLINPUT, // wait options

    MWFMO_WAITANY | MWMO_INPUTAVAILABLE // wait options (MWFMO_WAITANY is NOT defined by the system!)

    );

  12. It clogs up the messaging system.

Comments are closed.

Skip to main content