views:

539

answers:

2

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  A: 

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
+3  A: 

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 Windows messages related to the message box. This is implemented with it's own message loop - no new thread is created, but your application remains responsive, because selected messages like Paint are handled calling recursively your WndProc() function, and some messages are not transmitted to, because of the modal type of the created window.

Sleep() and other functions called directly from your WndProc() handling a Windows message, would actually block the execution of your single threaded Message Loop, no other message would be processed.

CsTamas
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?
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
Ah, I understand now, thanks.