首页 > 解决方案 > 通过 GetRawInputBuffer 读取 RAWINPUT 时,WM_INPUT_DEVICE_CHANGE 消息会丢失

问题描述

我试图编写与GetRawInputBuffer API 一起正常工作的程序,并在单独的线程上消耗所有输入而几乎没有开销。

但是我发现通过GetRawInputBuffer而不是通常的WM_INPUT方法读取RAWINPUT时, WM_INPUT_DEVICE_CHANGE消息会丢失。

我的代码(我删除了一些错误检查):

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, 0, 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), nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, wc.hInstance, 0);

    ::SetWindowLongPtrW(hWnd, 0, 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, 0, 0, WM_INPUT - 1, PM_REMOVE) ||
                    ::PeekMessageW(&msg, 0, WM_INPUT + 1, 0, PM_REMOVE);
            }
            else
            {
                // retrieve any message
                haveMessage = ::PeekMessageW(&msg, 0, 0, 0, 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,
            0,
            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 中添加的,并且已经证明它在其实现中存在一些错误

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

标签: winapihidraw-input

解决方案


推荐阅读