Nic*_*ler 8 c++ windows winapi message-queue
在Windows API中,我正在研究GetMessage函数的实际工作方式.我已经看到了3个Windows消息循环的实现,并希望探索它们.
截至撰写本文时,这篇MSDN文章描述了我认为实现消息循环的正确方法.
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);
}
}
Run Code Online (Sandbox Code Playgroud)
在GetMessage功能页面上,我看到了这个实现:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
最后,Visual Studio文档将此实现作为其Win32应用程序演示的一部分.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Run Code Online (Sandbox Code Playgroud)
简而言之,实现#3忽略了返回的错误GetMessage,但在其他方面与第一个实现相同.也就是说,它们都处理当前线程的所有消息.当GetMessage函数返回时0,循环终止.
由于我在#1之前发现了#2,我认为它已经完成了.但是,我注意到通过发布消息时GetMessage不会返回0WM_QUITPostQuitMessage
这导致了一些混乱,直到我发现实现#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,所以消息循环应该以另一种方式终止吗?
#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;
}
Run Code Online (Sandbox Code Playgroud)
WM_QUIT与线程相关,而不是单个窗口。请注意缺少 的hwnd参数PostQuitMessage()。它不可能是特定于窗口的,因为无法告诉它为哪个窗口生成消息。
WM_QUIT实际上并不是真正的消息。当您调用时,PostQuitMessage()内部标志会在WM_QUIT已请求的消息队列状态中设置。这将由GetMessage()或PeekMessage()在将来的某个时刻自动生成(通常是立即生成,但如果队列包含其他已发布的消息,这些消息将首先被处理)。
Raymond Chen 的博客对此进行了更详细的解释,其中还包含以下引用:
作为另一个特殊行为,生成的 WM_QUIT 消息绕过传递给 GetMessage 和 PeekMessage 函数的消息过滤器。如果设置了内部“退出消息挂起”标志,那么一旦队列安静,无论您通过什么过滤器,您都会收到 WM_QUIT 消息。
这表明您的观察结果是错误的,并且GetMessage()在上面的示例#2 中,PostQuitMessage()即使已提供了过滤器参数,调用后也应返回 0。
一般来说,只有在您有特定需要时才应该使用消息过滤器(例如,您特别想要检索发布到特定窗口的消息)。在大多数情况下,这些参数应全部设置为 0,以便 UI 正常运行。