0

Consider following code snippet where a message box is displayed upon a WM_TIMER message.

#define IDT_TIMER1 1001

INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    switch(message) {

        //...

        case WM_INITDIALOG:
        {
            //...

            SetTimer(hWnd, IDT_TIMER1, 1000, (TIMERPROC)NULL);

            break;
        }
        case WM_TIMER:
        {
            int ret = MessageBox(hWnd, L"Cancel operation?", NULL, MB_YESNO);
            if(ret == IDYES) {
                EndDialog(hWnd, 0);
            } else {
                // no-op: keep going
            }

            break;
        }

        //...

        default:
            return FALSE;
    }

    return FALSE;
}

I expect this code to display a message box at initial timer tick, and block the thread until user clicks a button. What actually happens is that a new message box is displayed at each timer tick even though user does not click any of the buttons.

When I inspect the call stack of the thread, I see multiple calls to DialogProc(), all stuck on line where MessageBox() is called(i.e. all waiting for user input).

Given the state of the call stack, how is it possible that DialogProc() keeps getting called in the same thread where MessageBox() has not yet returned in the last call to DialogProc()?

P.S. Note that I am not asking how to accomplish the desired behavior. I am only looking for insights to understand what is going on "under the hood" resulting in the actual behavior.

Kemal
  • 849
  • 5
  • 21

2 Answers2

9

MessageBox launches a new Message Loop, which amongst other things, has access to and will call your DialogProc, by the normal Windows callback mechanisms.

If it didn't do that then events like WM_PAINT wouldn't get handled and your app would look like it had died (apart from the message box). Because the timer is still running, the WM_TIMER events are enqueued at appropriate times.

Caleth
  • 52,200
  • 2
  • 44
  • 75
2

MessageBox enters nested message loop needed for rendering window, handling buttons.

Window specified as first argument in MessageBox call, has input disabled by EnableWindow but this doesn't disable all messages so you still receive WM_PAINT, WM_TIMER and others. Generally it disables user input: mouse, keyboard, but also window sizing with mouse.

This pseudo-code shows how MessageBox analog could be potentially implemented:

int MessageBox( HWND owner_hwnd, ... )
{
    ...
    HWND box_hwnd = CreateWindowEx( ..., owner_hwnd, ... );
    ...
    EnableWindow( owner_hwnd, FALSE );
    ...
    while ( !done )
    {
        MSG msg;
        GetMessage( &msg, ... );
        if ( IsDialogMessage( box_hwnd, &msg ) )
            continue;
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }
    ...
    EnableWindow( owner_hwnd, TRUE );
    ...
    DestroyWindow( box_hwnd );
    ...
    return button;
}
Daniel Sęk
  • 2,504
  • 1
  • 8
  • 17