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

HidD_* functions all fail due to an invalid HID handle (Windows HID C++ API)

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

我的下一步是尝试使用 Hidpi.h 实用程序来确定我的触摸板支持哪些用途 (HidP_GetCaps(), HidP_GetValueCaps(), etc...),然后获取它们的值。为了让事情顺利进行,我只是尝试打印 X、Y、提示开关、联系人 ID 和最大联系人计数)。

为了使用上述实用程序,我从文档中注意到我通常需要两样东西:准备好的数据 (PHIDP_PREPARSED_DATA) 和实际的输入或功能报告。

这就是事情开始出错的地方。我读过的所有内容都说我应该能够使用 HidD_GetPreparsedData() 来获得第一项。我传递的 HID 设备句柄是 raw_data_buffer->header.hDevice,其中 raw_data_buffer 是我从 GetRawInputData() 填充的 PRAWINPUT 类型。不幸的是,这失败了。调用 GetLastError() returns 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) 会出现在我的特征值报告中。

接下来关注 Input/Feature 报告,我 运行 进入我原来的问题。 HidP_GetUsageValue() 的第 7 个参数是输入或功能报告的 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……我猜是因为原始输入报告必须是原始数据的第一个块。但是,我仍然无法使用 HidP_GetUsageValue( HidP_Feature... 获取功能报告,因为我不知道该报告在原始输入中的位置。

我花了 10 多个小时研究可能的原因和解决方法,但一无所获。出于我的应用程序的目的,我也许可以只使用输入报告值,但必须缺少一些东西。

6/14 更新: @RbMm @psmears 感谢您的评论。我没有意识到 GetRawInputDeviceInfo() 中的 return,使用 RIDI_DEVICENAME,是为我的设备创建文件所需的符号路径。我不需要使用 SetupApi 或 cfgmgr32 来检查所有设备接口。不过,我仍然无法为我的设备创建文件。因为我可以在枚举所有设备接口时看到我正在寻找的路径,所以我决定通过为每个接口创建一个新文件来进行测试,以排除路径 format/encoding 问题。这是我的输出:

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)是触摸板。最后几个接口与同一个 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 文档:“如果指定的文件不存在并且是可写位置的有效路径,该函数将创建一个文件并将最后一个错误代码设置为零”这意味着这不是一个有效的、可写的位置,但在枚举我的接口时它肯定会出现。

我是否无法将文件打开到设备,因为它是使用 WM_INPUT 消息发送的原始输入?

“句柄”是一个非常通用的东西。我们可以称之为“句柄”的任何不透明指针大小的值。

HidD_GetPreparsedData 需要 文件句柄 。此句柄必须 openCreateFileWNtOpenFileNtCreateFile。并且此句柄必须 关闭CloseHandleNtClose 一旦句柄不再使用。

hDeviceRAWINPUTDEVICELIST is not a file handle. This is some opaque value, which does not need to be closed. GetRawInputDeviceListreturns一个数组RAWINPUTDEVICELIST并没有说需要关闭 hDevice.

调用CM_Get_Device_Interface_ListW with GUID_DEVINTERFACE_HID可以列出系统中所有的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 获取设备接口名称。我们可以使用此名称调用 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));
    }
}

根据this microsoft sample,您可以使用BOOLEAN GetFeature (PHID_DEVICE HidDevice)来获取特征。并希望示例对您有用。