Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WM_QUIT only posts for thread and not the window?

In Windows API, I'm looking into how the GetMessage function actually works. I've seen 3 implementations of the Windows message loop and would like to explore them.


1)

As of the time of writing this post, this MSDN article describes what I believe to be the correct way to implement the message loop.

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


2)

On the GetMessage function page, I see this implementation:

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


3)

Last, the Visual Studio documentation has this implementation as part of their Win32 Application demonstration.

MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


Discussion

In short, implementation #3 disregards errors returned from GetMessage, but otherwise works the same as the first implementation. That is, they both process all messages for the current thread. And when the GetMessage function returns 0, the loops terminate.

Since I found implementation #2 before #1, I thought it was complete. However, I've noticed that GetMessage does not return 0 when the WM_QUIT message is posted via PostQuitMessage

This led to a bit of confusion, until I found implementation #1 and tested it. The difference between the first two implementations is the 2nd parameter to GetMessage. In #2, it specifies the hWnd, which according to the GetMessage documentation is:

A handle to the window whose messages are to be retrieved. The window must belong to the current thread.

In #1, it is NULL, which pertains to this excerpt:

If hWnd is NULL, GetMessage retrieves messages for any window that belongs to the current thread, and any messages on the current thread's message queue whose hwnd value is NULL (see the MSG structure). Therefore if hWnd is NULL, both window messages and thread messages are processed.

When testing using NULL, the GetMessage function returns 0 when the WM_QUIT message is processed, successfully terminating the loop.

Questions

  1. Even though PostQuitMessage is called from a particular window's callback function, does WM_QUIT actually belong to the window or the current thread? Based on testing these three implementations, it appears to be associated with the current thread.

  2. If associated with the thread, when it is useful or appropriate to use a valid hWnd as a parameter to GetMessage? Such a message loop would not be able to return 0 as a reaction to WM_QUIT, so is there another way that the message loop should terminate?

References

  • GetMessage
  • PostQuitMessage
  • Message Loop #1 (Wikipedia Link)
  • Message Loop #2
  • Message Loop #3
  • WM_QUIT Message
  • Related Question

Code

#include <Windows.h>
#include <tchar.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch(msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR cmdLine, int nCmdShow) {
    LPCTSTR wndClassName =_T("Class_SHTEST");
    LPCTSTR wndName = _T("SHTest");

    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW|CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
    wcex.hCursor = LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW));
    wcex.hbrBackground = (HBRUSH) COLOR_WINDOW+1;
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = wndClassName;
    wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

    if (!RegisterClassEx(&wcex))
    {
        MessageBox(NULL, _T("Call to RegisterClassEx failed!"), wndName, MB_OK|MB_ICONERROR);
    }

    HWND window = CreateWindow(wndClassName, wndName,
        WS_OVERLAPPEDWINDOW | WS_MAXIMIZE, 
        0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL);
    if (!window) {
        MessageBox(NULL, _T("Call to CreateWindow failed!"), wndName, MB_OK|MB_ICONERROR);
    }

    ShowWindow(window, SW_SHOW);
    UpdateWindow(window);

    //Message loop (using implementation #1)
    MSG msg;
    BOOL bRet;
    while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
        if (bRet == -1) {
            //Handle error and possibly exit.
        }
        else {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    //Return the exit code in the WM_QUIT message.
    return (int) msg.wParam;
}
like image 536
Nicholas Miller Avatar asked Sep 24 '15 19:09

Nicholas Miller


2 Answers

Per the MSDN documention of WM_QUIT:

The WM_QUIT message is not associated with a window and therefore will never be received through a window's window procedure. It is retrieved only by the GetMessage or PeekMessage functions.

Since WM_QUIT is not associated with a window, and passing an HWND to GetMessage() only retrieves those messages associated with that window, the latter will never receive WM_QUIT by design.

As for when you would want to pass an HWND to GetMessage(), in a general message loop for an application you wouldn't. But there are times when you want to pump messages while something in the UI is happening, and are only concerned with messages that are associated with a specific window.

like image 109
Andy Avatar answered Nov 10 '22 21:11

Andy


WM_QUIT is relevant to a thread, not an individual window. Note the lack of a hwnd parameter for PostQuitMessage(). There's no way it could be window-specific because there is no way to tell it which window to generate the message for.

WM_QUIT is not actually a real message. When you call PostQuitMessage() an internal flag is set in the message queue state that WM_QUIT has been requested. This will be auto-generated by GetMessage() or PeekMessage() at some point in the future (often immediately, but if the queue contains other posted messages these will be processed first).

This is explained in more detail on Raymond Chen's blog, which also contains the following quote:

As another special behavior, the generated WM_QUIT message bypasses the message filters passed to the GetMessage and PeekMessage functions. If the internal "quit message pending" flag is set, then you will get a WM_QUIT message once the queue goes quiet, regardless of what filter you pass.

This suggests that your observations are wrong, and that GetMessage() in your example #2 above should return 0 after PostQuitMessage() is called even though a filter parameter has been provided.

In general you should only use message filters if you have a specific need for them (e.g. you specifically want to retrieve a message posted to a particular window). In most cases those parameters should all be set to 0 for normal functioning of your UI.

like image 30
Jonathan Potter Avatar answered Nov 10 '22 21:11

Jonathan Potter