After
our two quick introductions
to modality,
we're now going to dig in a little deeper.
The trick with modality is that when you call a modal function,
the responsibility of message dispatch is handled by that function
rather than by your main program. Consequently, if you have
customized your main program's message pump, those customizations
are lost once you lose control to a modal loop.
The other important thing about modality is that a
WM_QUIT
message always breaks the modal loop.
Remember this in your own modal loops! If ever
you call
the PeekMessage
function
or
The [typo fixed 10:30am] GetMessage
function and get
a WM_QUIT
message, you must not only exit your modal loop, but
you must also re-generate the WM_QUIT
message
(via
the PostQuitMessage
message)
so the next outer layer will see the WM_QUIT
message
and do its cleanup as well. If you fail to propagate
the message, the next outer layer will not know that it
needs to quit, and the program will seem to "get stuck"
in its shutdown code, forcing the user to
terminate the process the hard way.
In a later series, we'll see how this convention surrounding
the WM_QUIT
message is useful.
But for now, here's
the basic idea of how your modal loops should re-post
the quit message to the next outer layer.
BOOL WaitForSomething(void)
{
MSG msg;
BOOL fResult = TRUE; // assume it worked
while (!SomethingFinished()) {
if (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
// We received a WM_QUIT message; bail out!
CancelSomething();
// Re-post the message that we retrieved
PostQuitMessage(msg.wParam);
fResult = FALSE; // quit before something finished
break;
}
}
return fResult;
}
Suppose your program starts some operation and then calls
WaitForSomething()
.
While waiting for something to finish, some other part of your
program decides that it's time to exit.
(Perhaps the user clicked on a "Quit" button.)
That other part of the program will call
PostQuitMessage(wParam)
to indicate that the message loop should terminate.
The posted quit message will first be retrieved by the
GetMessage
in the WaitForSomething
function.
The GetMessage
function returns FALSE
if
the retrieved message is a WM_QUIT
message.
In that case, the "else" branch of the conditional is taken, which
cancels the "Something" operation in progress, then posts
the quit message back into the message queue for the next
outer message loop to handle.
When WaitForSomething
returns, control presumably will fall
back out into the program's main message pump. The main message
pump will then retrieve the WM_QUIT
message and do its
exit processing before finally exiting the program.
And if there were additional layers of modality between
WaitForSomething
and the program's main message pump,
each of those layers would retrieve the WM_QUIT
message,
do their cleanup, and then re-post the WM_QUIT
message
(again, via PostQuitMessage
) before exiting the loop.
In this manner, the WM_QUIT
message gets handed from modal
loop to modal loop, until it reaches the outermost loop, which
terminates the program.
"But wait," I hear you say. "Why do I have to do all this
fancy WM_QUIT
footwork? I could just have a private little
global variable named something like g_fQuitting
. When
I want the program to quit, I just set this variable, and all
of my modal loops check this variable and exit prematurely if it
is set. Something like this:
BOOL MyWaitForSomething(void) // code in italics is wrong
{
MSG msg;
while (!SomethingFinished()) {
if (g_fQuitting) {
CancelSomething();
return FALSE;
}
if (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return TRUE;
}
And so I can solve the problem of the nested quit without needing
to do all this PostQuitMessage
rigamarole."
And you'd be right, if you controlled every single modal loop in
your program.
But you don't.
For example, when you call
the DialogBox
function,
the dialog box code
runs its own private modal loop to do the dialog box UI until
you get around to calling
the EndDialog
function. And whenever the user
clicks on any of your menus, Windows runs its own private
modal loop to do the menu UI. Indeed, even the resizing of
your application's window is handled by a Windows modal loop.
Windows, of course, has no knowledge of your little
g_fQuitting
variable, so it has no idea that you want
to quit. It is the WM_QUIT
message that serves this
purpose of co-ordinating the intention to quit among separate
parts of the system.
Notice that this convention regarding the WM_QUIT
message cuts both ways. You can use this convention to cause
modal loops to exit (we'll see more of this later), but it also
obliges you to respect this convention so that
other components (including the window manager itself)
can get your modal loops to exit.
I’m guilty of using the private little global variable… Guess I’ll need to make a change one of these days ;-) Thanks Raymond for your insightful postings.
Once we’re on WM_QUIT, I’d like to ask: what’s the difference between PostQuitMessage and PostThreadMessage(GetCurrentThreadId, WM_QUIT)? Is there any?
What about using PeekMessage with PM_NOREMOVE? Do we still have to call PostQuitMessage or will our non-removal of the message from the queue cause it to get correctly handled at the next highest layer? In my head this seems like the way it should work, but am I missing some kind of subtle error here? I’ve written several of my modal message loops this way and never had any problems with incorrect message handling so my emperical evidence says its cool.
How about PeekMessage with PM_REMOVE? If the message that gets pulled off the queue is WM_QUIT, should you PostQuitMessage?
/he GetMessage ??
A. Skrobov: I’ll return to this topic in a few months.
Lonnie McCullough/Daniel Bowen: The point of the article isn’t PeekMessage vs GetMessage. The point is propagation of the WM_QUIT message.
A. Skrobov, as far as I know PostQuitMessage sets a flag only and PostThreadMessage posts a real message.
Thanks for the reply! It looks like I’ll definitley need to update the "WaitWithMessageLoop" I asked about – http://weblogs.asp.net/oldnewthing/archive/2005/02/17/375307.aspx#375748 . I also found this – http://www.mvps.org/user32/modal.html, which uses PostMessage(NULL,WM_QUIT,0,0); to repost WM_QUIT. However, a later sample uses PostQuitMessage.
For that WaitWithMessageLoop, should I have
A.
while(::PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
{
if(msg.message == WM_QUIT)
{
return FALSE;
}
…
B.
while(::PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
{
if(msg.message == WM_QUIT)
{
::PostMessage(NULL,WM_QUIT,0,0);
return FALSE;
}
…
C.
while(::PeekMessage(&msg,NULL,NULL,NULL,PM_REMOVE))
{
if(msg.message == WM_QUIT)
{
::PostQuitMessage(msg.wParam);
return FALSE;
}
…
D. Something else?
Thanks!
The Microsoft KB has an article that relates to this, concerning creating a timed message box:
http://support.microsoft.com/default.aspx?scid=kb;en-us;181934
A quit message is posted to MessageBox’s modal loop, which closes the message box, but then your app continues as usual.
I know the article is about propigation of the WM_QUIT message. My question was whether or not using PM_NOREMOVE relieves me of the responsiblity of propigating WM_QUIT. As I have written a relatively large number of message pumping code routines over my career I just want to know if the thing I’ve been doing this entire time is correct or not. As I said I have never observed any strangeness here, but the docs on MSDN explicitly call out the WM_QUIT message and since it is obviously a somewhat special case I just wanted to ensure that PeekMessage retrieving it with PM_NOREMOVE does what I expect it to. I think that my question is germain to the discussion at hand especially since you mention PeekMessage in your post. This is what the code I’ve written looks like:
while( true ) {
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
if (msg.message == WM_QUIT)
return;
if (ShouldExitModalLoop())
return;
PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Or something close to that. If what rrrr is saying is true then it seems that calling PeekMessage may clear the WM_QUIT flag regardless of the PM_REMOVE / PM_NOREMOVE flag passed to it. I’ve observed that this works fine, but is there some corner case that I’m not aware of?
If you pass PM_NOREMOVE then the WM_QUIT message is not removed.
The SDK documentation rather emphatically points out that the return value for GetMessage() is not a simple TRUE/FALSE. Supposedly it can return -1 if there is an error. So technically:
while (GetMessage(…)) {…}
is wrong. It should be:
while (GetMessage(…) > 0) {…}
The Visual Studio wizards still don’t do this, and even Fifth Edition Petzold examples don’t get it right.
The SDK suggests that errors can occur if your HWND or the pointer to the MSG struct is invalid, but it’s not clear if those are the only error cases. If those are the only cases, then you’re OK as a long as your sure of those parameters.
And I suppose you should only repost a WM_QUIT if it actually was a WM_QUIT that caused you to exit and not an error. Otherwise an error could cause your program to exit without saving data.
Adrian:
If you’re only inspecting a specific HWND, I’m not sure that you’ll even get the WM_QUIT. If your MSG pointer is bad, you’d better just quit your app, because you’re about to crash or do very bad things.
Adrian wrote:
> So technically:
>
> while (GetMessage(…)) {…}
>
> is wrong. It should be:
>
> while (GetMessage(…) > 0) {…}
Technically, that’s wrong too. :)
According to the documentation, only -1 indicates failure, but you’re treating all return values less than 0 as failures.
Programs use WM_QUIT to terminate modal loops? That sounds dangerous to me: how do they distinguish between their own propagated quit message and someone else’s quit? I guess we’ll find out.
VB6’s DoEvents doesn’t handle WM_QUIT? Very Poor.
One related question though:
Any idea why Explorer windows don’t close in XP (all other windows do) with
IsHung = SendMessageTimeout(hwnd, WM_CLOSE, 0&, 0&, SMTO_ABORTIFHUNG, 1000, Killed)
The system just beeps, instead, and it takes a
PostMessage(hwnd, WM_CLOSE, 0&, 0&)
to that window for it to close.
I don’t know but I can guess – and in fact so can you: RPC_E_CANTCALLOUT_ININPUTSYNCCALL. There are some things that cannot be done in response to a sent message. WM_CLOSE is normally posted.
Exploiting the rules for handling of the WM_QUIT message.
Just processing messages until EndDialog.
Putting together pieces you already know.
PingBack from http://www.adrianmccarthy.com/blog/?p=52