通过 GetRawInputBuffer 读取 RAWINPUT 时,WM_INPUT_DEVICE_CHANGE 消息丢失

问题描述

我尝试编写与 GetRawInputBuffer API 一起正常工作的程序,并在单独的线程上以很少的开销消耗所有输入。

但我发现通过 WM_INPUT_DEVICE_CHANGE 而不是通常的 RAWINPUT 方法阅读 GetRawInputBufferWM_INPUT 消息会丢失。

My code(我删除了一些错误检查):

void RawInputDeviceManager::RawInputManagerImpl::ThreadRun()
{
    // if I set it to true then WM_INPUT_DEVICE_CHANGE does not come
    constexpr bool buffered = false;

    // prepare buffer for up to 32 raw input messages
    m_InputDataBuffer.resize(std::max({ sizeof(RAWKEYBOARD),sizeof(RAWMOUSE),sizeof(RAWHID) }) * 32);

    m_WakeUpEvent = ::CreateEventExW(nullptr,nullptr,EVENT_ALL_ACCESS);

    WNDCLASSEXW wc{};
    wc.cbSize = sizeof(wc);
    wc.lpszClassName = L"Message";
    wc.lpfnWndProc = [](HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) -> LRESULT
    {
        RawInputManagerImpl* manager = reinterpret_cast<RawInputManagerImpl*>(::GetWindowLongPtrW(hWnd,0));

        if (manager)
        {
            switch (message)
            {
            case WM_INPUT_DEVICE_CHANGE:  // <== get lost after ::GetRawInputBuffer(..) call
            {
                manager->OnInputDeviceChange();
                return 0;
            }
            case WM_INPUT:
            {
                manager->OnInput(reinterpret_cast<RAWINPUT*>(lParam));
                return 0;
            }
            }
        }

        return ::DefWindowProcW(hWnd,message,wParam,lParam);
    };
    wc.cbWndExtra = sizeof(RawInputManagerImpl*); // add some space for this pointer
    wc.hInstance = ::GetModuleHandleW(nullptr);

    ATOM classAtom = ::RegisterClassExW(&wc);

    HWND hWnd = ::CreateWindowExW(0,reinterpret_cast<LPCWSTR>(classAtom),HWND_MESSAGE,wc.hInstance,0);

    ::SetWindowLongPtrW(hWnd,reinterpret_cast<LONG_PTR>(this));

    Register(hWnd);

    // enumerate devices before start
    OnInputDeviceChange();

    // main message loop
    while (m_Running)
    {
        MSG msg;

        if (buffered)
            OnInputBuffered();

        while (true)
        {
            bool haveMessage = false;

            if (buffered)
            {
                // retrieve any message but WM_INPUT
                haveMessage = ::PeekMessageW(&msg,WM_INPUT - 1,PM_REMOVE) ||
                    ::PeekMessageW(&msg,WM_INPUT + 1,PM_REMOVE);
            }
            else
            {
                // retrieve any message
                haveMessage = ::PeekMessageW(&msg,PM_REMOVE);
            }

            if (!haveMessage)
                break;

            // not needed since we are not interested in WM_CHAR or WM_DEADCHAR
            //::TranslateMessage(&msg);

            // dispatch message to WndProc
            ::DispatchMessageW(&msg);
        }

        // wait for new messages
        ::MsgWaitForMultipleObjectsEx(1,&m_WakeUpEvent,INFINITE,QS_ALLEVENTS,MWMO_INPUTAVAILABLE);
    }

    Unregister();
    ::DestroyWindow(hWnd);
    ::UnregisterClassW(reinterpret_cast<LPCWSTR>(classAtom),wc.hInstance);
}

bool RawInputDeviceManager::RawInputManagerImpl::Register(HWND hWnd)
{
    RAWINPUTDEVICE rid[] =
    {
        // register for all HID device generic types (keyboard/mouse/joystick etc)
        {
            HID_USAGE_PAGE_GENERIC,RIDEV_DEVNOTIFY | RIDEV_INPUTSINK | RIDEV_PAGEONLY,hWnd
        }
    };

    return ::RegisterRawInputDevices(rid,ARRAYSIZE(rid),sizeof(RAWINPUTDEVICE));
}

void RawInputDeviceManager::RawInputManagerImpl::OnInputBuffered()
{
    // Processing all pending WM_INPUT messages in message queue
    while (true)
    {
        UINT size = static_cast<UINT>(m_InputDataBuffer.size());
        RAWINPUT* input = reinterpret_cast<RAWINPUT*>(m_InputDataBuffer.data());

        UINT result = ::GetRawInputBuffer(input,&size,sizeof(RAWINPUTHEADER));

        if (result == 0 || result == static_cast<UINT>(-1))
            return;

        // hack for a undefined QWORD used in NEXTRAWINPUTBLOCK macro
        using QWORD = __int64;

        for (; result; result--,input = NEXTRAWINPUTBLOCK(input))
        {
            OnInput(input);
        }
    }
}

这是 Windows 中的错误吗?

PS:此 WM_INPUT_DEVICE_CHANGE 事件已添加到 Windows Vista 中,并已证明它has some bugs in its implementation

PPS:作为一种解决方法,我可以通过 WM_DEVICECHANGE 订阅 RegisterDeviceNotification 消息,但我不确定在这种情况下我是否做错了什么。

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)