13

Why is it that if I call a seemingly synchronous Windows function like MessageBox() inside of my message loop, the loop itself doesn't freeze as if I called Sleep() (or a similar function) instead? To illustrate my point, take the following skeletal WndProc:

int counter = 0;

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_CREATE:
             SetTimer(hwnd, 1, 1000, NULL); //start a 1 second timer
             break;
        case WM_PAINT:
             // paint/display counter variable onto window
             break;
        case WM_TIMER: //occurs every second
             counter++;
             InvalidateRect(hwnd, NULL, TRUE); //force window to repaint itself
             break; 
        case WM_LBUTTONDOWN: //someone clicks the window
             MessageBox(hwnd, "", "", 0);
             MessageBeep(MB_OK); //play a sound after MessageBox returns
             break;
        //default ....
    }
    return 0;
}

In the above example, the program's main function is to run a timer and display the counter's value every second. However, if the user clicks on our window, the program displays a message box and then beeps after the box is closed.

Here's where it gets interesting: we can tell MessageBox() is a synchronous function because MessageBeep() doesn't execute until the message box is closed. However, the timer keeps running, and the window is repainted every second even while the message box is displayed. So while MessageBox() is apparently a blocking function call, other messages (WM_TIMER/WM_PAINT) can still be processed. That's fine, except if I substitute MessageBox for another blocking call like Sleep()

    case WM_LBUTTONDOWN:
         Sleep(10000); //wait 10 seconds
         MessageBeep(MB_OK);
         break;

This blocks my application entirely, and no message processing occurs for the 10 seconds (WM_TIMER/WM_PAINT aren't processed, the counter doesn't update, program 'freezes', etc). So why is it that MessageBox() allows message processing to continue while Sleep() doesn't? Given that my application is single-threaded, what is it that MessageBox() does to allow this functionality? Does the system 'replicate' my application thread, so that way it can finish the WM_LBUTTONDOWN code once MessageBox() is done, while still allowing the original thread to process other messages in the interim? (that was my uneducated guess)

Thanks in advance

2 Answers2

12

The MessageBox() and similar Windows API functions are not blocking the execution, like an IO operation or mutexing would do. The MessageBox() function creates a dialog box usually with an OK button - so you'd expect automatic handling of the window messages related to the message box. This is implemented with its own message loop: no new thread is created, but your application remains responsive, because selected messages (like for painting) are handled calling recursively your WndProc() function, while other messages are not transmitted, because of the modal type of the created window.

Sleep() and other functions (when called directly from your WndProc() handling a window message) would actually block the execution of your single threaded message loop - no other message would be processed.

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
CsTamas
  • 4,103
  • 5
  • 31
  • 34
  • While I understand that MessageBox has its own message loop, what I don't understand is how MessageBox() can return X seconds later to my application and continue execution (in this case, move on to MessageBeep) while my application has supposedly already moved on to other messages (the WM_PAINT/WM_TIMER messages) in the meantime. That seems as if there are two different paths of execution (one still completing the old WM_LBUTTONDOWN message, and other(s) dealing with the other messages). Am I wrong? –  Aug 10 '09 at 20:38
  • 1
    That's the call stack. When you click on the application, it starts handling the button down message, so it reaches the switch case with WM_LBUTTONDOWN. Calling the MessageBox() function will create a new window, and have it's own loop for handling messages. Messages of the message box are handled inside this loop, messages for the application (like the Paint or Timer) will be handled by calling your WndProc recursively. The MessageBox() function will not return while the window is not closed in some way, at that point it will return to the next statement after the MessageBox() function. – CsTamas Aug 10 '09 at 21:17
  • Your message is correct but unclear: "selected messages like Paint are handled calling recursively your WndProc() function, and some messages are not transmitted to" – Gabriel Jul 13 '21 at 11:16
3

MessageBox runs its own Win32 message loop (so as not to freeze calling app).

Beware of using it in non reentrant functions...

EDIT: to elaborate: Message loop on windows is something like that (stolen from msdn):

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
} 

DispatchMessage will call whatever window procedure it needs to. That window proc can start its own loop (on the same thread), and it will call DispatchMessage itself, which will call whatever message handlers.

If you want to see it, launch your app in debugger, pop up message box and break. You will be dropped somewhere within its loop. Look at the callstack and see if you can find parent loop.

Eugene
  • 7,180
  • 1
  • 29
  • 36
  • Could you elaborate on what non-reentrant means? I'm trying to create a message box when an unhandled exception has been created and the main window is unresponsive, and calling `MessageBox()` doesn't make a box, just plays the sound that the messagebox would create. Wondering if reentrant is related, or other things could be used to make sure the box turns up, like rolling my own dialog with its own wndproc. – Phi May 19 '21 at 01:05
  • 1
    @Phi Imagine you have a FileSave() function. User presses "Save" in UI somewhere, function prepares some data and asks user a question via a MessageBox. While user is deliberating you also happen to have an autosave functionality implemented through WM_TIMER events and such an event arrives and launches FileSave() function again. Which asks the same question again, popping up another message box. When user comes back from lunch, they see 2000 message boxes waiting and more coming up. – Eugene May 20 '21 at 14:45
  • 1
    Call stack will look like FileSave() -> MessageBox() -> EventLoop() -> FileSave() -> MessageBox() -> EventLoop() -> FileSave() -> MessageBox() -> ... Simple way to solve it is having a flag inside FileSave that would exit early if saving process is already started, but not all such problems are as obvious or as easy to fix. – Eugene May 20 '21 at 14:47
  • 1
    As for you actual problem, not sure, likely not related to reentrancy – Eugene May 20 '21 at 14:52
  • Yea, I don't think that's the problem... thanks for the explanation tho! – Phi May 22 '21 at 02:22