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.
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);
}
}
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);
}
}
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);
}
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.
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.
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?
GetMessage
PostQuitMessage
WM_QUIT
Message#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;
}
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With