c++ - 由于 HID 句柄无效(Windows HID C++ API),HidD_* 函数全部失败

标签 c++ c windows winapi hid

我在从 C++ 桌面应用程序中的 Windows Precision Touchpad 获取使用数据时遇到问题。我已经完成了以下工作:

  • 注册原始输入
  • 我正在接收包含原始数据的 WM_INPUT 消息
  • 我可以调用GetRawInputData()返回 RAWINPUT结构

  • 我的下一步是尝试使用 Hidpi.h实用程序来确定我的触摸板支持哪些用途(HidP_GetCaps(), HidP_GetValueCaps(), etc...),然后获取它们的值。为了让事情顺利进行,我只是尝试打印 X、Y、提示开关、联系人 ID 和最大联系人计数)。
    为了使用上述实用程序,我从文档中注意到我通常需要两件事:预解析数据 (PHIDP_PREPARSED_DATA) 和实际的输入或特征报告。
    这就是事情开始出错的地方。我读过的所有内容都说我应该能够使用HidD_GetPreparsedData()得到第一个项目。我传递的 HID 设备句柄是 raw_data_buffer->header.hDevice ,其中 raw_data_buffer 是 PRAWINPUT类型 I 填充自 GetRawInputData() .不幸的是,这失败了。调用GetLastError()返回 ERROR_INVALID_HANDLE .我找不到任何解释...句柄不是 NULL 并且与我试图从中读取的 HID 设备匹配。
    我找到了一种不同的方法来获取预解析的数据:
    ...
    preparsed_data_buffer = (PHIDP_PREPARSED_DATA)malloc( pp_data_buf_sz );
    GetRawInputDeviceInfo( raw_data_buffer->header.hDevice, RIDI_PREPARSEDDATA, preparsed_data_buffer, &pp_data_buf_sz );
    ...
    
    据我所知,数据是正确的。然后我就可以使用 *_GetCaps()_*GetValueCaps()查看支持的用法。
    11 Input Button Capabilites Found
    Index:   0, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
    Index:   1, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
    Index:   2, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
    Index:   3, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
    Index:   4, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
    Index:   5, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
    Index:   6, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
    Index:   7, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
    Index:   8, IsRange: 0, UsagePage: 0X000D, Usage: 0X0042
    Index:   9, IsRange: 0, UsagePage: 0X000D, Usage: 0X0047
    Index:  10, IsRange: 0, UsagePage: 0X0009, Usage: 0X0001
    
    17 Input Value Capabilites Found
    Index:   0, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
    Index:   1, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
    Index:   2, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
    Index:   3, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
    Index:   4, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
    Index:   5, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
    Index:   6, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
    Index:   7, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
    Index:   8, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
    Index:   9, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
    Index:  10, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
    Index:  11, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
    Index:  12, IsRange: 0, UsagePage: 0X000D, Usage: 0X0051
    Index:  13, IsRange: 0, UsagePage: 0X0001, Usage: 0X0030
    Index:  14, IsRange: 0, UsagePage: 0X0001, Usage: 0X0031
    Index:  15, IsRange: 0, UsagePage: 0X000D, Usage: 0X0056
    Index:  16, IsRange: 0, UsagePage: 0X000D, Usage: 0X0054
    
    3 Feature Value Capabilites Found
    Index:   0, IsRange: 0, UsagePage: 0X000D, Usage: 0X0059
    Index:   1, IsRange: 0, UsagePage: 0X000D, Usage: 0X0055
    Index:   2, IsRange: 0, UsagePage: 0XFF00, Usage: 0X00C5
    
    我找到了我所期望的一切。 X (x1/x30)、Y (x1/x31) 和 Contact ID (xD/x51) 应该在我的输入报告中,Tip Switch 是一个按钮,Max Contact Count (xD/x55) 应该在我的输入报告中特征值报告。
    接下来关注输入/功能报告,我遇到了我原来的问题。第 7 个参数为 HidP_GetUsageValue()PCHAR输入或功能报告,但我不知道如何获取它们。 HidD_GetInputReport()HidD_GetFeature()两者都失败,出现相同的无效句柄错误,我试图通过该 API 获取预解析的数据。
    在经历了大概 10 个线程或示例之后,我注意到有人使用他们的原始(未解析)数据来获取输入值,如下所示:
    HidP_GetUsageValue( HidP_Input, usages[ usage_idx ].page, 0, usages[ usage_idx ].usage, &val,
                        preparsed_data, (PCHAR)raw_data_buffer->data.hid.bRawData, hid_capabilities.InputReportByteLength );
    
    这行得通。我可以从输入报告中获取 x、y 和联系人 ID……我猜是因为原始输入报告必须是原始数据的第一 block 。但是,我仍然无法使用 HidP_GetUsageValue( HidP_Feature... 获得功能报告因为我不知道该报告在原始输入中的位置。
    我花了 10 多个小时研究可能的原因和解决方法,但一无所获。出于我的应用程序的目的,我可能只使用输入报告值就可以逃脱,但我必须缺少一些东西。
    6/14 更新:
    @RbMm @psmears 感谢您的评论。我没有意识到 GetRawInputDeviceInfo() 的返回, 使用 RIDI_DEVICENAME ,是为我的设备创建文件所需的符号路径。我不需要使用 SetupApi 或 cfgmgr32 来检查所有设备接口(interface)。不过,我仍然无法为我的设备创建文件。由于在枚举所有设备接口(interface)时可以看到我正在寻找的路径,因此我决定通过为每个设备接口(interface)创建一个新文件来进行测试,以排除路径格式/编码问题。这是我的输出:
    CM_Get_Device_Interface_List() found device #1.
    Path: \\?\HID#VID_1FD2&PID_6103&MI_00&Col02#9&885ad2f&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #2.
    Path: \\?\HID#VID_046D&PID_C077#6&b39df8e&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CreateFile() failed with error: 0x5
    
    CM_Get_Device_Interface_List() found device #3.
    Path: \\?\HID#VID_B404&PID_0101&MI_01&Col01#7&22822429&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CreateFile() failed with error: 0x20
    
    CM_Get_Device_Interface_List() found device #4.
    Path: \\?\HID#VID_B404&PID_0101&MI_01&Col02#7&22822429&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CreateFile() failed with error: 0x20
    
    CM_Get_Device_Interface_List() found device #5.
    Path: \\?\HID#VID_1FD2&PID_6103&MI_01#9&20493974&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #6.
    Path: \\?\HID#VID_0424&PID_274C&MI_01#8&2d3dcce0&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #7.
    Path: \\?\HID#VID_B404&PID_0101&MI_01&Col03#7&22822429&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}\KBD
    CreateFile() failed with error: 0x5
    
    CM_Get_Device_Interface_List() found device #8.
    Path: \\?\HID#VID_B404&PID_0101&MI_00#7&3a45b06e&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\KBD
    CreateFile() failed with error: 0x5
    
    CM_Get_Device_Interface_List() found device #9.
    Path: \\?\HID#VID_1FD2&PID_6103&MI_00&Col01#9&885ad2f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CreateFile() failed with error: 0x20
    
    CM_Get_Device_Interface_List() found device #10.
    Path: \\?\HID#VID_05AC&PID_0265&MI_00&Col01#8&7741f1a&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #11.
    Path: \\?\HID#VID_05AC&PID_0265&MI_00&Col02#8&7741f1a&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #12.
    Path: \\?\HID#VID_05AC&PID_0265&MI_01&Col02#8&1f37ab5f&0&0001#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #13.
    Path: \\?\HID#VID_05AC&PID_0265&MI_01&Col03#8&1f37ab5f&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #14.
    Path: \\?\HID#VID_05AC&PID_0265&MI_02#8&36fb37a4&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #15.
    Path: \\?\HID#VID_05AC&PID_0265&MI_03#8&1323f9e2&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    CrateFile() worked.
    
    CM_Get_Device_Interface_List() found device #16.
    Path: \\?\HID#VID_05AC&PID_0265&MI_01&Col01#8&1f37ab5f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}
    THIS is the device we want a file handle for. Path matches what we expect.
    CreateFile() failed with error: 0x20
    
    我要连接的设备(此处为#16)是触摸板。最后几个接口(interface)与同一个 Apple Trackpad 设备相关联,为它们创建一个文件是可行的。错误“ERROR_FILE_NOT_FOUND (0x2)”对我来说没有任何意义。
    这是我创建文件的方式:
    file_handle = CreateFile( comp_hid_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
    
    根据 OPEN_ALWAYS 的 MS 文档:“如果指定的文件不存在并且是可写位置的有效路径,则该函数将创建一个文件并将最后一个错误代码设置为零。”这意味着这不是一个有效的、可写的位置,但它肯定会在枚举我的接口(interface)时出现。
    我是否无法向设备打开文件,因为它只是使用 WM_INPUT 消息发送了原始输入?

    最佳答案

    “句柄”是一个非常通用的东西。我们可以将任何不透明的指针大小的值称为“句柄”。
    HidD_GetPreparsedData 需要 文件句柄 .此句柄必须是 已打开 CreateFileWNtOpenFile , NtCreateFile .并且这个句柄必须是 关闭CloseHandleNtClose一旦 handle 不再使用。
    RAWINPUTDEVICELIST 中的 hDevice不是文件句柄。这是一些不透明的值,不需要关闭。 GetRawInputDeviceList 返回 RAWINPUTDEVICELIST 的数组并没有说你需要关闭hDevice。
    您可以通过调用 CM_Get_Device_Interface_ListW 列出系统中所有隐藏的接口(interface)与 GUID_DEVINTERFACE_HID .然后将返回的字符串传递给 CreateFileW这样你就得到了一个有效的文件句柄,可以在调用HidD_GetPreparsedData时使用.
    例如:

    Enum(&GUID_DEVINTERFACE_HID);
    
    // must be /RTCs-
    
    volatile const UCHAR guz = 0;
    
    void Enum(const GUID* InterfaceClassGuid)
    {
        CONFIGRET cr;
        ULONG cb = 0, rcb;
        union {
            PVOID buf;
            PZZWSTR Buffer;
        };
    
        PVOID stack = alloca(guz);
        do 
        {
            cr = CM_Get_Device_Interface_List_SizeW(&rcb, const_cast<GUID*>(InterfaceClassGuid), 0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
    
            if (cr != CR_SUCCESS)
            {
                break;
            }
    
            if (cb < (rcb *= sizeof(WCHAR)))
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }
    
            cr = CM_Get_Device_Interface_ListW(const_cast<GUID*>(InterfaceClassGuid), 
                0, Buffer, cb/sizeof(WCHAR), CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
    
        } while (cr == CR_BUFFER_SMALL);
    
        if (cr == CR_SUCCESS)
        {
            while (*Buffer)
            {
                DumpInterface(Buffer);
                Buffer += wcslen(Buffer) + 1;
            }
        }
    }
    
    我的 DumpInterface 的实现:
    void GetPropertyByDeviceID(PCSTR prefix, DEVINST dnDevInst)
    {
    
        HANDLE hFile;
        IO_STATUS_BLOCK iosb;
        UNICODE_STRING ObjectName;
        OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName, OBJ_CASE_INSENSITIVE };
    
    
        CONFIGRET status;
    
        ULONG cb = 0, rcb = 0x80;
    
        PVOID stack = alloca(guz);
    
        DEVPROPTYPE PropertyType;
    
        union {
            PVOID pv;
            PWSTR sz;
            PBYTE pb;
        };
    
        static struct  
        {
            CONST DEVPROPKEY *PropertyKey;
            PCWSTR PropertyName;
        } PropertyKeys[] = {
            { &DEVPKEY_Device_PDOName, L"PDOName"},
            { &DEVPKEY_Device_Parent, L"Parent"},
            { &DEVPKEY_Device_DriverVersion, L"DriverVersion"},
            { &DEVPKEY_Device_LocationInfo, L"LocationInfo"},
            { &DEVPKEY_Device_FirmwareVersion, L"FirmwareVersion"},
            { &DEVPKEY_Device_Model, L"Model"},
            { &DEVPKEY_NAME, L"NAME"},
            { &DEVPKEY_Device_InstanceId, L"DeviceID"}
        };
    
        do 
        {
            int n = RTL_NUMBER_OF(PropertyKeys);
    
            do 
            {
                CONST DEVPROPKEY *PropertyKey = PropertyKeys[--n].PropertyKey;
    
                do 
                {
                    if (cb < rcb)
                    {
                        rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
                    }
    
                    status = CM_Get_DevNode_PropertyW(dnDevInst, PropertyKey, &PropertyType, pb, &rcb, 0);
    
                    if (status == CR_SUCCESS)
                    {
                        if (PropertyType == DEVPROP_TYPE_STRING)
                        {
                            DbgPrint("%s%S=[%S]\n", prefix, PropertyKeys[n].PropertyName, sz);
    
                            if (!n)
                            {
                                // DEVPKEY_Device_PDOName can use in NtOpenFile
    
                                RtlInitUnicodeString(&ObjectName, sz);
    
                                if (0 <= NtOpenFile(&hFile, FILE_READ_ATTRIBUTES|SYNCHRONIZE, &oa,
                                    &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT))
                                {
                                    NtClose(hFile);
                                }
                            }
    
                        }
                    }
    
                } while (status == CR_BUFFER_SMALL);
    
            } while (n);
    
            if (!*--prefix) break;
    
        } while (CM_Get_Parent(&dnDevInst, dnDevInst, 0) == CR_SUCCESS);
    }
    
    void DumpInterface(PCWSTR InterfaceLink)
    {
        DbgPrint("\r\n--------\r\n%S\r\n\r\n", InterfaceLink);
    
        DEVPROPTYPE PropertyType;
    
        union {
            PBYTE PropertyBuffer;
            PVOID buf;
            DEVINSTID_W pDeviceID;
            PWSTR PDOName;
        };
    
        ULONG cb = 0, rcb = 0x80;
        CONFIGRET cr;
        PVOID stack = alloca(guz);
    
        do 
        {
            if (cb < rcb)
            {
                cb = RtlPointerToOffset(buf = alloca(rcb - cb), stack);
            }
    
            cr = CM_Get_Device_Interface_PropertyW(InterfaceLink, &DEVPKEY_Device_InstanceId, 
                &PropertyType, PropertyBuffer, &(rcb = cb), 0);
    
        } while (cr == CR_BUFFER_SMALL);
    
        DEVINST dnDevInst;
    
        if (cr == CR_SUCCESS && 
            PropertyType == DEVPROP_TYPE_STRING && 
            CR_SUCCESS == CM_Locate_DevNodeW(&dnDevInst, pDeviceID, CM_LOCATE_DEVNODE_NORMAL ))
        {
            char prefix[64];
            memset(prefix, '\t', _countof(prefix));
            prefix[_countof(prefix) - 1] = 0;
            //DbgPrint("%S\n", pDeviceID);
            GetPropertyByDeviceID(prefix + _countof(prefix) - 1, dnDevInst);
        }
    }
    

    或者如果我们有 RAWINPUTDEVICELIST 作为输入 - 我们可以使用 RIDI_DEVICENAME 获取设备接口(interface)名称.我们可以用这个名字调用CreateFileW获取设备的文件句柄。
    ULONG GetRawInputDeviceInfoExW(_In_opt_ HANDLE hDevice, 
                                   _In_ UINT uiCommand, 
                                   _Inout_updates_bytes_to_opt_(*pcbSize, *pcbSize) LPVOID pData, 
                                   _Inout_ PUINT pcbSize)
    {
        switch (int i = GetRawInputDeviceInfoW(hDevice, uiCommand, pData, pcbSize))
        {
        case 0:
            return ERROR_INSUFFICIENT_BUFFER;
        case 1:
            return ERROR_INVALID_NAME;
        default:
            if (0 > i)
            {
                return GetLastError();
            }
            *pcbSize = i;
    
            return NOERROR;
        }
    }
    
    void DemoRI()
    {
        PRAWINPUTDEVICELIST pRawInputDeviceList = 0;
        UINT uiNumDevices = 0; 
        UINT cch, cchAllocated = 0;
        union {
            PVOID buf;
            PWSTR name;
        };
    
        buf = 0;
    
        while (0 <= (int)GetRawInputDeviceList(pRawInputDeviceList, &uiNumDevices, sizeof(RAWINPUTDEVICELIST)))
        {
            if (pRawInputDeviceList)
            {
                do 
                {
                    HANDLE hDevice = pRawInputDeviceList->hDevice;
    
                    ULONG dwError;
                    while (ERROR_INSUFFICIENT_BUFFER == (dwError = 
                        GetRawInputDeviceInfoExW(hDevice, RIDI_DEVICENAME, name, &(cch = cchAllocated))))
                    {
                        if (cch > cchAllocated)
                        {
                            cchAllocated = RtlPointerToOffset(buf = alloca((cch - cchAllocated) * sizeof(WCHAR)), 
                                pRawInputDeviceList) / sizeof(WCHAR);
                        }
                        else
                        {
                            __debugbreak();
                        }
                    }
    
                    if (dwError == NOERROR)
                    {
                        DbgPrint("[%p, %x %S]\n", hDevice, pRawInputDeviceList->dwType, name);
                    }
                    else
                    {
                        DbgPrint("error = %u\n", dwError);
                    }
    
                } while (pRawInputDeviceList++, --uiNumDevices);
    
                break;
            }
            pRawInputDeviceList = (PRAWINPUTDEVICELIST)alloca(uiNumDevices * sizeof(RAWINPUTDEVICELIST));
        }
    }
    

    关于c++ - 由于 HID 句柄无效(Windows HID C++ API),HidD_* 函数全部失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67925174/

    相关文章:

    python - 如何从一个脚本一起启动多个其他 python 脚本并向它们发送参数?

    c++ - SDL_ttf "Couldn' t 用 C++ 中的 SDL2 加载字体文件

    c - 打印带有和不带有换行符的空字符指针时的不同输出

    c++ - 何时重载逗号运算符?

    sql - 解析 .sql 文件时输出错误

    c - 移位分配的内存

    windows - 为什么 python 中的 pool.map 不起作用

    c++ - STL容器泄漏

    c++ while循环不会在错误条件下退出

    c++ - 字符串中的IP地址检测(C++)