非活动窗口能否接收消息?子线程窗口接收WM_INPUT的问题求解
问题背景
我在主线程外的独立子线程中创建了默认窗口与消息窗口,目标是在子线程中接收HID设备的WM_INPUT消息——因为主线程的消息循环处理输入的速率远达不到需求。但目前只有当主线程的主窗口处于非活动状态时,子线程中的这两个窗口才能正常接收消息。
编辑补充(附代码)
此前未附上代码是因为均为样板代码,鉴于诸多评论要求,现将代码附上以供参考:
//FIRST: CLASS REGISTRATION: WNDCLASSEXW wcex; wchar_t local_name[] = L"ClassGenericName"; wcsncat(local_name, std::to_wstring(GetCurrentThreadId()).c_str(), std::to_wstring(GetCurrentThreadId()).size()); //You can ignore naming process as it was made to make more than one window for more than one thread, just for testing purposes. 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_WINAPITEST)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINAPITEST); wcex.lpszClassName = local_name; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); RegisterClassExW(&wcex); hWnd = CreateWindowEx(0, wcex.lpszClassName, L"no title", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0); ShowWindow(hWnd, 3);//Testing, I switch it on and off. UpdateWindow(hWnd); //SECOND: DEVICE REGISTRATION: RAWINPUTDEVICE RID; RID.usUsagePage = 0x01; RID.usUsage = 0x05; RID.dwFlags = 0; RID.hwndTarget = 0; RegisterRawInputDevices(&RID, 1, sizeof(RID)); //THIRD: THREAD FUNCTION MESSAGE LOOP. HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINAPITEST)); MSG msg; while (GetMessageW(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } //FOURTH: WINDOW PROCEDURE MESSAGE PROCESSING. //I don't think I need to add this as it is boilerplate and tested to work all the time. If wanted I'd add it tho. //FIVE: Thanks for helpful comments. My bad for not adding code ahead of time.
可行解决方案
1. 明确指定RawInput的目标窗口
当前代码里注册RawInput设备时RID.hwndTarget = 0,这会让系统把WM_INPUT消息发送到当前活动窗口——也就是主线程主窗口激活时,消息全发给它,子线程窗口根本收不到。修改方式如下:
// 修改DEVICE REGISTRATION部分 RAWINPUTDEVICE RID; RID.usUsagePage = 0x01; RID.usUsage = 0x05; RID.dwFlags = RIDEV_INPUTSINK; // 必须加这个标志,允许非活动窗口接收输入 RID.hwndTarget = hWnd; // 直接指定子线程创建的窗口句柄 RegisterRawInputDevices(&RID, 1, sizeof(RID));
核心是RIDEV_INPUTSINK标志,它能让目标窗口即使不在前台,也能强制接收RawInput消息;同时明确指定目标窗口为子线程的窗口,不让系统自动选活动窗口。
2. 简化子线程的消息循环
子线程的窗口不需要处理快捷键,完全可以去掉TranslateAccelerator的调用,避免不必要的消息拦截,简化后的消息循环:
// 修改THREAD FUNCTION MESSAGE LOOP部分 MSG msg; while (GetMessageW(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
同时要确保子线程的消息循环持续运行,不能提前退出,否则窗口会失效。
3. 优化消息窗口的创建(可选)
既然用的是HWND_MESSAGE类型的消息窗口,根本不需要显示,把ShowWindow(hWnd, 3)改成ShowWindow(hWnd, SW_HIDE)更合理,避免弹出不必要的窗口:
hWnd = CreateWindowEx(0, wcex.lpszClassName, L"no title", 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0); ShowWindow(hWnd, SW_HIDE); // 隐藏消息窗口 UpdateWindow(hWnd);
4. 避免主线程抢占消息
如果主线程的消息循环里有阻塞操作(比如长时间计算、未响应的窗口过程),会干扰系统的消息分发。要么保证主线程消息循环随时响应,要么在主线程的窗口过程里直接忽略WM_INPUT消息,不让它抢子线程的消息。
5. 动态设备管理的进阶方案(可选)
如果需要处理设备的连接/断开,可以在注册RawInput时加上RIDEV_DEVNOTIFY标志,配合WM_INPUT_DEVICE_CHANGE消息来管理设备。不过这种方式依然需要窗口接收消息,本质和方案1一致,只是多了设备状态的监听能力。
内容的提问来源于stack exchange,提问作者Physician

