在多线程中,GetMessage()必须和创建窗口在同一个线程。否则:
1、GetMessage的调用会一直堵塞。消息处理函数不会被调用。
2、SendMessage也无法发消息,发了对方也接收不到不能及时处理。
下面梳理多线程、创建窗口、消息循环的方式和结果:
一、以往大多在WIN32界面程序创建窗口,即:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 创建窗口界面。
}
二、下面介绍在console程序创建窗口,应用里。并介绍多线程消息。即:
// 不一定是:int main(int argc, char* argv[])。 意不意外~
int main() {
// 创建窗口界面。
}
下面给一个完整的例子,解释多线程对windows窗口消息循环的影响。
多线程采用C++标准库的<thread>,更多说明见注释:
#include <windows.h> //包含 Windows 相关的 API 函数
#include <thread> // 多线程
// 消息处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 注册窗口
bool RegisterWindow();
// 窗口创建
bool Create();
// 消息循环
void MsgLoop();
// 测试在一个线程的场景。
void Test();
// 测试在多线程场景。
void TestWithThread();
void TestAllInThread();
/*************************
* 入口函数,测试窗口消息
***************************/
int main()
{
// 可以分别注释对应的接口,验证
// 结果:正常。测试不启用线程的场景。
Test();
// 结果:异常。测试窗口注册、创建和消息循环不在同一个线程里。
//TestWithThread();
// 结果:正常。测试窗口注册、创建、消息循环都在同一个线程里。
//TestAllInThread();
return 0;
}
void TestNoThread() {
RegisterWindow(); // 窗口注册
Create(); // 窗口创建
// 消息循环,不启用线程。
//这里调用GetMessage会等待其它线程、进程发到当前窗口的消息。
MsgLoop();
}
void TestWithThread() {
RegisterWindow(); // 窗口注册
Create(); // 窗口创建
// 消息循环, 放到不同窗口注册和创建的另一个线程里。
// 线程里面调用GetMessage会一直堵塞。
// 导致不能正常收到其它线程、进程发到当前窗口的消息。
std::thread t(MsgLoop);
t.join(); // 等待消息线程结束,避免程序直接退出。
}
void WindThread() {
RegisterWindow(); // 窗口注册
Create(); // 窗口创建
// 消息循环, 和窗口注册和创建同一个线程里。
//这里调用GetMessage会等待其它线程、进程发到当前窗口的消息。
MsgLoop();
}
void TestAllInThread() {
std::thread t(WindThread);
t.join();
}
// 消息处理函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
bool handle = true;
switch (uMsg) //消息选择
{
case WM_LBUTTONDOWN:
break;
default:
handle = false;
break;
}
if (handle) {
return S_OK;
}
return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}
// 注册窗口
bool RegisterWindow() {
HINSTANCE hInstance = GetModuleHandleA(NULL);
//当然, 如果不创建窗口, 不使用窗口 也可以使用 main.
WNDCLASSEXA wc = { 0 }; //窗口类结构, 为注册窗口类作准备
wc.cbClsExtra = 0; //附加的类信息, 没有, 设为0
wc.cbSize = sizeof(wc); //WNDCLASSEX结构的大小
wc.cbWndExtra = 0; //窗口额外内存, 没有, 设为0
wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); //窗口背景, 这里使用灰色背景
wc.hCursor = LoadCursorA(NULL, MAKEINTRESOURCEA(IDC_ARROW)); //应用程序使用的鼠标类型
wc.hIcon = LoadIconA(NULL, MAKEINTRESOURCEA(IDI_APPLICATION)); //光标类型
wc.hIconSm = NULL; //应用程序程序的小光标, 不管, 设为 NULL
wc.hInstance = hInstance; //应用程序程序实例句柄, 由 WinMain 函数传递过来
//当前窗口的消息处理函数, 传递 WndProc 的地址
wc.lpfnWndProc = WndProc;
wc.lpszClassName = "vbgk_class"; //创建类时使用的类名, 可以自定义
wc.lpszMenuName = NULL; //菜单, 没有, 就使用 NULL
wc.style = CS_HREDRAW | CS_VREDRAW; //类的风格, 垂直重绘, 水平重绘
if (!RegisterClassExA(&wc)) { //注册窗口类
MessageBoxA(NULL, "Register Class Failed!", NULL, MB_OK);
return false;
}
return true;
}
// 窗口创建
bool Create() {
HINSTANCE hInstance = GetModuleHandleA(NULL);
//创建窗口咯, 介绍一下 CreateWindowEx 函数的参数
HWND hWnd = CreateWindowExA(
0, //扩展窗口风格
"vbgk_class", //这里就是我们刚才创建的窗口类名
"test title",
WS_OVERLAPPEDWINDOW,//窗口风格
CW_USEDEFAULT, //初始化时的 X 坐标
CW_USEDEFAULT, //Y坐标
320, //窗口宽度, 我们这里设为 320
240, //窗口高度
NULL, //父窗口句柄, 没有
NULL, //菜单, 没有
hInstance, //实例句柄, 来自WinMain
NULL //发送 WM_CREATE 消息时的附加参数, 一般为零
);
if (!hWnd || INVALID_HANDLE_VALUE == hWnd) {
return false;
}
UpdateWindow(hWnd); //更新窗口
ShowWindow(hWnd, SW_HIDE); //隐藏窗口
return true;
}
// 消息循环
void MsgLoop() {
MSG msg;
BOOL bRet = TRUE;
//进入消息循环,
while ((bRet = GetMessageA(&msg, NULL, 0, 0)) != -1) {
if (bRet == 0) {
break;
}
TranslateMessage(&msg); //翻译消息
//分发消息- 即会调用WndProc();
DispatchMessageA(&msg);
}
}