在Windows API中,我正在研究GetMessage
函数的实际工作方式。我已经看过3个Windows消息循环的实现,并希望探索它们。
<小时/>
截至撰写本文时,this MSDN article描述了我认为实现消息循环的正确方式。
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);
}
}
<小时/>
在GetMessage
function page上,我看到了这个实现:
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);
}
}
<小时/>
最后,Visual Studio documentation将此实现作为其Win32应用程序演示的一部分。
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
<小时/>
简而言之,实现#3忽略了从GetMessage
返回的错误,但其他方式与第一个实现相同。也就是说,它们都处理当前线程的所有消息。当GetMessage
函数返回0
时,循环终止。
由于我在#1之前发现了#2,我认为它已经完成了。但是,我发现当GetMessage
消息通过PostQuitMessage
发布时,0
不会返回WM_QUIT
这导致了一些混乱,直到我发现实现#1并对其进行了测试。前两个实现之间的差异是GetMessage
的第二个参数。在#2中,它指定hWnd
,根据GetMessage
文档是:
要检索其消息的窗口句柄。该窗口必须属于当前线程。
在#1中,它是NULL
,与此摘录有关:
如果hWnd为NULL,则GetMessage将检索属于当前线程的任何窗口的消息,以及当前线程的hwnd值为NULL的消息队列中的任何消息(请参阅MSG结构)。因此,如果hWnd为NULL,则处理窗口消息和线程消息。
使用NULL
进行测试时,GetMessage
函数在处理0
消息时返回WM_QUIT
,成功终止循环。
即使从特定窗口的回调函数调用PostQuitMessage
,WM_QUIT
实际上属于窗口还是当前线程?基于测试这三个实现,它似乎与当前线程相关联。
如果与该主题相关联,那么将有效的hWnd
用作GetMessage
的参数是否有用或适当?这样的消息循环无法返回0
作为对WM_QUIT
的反应,那么消息循环应该以另一种方式终止吗?
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;
}
答案 0 :(得分:5)
根据WM_QUIT
的{{3}}:
WM_QUIT消息与窗口无关,因此会 永远不会通过窗口的窗口程序接收。它被检索 只能通过GetMessage或PeekMessage函数。
由于WM_QUIT
未与窗口相关联,并且将HWND
传递给GetMessage()
仅检索与该窗口关联的那些消息,后者将永远不会收到WM_QUIT
设计。
至于您希望将HWND
传递给GetMessage()
的时间,在应用程序的常规消息循环中,您不会。但是有时候你想在UI中的某些东西发生时抽取消息,并且只关注与特定窗口相关的消息。
答案 1 :(得分:4)
WM_QUIT
与线程相关,而不是单个窗口。请注意hwnd
缺少PostQuitMessage()
参数。它不可能是特定于窗口的,因为没有办法告诉它为哪个窗口生成消息。
WM_QUIT
实际上并不是真正的信息。当您调用PostQuitMessage()
时,在消息队列状态中设置了内部标志,表明已请求WM_QUIT
。这将在GetMessage()
或PeekMessage()
在将来的某个时间点自动生成(通常会立即生成,但如果队列中包含其他已发布的消息,则会先处理这些消息)。
这在Raymond Chen's blog上有更详细的解释,其中还包含以下引用:
作为另一种特殊行为,生成的WM_QUIT消息会绕过 传递给GetMessage和PeekMessage的消息过滤器 功能。如果内部&#34;退出消息等待&#34;然后设置标志 无论如何,一旦队列安静,你将收到WM_QUIT消息 您通过的过滤器。
这表明您的观察结果是错误的,即使提供了过滤器参数,上面示例#2中的GetMessage()
也应在调用PostQuitMessage()
后返回0。
一般情况下,只有在您有特定需要时才应使用邮件过滤器(例如,您特别想要检索发布到特定窗口的邮件)。在大多数情况下,这些参数都应设置为0,以便UI正常运行。