tobin
tobin

Reputation: 127

Why can't I quit the MFC program when I used MsgWaitForMultipleObjects?

HANDLE g_event = CreateEvent(NULL, TRUE, FALSE, L"tasdfasdfsadfasdfasdfas");
BOOL bIsok = TRUE;
while(bIsok)
{
    DWORD dwTime = MsgWaitForMultipleObjects(1, &g_event, FALSE, 5000, QS_ALLINPUT);
    MSG msg;
    switch(dwTime)
    {
    case WAIT_OBJECT_0:
    break;

    case WAIT_OBJECT_0 + 1:
    {
        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            switch(msg.message)
            {
            case WM_DESTROY:
            case WM_CLOSE:
            case WM_QUIT:
                break;
            default:
                break;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    break;
    case WAIT_TIMEOUT:
    break;
    }
}

Run this code and then click the close-button. The window hides but the program doesn’t exit.

Why, and how can I resolve this?

Upvotes: 1

Views: 200

Answers (1)

IInspectable
IInspectable

Reputation: 51355

The process never terminates, because the while(bIsok) loop's predicate never evaluates to false. To fix this, the code needs to update the bIsok when it's time to shut down. The easiest fix would be to do so in the WM_QUIT message handler:

    if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        switch(msg.message)
        {
        case WM_DESTROY:
        case WM_CLOSE:
            break;
        case WM_QUIT:
            bIsok = false;
            break;
        default:
            break;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

While that addresses the immediate issue, there are still more issues with the code, that should be fixed, most notably failure to fully deplete the message queue, when messages arrive. The documentation for MsgWaitForMultipleObjects contains the following remark:

WAIT_OBJECT_0 + nCount: New input of the type specified in the dwWakeMask parameter is available in the thread's input queue. Functions such as PeekMessage, GetMessage, and WaitMessage mark messages in the queue as old messages. Therefore, after you call one of these functions, a subsequent call to MsgWaitForMultipleObjects will not return until new input of the specified type arrives.

Whenever a message arrives, the code handles only the first message, and then moves on to wait for new messages. Instead, the code should process all messages before moving on.

Not strictly required, but I would also apply the following changes:

  • Remove the timeout from the MsgWaitForMultipleObjects. It is not needed and merely causes the thread to wake up repeatedly, even if nothing happened.
  • Move the message handling into the window procedure for the toplevel window. Closing the window provides information on the standard procedure: Call DestroyWindow in response to WM_CLOSE, and PostQuitMessage in response to WM_DESTROY.

This is the window procedure of the toplevel window:

LRESULT CALLBACK MyWindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
    switch( uMsg )
    {
    case WM_CLOSE:
        ::DestroyWindow( hwnd );
        return 0;
    case WM_DESTROY:
        ::PostQuitMessage( 0 );
        return 0;
    default:
        return ::DefWindowProc( hwnd, uMsg, wParam, lParam );
    }
}

Updated main loop:

HANDLE g_event = CreateEvent(NULL, TRUE, FALSE, L"tasdfasdfsadfasdfasdfas");
BOOL bIsok = TRUE;
while(bIsok)
{
    DWORD dwTime = MsgWaitForMultipleObjects(1, &g_event, FALSE, INFINITE, QS_ALLINPUT);
    switch(dwTime)
    {
    case WAIT_OBJECT_0:
    break;

    case WAIT_OBJECT_0 + 1:
    {
        MSG msg;
        while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if(msg.message == WM_QUIT)
            {
                bIsok = false;
            }
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    break;
    case WAIT_TIMEOUT:
    break;
    }
}

Upvotes: 1

Related Questions