10

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

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;
}
Community
  • 1
  • 1
Nicholas Miller
  • 4,205
  • 2
  • 39
  • 62
  • Hmya, everybody knows that a BOOL can be FALSE, TRUE or FileNotFound. If you get -1 then you are doing it really wrong, nothing you can handle. Not getting 0 from GetMessage() when you use PostQuitMessage() is really wrong too. You'll need to post repro code. Or use a library, writing reliable Win32 code today is rocket science that should be left to somebody that has to support it. – Hans Passant Sep 24 '15 at 19:20
  • @HansPassant Could you please elaborate what makes those scenarios wrong? I've gone ahead and appended my post with some code. – Nicholas Miller Sep 24 '15 at 19:37
  • Use #3. Since you are passing NULL for the window handle, and a valid MSG variable, GetMessage cannot return -1. Read Raymond Chen's article on the subject. – David Heffernan Sep 24 '15 at 22:09
  • 2
    [When will GetMessage return -1?](http://blogs.msdn.com/b/oldnewthing/archive/2013/03/22/10404367.aspx). Raymond Chen's blog also has several articles on how `WM_QUIT` and `PostQuitMessage()` operate, and how to use them correctly in message loops. – Remy Lebeau Sep 25 '15 at 20:35
  • Interesting. That would explain why I was receiving -1 as a return value when I pass an hWnd and attempt to close that window. That should mean I only need to check for -1 when not using a completely unfiltered message pump (i.e. not `GetMessage(&msg, NULL, 0, 0)`) – Nicholas Miller Sep 25 '15 at 21:47
  • Link went dead. It is the article published on March 22, 2013. See: https://blogs.msdn.microsoft.com/oldnewthing/20130322-00/?p=4873 – Nicholas Miller Jan 16 '16 at 06:33
  • @NicholasMiller Microsoft changed the links again, it's now at https://devblogs.microsoft.com/oldnewthing/20130322-00/?p=4873 hopefully, it stays there, so future references keep working – Tom Lint Nov 29 '22 at 21:04

2 Answers2

7

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.

Andy
  • 30,088
  • 6
  • 78
  • 89
5

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.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79
  • It is interesting to see that WM_QUIT is not a normal message. In regards to your quote, I believe the filter mentioned is what is specified by the last 2 parameters of `Getmessage` (wMsgFilterMin & wMsgFilterMax). It does not appear to me that the `HWND` parameter should be included as part of that filter even though it restricts which messages are retrieved. Unfortunately, I have no reliable way to test filtering. In my test for implementation #2, I logged all return values for `GetMessage` and `0` did not appear once. This is consistent with WM_QUIT being thread-relevant. – Nicholas Miller Sep 24 '15 at 20:04
  • 1
    @NickMiller I agree it's not completely clear, although all three parameters can be considered filters in the sense that they control which messages the functions return. – Jonathan Potter Sep 24 '15 at 20:07