Modality, part 3: The WM_QUIT message


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.

Comments (23)
  1. Jonathan says:

    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.

  2. A. Skrobov says:

    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?

  3. Lonnie McCullough says:

    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.

  4. Daniel Bowen says:

    How about PeekMessage with PM_REMOVE? If the message that gets pulled off the queue is WM_QUIT, should you PostQuitMessage?

  5. /he says:

    /he GetMessage ??

  6. Raymond Chen says:

    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.

  7. rrrr says:

    A. Skrobov, as far as I know PostQuitMessage sets a flag only and PostThreadMessage posts a real message.

  8. Daniel Bowen says:

    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!

  9. Seth McCarus says:

    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.

  10. Lonnie McCullough says:

    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?

  11. Raymond Chen says:

    If you pass PM_NOREMOVE then the WM_QUIT message is not removed.

  12. Adrian says:

    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.

  13. Jack Mathews says:

    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.

  14. A says:

    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.

  15. Loop says:

    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.

    This is very common in single-threaded programs written, especially those written in VB6 which uses DoEvents(), this is usually used to prevent the gui from stop responding. Works fine as long the user doesn’t try to close the program.

  16. cola says:

    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.

  17. omg says:

    VB6’s DoEvents doesn’t handle WM_QUIT? Very Poor.

  18. Jorge Coelho says:

    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.

  19. Raymond Chen says:

    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.

  20. Exploiting the rules for handling of the WM_QUIT message.

  21. Just processing messages until EndDialog.

  22. Putting together pieces you already know.

Comments are closed.