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.