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

如何针对RawInput设备句柄使用DeviceIoControl控制键盘指示灯

解决RawInput句柄无法用于DeviceIoControl控制键盘指示灯的问题

你遇到的核心问题是:RawInput提供的设备句柄是RawInput子系统的内部句柄,并非设备的内核对象句柄,不能直接传给DeviceIoControl。必须通过设备的真实路径重新打开,才能获得可用于控制的有效句柄。

具体解决步骤

1. 存储设备路径并打开真实设备句柄

当收到WM_INPUT_DEVICE_CHANGE消息的GIDC_ARRIVAL事件时:

  • 通过GetRawInputDeviceInfo获取设备的完整路径(使用RIDI_DEVICENAME参数)
  • 用CreateFile打开该路径,获取具备读写权限的设备句柄,用于后续DeviceIoControl调用

2. 设备移除时清理句柄

收到GIDC_REMOVAL事件时,关闭之前打开的设备句柄,避免资源泄漏。

3. 控制指示灯时使用正确句柄

在WM_INPUT或其他控制逻辑中,使用CreateFile获取的真实句柄,而非RawInput的内部句柄。

修正后的关键代码部分

首先修改存储设备信息的结构体,新增真实设备句柄和路径字段:

typedef struct KeyboardDevice
{
    HANDLE rawInputHandle;  // RawInput内部句柄,用于匹配输入消息
    HANDLE deviceHandle;    // CreateFile打开的真实句柄,用于DeviceIoControl
    wchar_t devicePath[MAX_PATH]; // 设备路径,用于识别和打开
} KeyboardDevice;

KeyboardDevice keyboardDevices[32];
u32 deviceCount = 0;

修改WM_INPUT_DEVICE_CHANGE的处理逻辑:

case WM_INPUT_DEVICE_CHANGE:
{
    HANDLE rawHandle = (HANDLE)Message.lParam;
    RID_DEVICE_INFO deviceInfo;
    deviceInfo.cbSize = sizeof(RID_DEVICE_INFO);
    UINT infoSize = sizeof(RID_DEVICE_INFO);
    if (!GetRawInputDeviceInfo(rawHandle, RIDI_DEVICEINFO, &deviceInfo, &infoSize))
    {
        logWindowsError("获取设备信息失败");
        break;
    }

    if (deviceInfo.dwType != RIM_TYPEKEYBOARD)
        break;

    wchar_t devicePath[MAX_PATH];
    UINT pathSize = MAX_PATH;
    if (GetRawInputDeviceInfo(rawHandle, RIDI_DEVICENAME, devicePath, &pathSize) == (UINT)-1)
    {
        logWindowsError("获取设备路径失败");
        break;
    }

    switch(Message.wParam)
    {
        case GIDC_ARRIVAL:
        {
            // 打开设备,获取可用于DeviceIoControl的句柄
            HANDLE devHandle = CreateFile(devicePath, 
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                NULL,
                OPEN_EXISTING,
                0,
                NULL);

            if (devHandle != INVALID_HANDLE_VALUE)
            {
                keyboardDevices[deviceCount].rawInputHandle = rawHandle;
                keyboardDevices[deviceCount].deviceHandle = devHandle;
                wcscpy_s(keyboardDevices[deviceCount].devicePath, devicePath);
                deviceCount++;
                wprintf(L"设备已添加: %s\n", devicePath);
            }
            else
            {
                logWindowsError("打开设备失败");
            }
        } break;
        case GIDC_REMOVAL:
        {
            // 找到对应设备并关闭句柄
            for (u32 i = 0; i < deviceCount; i++)
            {
                if (keyboardDevices[i].rawInputHandle == rawHandle)
                {
                    CloseHandle(keyboardDevices[i].deviceHandle);
                    // 简单调整数组,移除设备
                    keyboardDevices[i] = keyboardDevices[deviceCount - 1];
                    deviceCount--;
                    wprintf(L"设备已移除: %s\n", keyboardDevices[i].devicePath);
                    break;
                }
            }
        } break;
    }
} break;

修改WM_INPUT中控制指示灯的逻辑:

case WM_INPUT:
{
    UINT dwSize = sizeof(RAWINPUT);
    static BYTE lpb[sizeof(RAWINPUT)];
    GetRawInputData((HRAWINPUT)Message.lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));
    RAWINPUT* raw = (RAWINPUT*)lpb;

    if(raw->header.dwType == RIM_TYPEKEYBOARD )
    {
        bool bIsUp = (raw->data.keyboard.Flags & RI_KEY_BREAK) != 0;
        u32 dwScanCode = raw->data.keyboard.MakeCode;

        // 仅处理CapsLock按下事件
        if( 0x3A == dwScanCode && !bIsUp)
        {
            HANDLE currentRawHandle = raw->header.hDevice;
            // 切换当前输入设备之外的其他键盘CapsLock指示灯
            for( u32 i = 0; i < deviceCount; ++i )
            {
                if( keyboardDevices[i].rawInputHandle != currentRawHandle)
                {
                    KEYBOARD_INDICATOR_PARAMETERS inputBuf = {0};
                    KEYBOARD_INDICATOR_PARAMETERS outputBuf = {0};
                    ULONG returnedLen = 0;
                    inputBuf.UnitId = 0;

                    // 查询当前指示灯状态
                    if (DeviceIoControl(keyboardDevices[i].deviceHandle, IOCTL_KEYBOARD_QUERY_INDICATORS,
                                        &inputBuf, sizeof(inputBuf),
                                        &outputBuf, sizeof(outputBuf),
                                        &returnedLen, NULL))
                    {
                        // 切换CapsLock状态
                        BOOL capsOn = (outputBuf.LedFlags & KEYBOARD_CAPS_LOCK_ON) != 0;
                        inputBuf.LedFlags = capsOn ? (outputBuf.LedFlags & ~KEYBOARD_CAPS_LOCK_ON) : (outputBuf.LedFlags | KEYBOARD_CAPS_LOCK_ON);

                        // 设置新的指示灯状态
                        DeviceIoControl(keyboardDevices[i].deviceHandle, IOCTL_KEYBOARD_SET_INDICATORS,
                                        &inputBuf, sizeof(inputBuf),
                                        NULL, 0, &returnedLen, NULL);
                    }
                    else
                    {
                        logWindowsError("查询/设置指示灯失败");
                    }
                }
            }
        }
    }
    else
    {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    break;
}

额外注意事项

  • CreateFile打开设备时,必须指定GENERIC_READ | GENERIC_WRITE权限和FILE_SHARE_READ | FILE_SHARE_WRITE共享模式,否则会打开失败。
  • 设备路径是Unicode字符串,需使用宽字符相关函数处理(如wchar_t、wcscpy_s)。
  • 程序退出时要遍历所有设备,关闭打开的deviceHandle,避免资源泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:49:50