由于无效的 HID 句柄 (Windows HID C++ API),HidD_* 函数全部失败
HidD_* functions all fail due to an invalid HID handle (Windows HID C++ API)
我在从我的 C++ 桌面应用程序中的 Windows 精密触摸板获取使用数据时遇到问题。我已经完成了以下工作:
- 注册原始输入
- 我收到 WM_INPUT 条包含原始数据的消息
- 我可以调用
GetRawInputData()
到 return 一个 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 是我从 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
需要 文件句柄 。此句柄必须 open 且 CreateFileW
或 NtOpenFile
、NtCreateFile
。并且此句柄必须 关闭 并 CloseHandle
或 NtClose
一旦句柄不再使用。
hDevice在RAWINPUTDEVICELIST
is not a file handle. This is some opaque value, which does not need to be closed. GetRawInputDeviceList
returns一个数组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)
来获取特征。并希望示例对您有用。
我在从我的 C++ 桌面应用程序中的 Windows 精密触摸板获取使用数据时遇到问题。我已经完成了以下工作:
- 注册原始输入
- 我收到 WM_INPUT 条包含原始数据的消息
- 我可以调用
GetRawInputData()
到 return 一个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 是我从 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
需要 文件句柄 。此句柄必须 open 且 CreateFileW
或 NtOpenFile
、NtCreateFile
。并且此句柄必须 关闭 并 CloseHandle
或 NtClose
一旦句柄不再使用。
hDevice在RAWINPUTDEVICELIST
is not a file handle. This is some opaque value, which does not need to be closed. GetRawInputDeviceList
returns一个数组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)
来获取特征。并希望示例对您有用。