You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

程序后台运行时如何接收WM_INPUT消息获取鼠标原始输入数据

解决方法

你现有代码存在三个核心问题,修改后即可实现后台全局接收鼠标原始输入:

  1. 没有创建属于自己程序的消息窗口,WM_INPUT消息没有投递目标
  2. 注册RAWINPUT设备时未开启后台接收标志,也未绑定目标窗口
  3. 主线程死循环打印前台窗口,没有运行Windows消息队列,无法接收分发消息

修改后的完整实现代码

#include <windows.h>
#include <iostream>
#include <memory>

// 窗口过程声明
LRESULT CALLBACK targetWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int main()
{
    // 1. 注册窗口类,创建隐藏消息窗口
    const wchar_t CLASS_NAME[] = L"RawInputHiddenWindowClass";
    WNDCLASS wc = {0};
    wc.lpfnWndProc = targetWindowProc;
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = CLASS_NAME;
    RegisterClass(&wc);

    // 创建隐藏窗口,不需要显示出来
    HWND hWnd = CreateWindowEx(
        WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE, // 扩展样式:不在任务栏显示、不激活
        CLASS_NAME,
        L"RawInputMonitor",
        WS_POPUP, // 无标题栏样式
        0, 0, 0, 0, // 大小位置都设为0
        NULL, NULL, GetModuleHandle(NULL), NULL
    );
    if (hWnd == NULL) {
        exit(-1);
    }
    ShowWindow(hWnd, SW_HIDE); // 隐藏窗口

    // 2. 注册Raw Input设备,开启后台接收
    RAWINPUTDEVICE rid;
    rid.usUsagePage = 0x01; // HID通用桌面页
    rid.usUsage = 0x02;     // 鼠标设备
    // RIDEV_INPUTSINK标志:即使窗口在后台也能收到输入
    rid.dwFlags = RIDEV_INPUTSINK;
    rid.hwndTarget = hWnd; // 绑定到我们创建的隐藏窗口

    if (RegisterRawInputDevices(&rid, 1, sizeof(rid)) == FALSE)
    {
        exit(-1);
    }

    // 3. 替换原有死循环为Windows消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        // 如果你还需要打印前台窗口,可以加在这里
        HWND foregroundWnd = GetForegroundWindow();
        std::cout << "当前前台窗口句柄:" << foregroundWnd << '\n';
    }

    return 0;
}

LRESULT CALLBACK targetWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INPUT:
    {
        UINT dataSize;
        GetRawInputData(reinterpret_cast<HRAWINPUT>(lParam), RID_INPUT, NULL, &dataSize, sizeof(RAWINPUTHEADER));
        // 可以通过这个判断是前台还是后台收到的输入
        int inputCode = GET_RAWINPUT_CODE_WPARAM(wParam);
        if (inputCode == RIM_INPUTSINK) {
            std::cout << "[后台收到输入] ";
        } else if (inputCode == RIM_INPUT) {
            std::cout << "[前台收到输入] ";
        }

        if (dataSize > 0)
        {
            std::unique_ptr<BYTE[]> rawdata = std::make_unique<BYTE[]>(dataSize);
            if (GetRawInputData(reinterpret_cast<HRAWINPUT>(lParam), RID_INPUT, rawdata.get(), &dataSize, sizeof(RAWINPUTHEADER)) == dataSize)
            {
                RAWINPUT* raw = reinterpret_cast<RAWINPUT*>(rawdata.get());
                if (raw->header.dwType == RIM_TYPEMOUSE)
                {
                    std::cout << "X偏移:" << raw->data.mouse.lLastX << " Y偏移:" << raw->data.mouse.lLastY << std::endl;
                }
            }
        }
        return 0;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
}

关键修改说明

  • RIDEV_INPUTSINK是实现后台接收的核心参数,只要注册设备时加上这个标志并绑定目标窗口,不管窗口是否处于前台,都能收到所有全局的鼠标原始输入消息
  • 必须运行标准Windows消息循环才能收到WM_INPUT消息,原代码的死循环会阻塞消息队列,导致窗口过程永远无法被调用
  • 隐藏窗口只用来接收消息,不会显示到桌面或任务栏,完全不影响前台操作

内容的提问来源于stack exchange,提问作者Lenny

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.10 15:24:02