The dialog manager, part 7: More subtleties in message loops


Last time, we solved the problem with the EndManualModalDialog function by posting a harmless message. Today, we're going to solve the problem in an entirely different way.

The idea here is to make sure the modal message loop regains control, even if all that happened were incoming sent messages, so that it can detect that the fEnded flag is set and break out of the modal loop.

Instead of changing the EndManualModalDialog function, we will change the modal message loop.

int DoModal(HWND hwnd)
{
 DIALOGSTATE ds = { 0 };
 HWND hdlg = CreateDialogParam(g_hinst, MAKEINTRESOURCE(1),
             hwnd, DlgProc, reinterpret_cast<LPARAM>(&ds));
 if (!hdlg) {
  return -1;
 }

 EnableWindow(hwnd, FALSE);
 MSG msg;
 msg.message = WM_NULL; // anything that isn't WM_QUIT
 while (!ds.fEnded) {
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
   if (msg.message == WM_QUIT) { /*  fixed 8am */
    break;
   } else if (!IsDialogMessage(hdlg, &msg)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
   } /* fixed 10am */
  } else if (!ds.fEnded) {
   WaitMessage();
  }
 }
 if (msg.message == WM_QUIT) {
  PostQuitMessage((int)msg.wParam);
 }
 EnableWindow(hwnd, TRUE);
 DestroyWindow(hdlg);
 return ds.iResult;
}

We changed the call to GetMessage into a call to the PeekMessage function, asking to remove the peeked message if any. Like GetMessage, this delivers any incoming sent messages, then checks if there are any posted messages in the queue. The difference is that whereas GetMessage keeps waiting if there are no posted message, PeekMessage returns and tells you that there were no posted messages.

That's the control we want. If PeekMessage says that it couldn't find a posted message, we check our fEnded flag once again, in case an incoming sent message set the fEnded flag. If not, then we call the WaitMessage function to wait until there is something to do (either an incoming sent message or a posted message).

Exercise: If the whole point was to regain control after sent messages are delivered, why isn't there a test of the fEnded flag immediately after DispatchMessage returns?

Comments (10)
  1. Jack Mathews says:

    Raymond… Completely off topic, but you may want to make a post about the new login stuff.

    Or if you could at least get a log in link on your page, that might help. It took some digging to figure out that I had to

    a) Log in to post a comment.. and

    b) Go to the main blogs.msdn.com to do it.

    I think the reduction in your comment traffic shows that other people are just as confused.

  2. Dave says:

    "…why isn’t there a test of the fEnded flag immediately after DispatchMessage returns? "

    There is, or at least the only thing between the return of DispatchMessage and the test of fEnded is most likely a single JMP instruction to the top of the while loop.

    Since IsDialogMessage has the side-effect of processing the message when it returns TRUE, the DispatchMessage in the main loop isn’t being used for any case where fEnded might be set.

    Is that loop handling WM_QUIT correctly now? PeekMessage retrieves WM_QUITs but GetMessage doesn’t and returns 0.

  3. waleri says:

    > why isn’t there a test of the fEnded flag immediately after DispatchMessage returns

    Because DispatchMessage() is invoked only for non-dialog messages. If for some reason dialog terminates while processing some of these messages, we’ll miss the check

  4. CoMargo says:

    But why not to use Event and MsgWaitForMultipleObjects instead of using boolean variable?

  5. Adrian says:

    Call me stupid, but I don’t get how this solves the problem. Can’t you get stuck in WaitMessage just like the previous implementation got stuck in GetMessage? Wouldn’t you still need to post a message to get the loop to wake up and see that the flag has changed?

    I suppose that if you assume the EndManualModalDialog only gets called while processing a message that was dispatched from the modal loop, it’ll work, but that seems like you’re solving for a specific case.

    [A sign-in link from the individual blog pages would be nice, since many of us navigate directly to postings we’re interested in from the RSS feed.]

  6. "But why not to use Event and MsgWaitForMultipleObjects instead of using boolean variable?"

    I would ask the question in reverse. Why use an Event and MsgWaitForMultipleObjects when a simple boolean variable is all you need?

    [Alas I do not control the server software. I’ll forward these concerns to the people who run the server.]

  7. Dave says:

    Okay, it wasn’t handling WM_QUIT correctly. To correct my previous comment, I meant to say that GetMessage returns 0 on WM_QUIT, but it does retrieve it.

    One topic I’d like to see discussed is when it is "safe" to ignore the fact that GetMessage can return -1. For example, am I safe if hwnd is valid and &msg is a good address? The GetMessage docs are pretty insistent that -1 is a possible outcome and should be handled gracefully.

  8. Ben Hutchings says:

    That code still isn’t quite right; the braces don’t match. If I’m following this correctly I think there should be two close-braces after the DispatchMessage() call.

  9. asdf says:

    Doesn’t this suffer from the same thing as http://blogs.msdn.com/oldnewthing/archive/2005/02/17/375307.aspx ?

  10. BillArnette says:

    Adrian: As I understand it (from the MSDN docs) GetMessage does not return until a *posted* message is in the queue. WaitMessage returns if *any* message (posted, input, sent, WM_PAINT, WM_TIMER) is in the queue.

    My stab at the exercise: From the SDK PeekMessage docs…

    "The PeekMessage function *dispatches* incoming *sent messages*, checks the thread message queue for a posted message, and retrieves the message (if any exist)."

    Therefore sent messages never get to the DispatchMessage branch.

Comments are closed.