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
Even though
PostQuitMessage
is called from a particular window's callback function, doesWM_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 toGetMessage
? Such a message loop would not be able to return0
as a reaction toWM_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;
}