Date: | January 26, 2006 / year-entry #35 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20060126-00/?p=32513 |
Comments: | 13 |
Summary: | 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... |
We can use the #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
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)
Comments are closed. |
MWFMO_WAITANY?
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.)
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.
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.
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.
I called the wrong function, but if you have been reading and understanding you should have been able to fix it yourself.
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.
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).
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?
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.
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!)
);
It clogs up the messaging system.
PingBack from http://blog.strafenet.com/2006/09/30/msgwaitformultipleobjectsex/