gpt4 book ai didi

c++ - 查找生成 WM_INPUT 消息的设备的位置信息

转载 作者:塔克拉玛干 更新时间:2023-11-03 00:44:13 25 4
gpt4 key购买 nike

精简版:
在我正在测试的系统中,USB 设备和电缆应始终连接在相同的连接器上,因此在 USBview 应用程序中查看时,USB 树看起来应该始终相同。但由于我没有从该树中识别设备的信息,我仍然无法判断设备 X 是否在现场实际连接到 X。但是,我可以让设备 X 开始发送输入消息。所以我希望能够通过 USB 设备生成的输入消息来验证所有设备和布线是否正确连接。

带有更多详细信息的长版:我想测试所有 USB 电缆是否正确连接到系统中预先指定的连接器。要正确执行此操作,我需要有关系统中 USB 输入设备连接到的端口的信息。我知道这是可行的,因为我已经调试了 USBview 示例应用程序(它可以在 here 中找到)。不幸的是,我事先不知道连接的设备,所以我只能测试端口号并让设备生成输入消息来帮助我检查布线是否正确连接。为了做到这一点,我需要找出生成的消息的来源(它是设备位置信息)。这是我迷路的地方。

我已经订阅了从键盘和鼠标接收 WM_INPUT 消息,我得到了这些消息。我还通过从消息中获取原始设备名称(或路径,更多信息 here )并使用它在注册表中从 HKLM\SYSTEM\CurrentControlSet\Enum\USB 查找位置信息来获取生成消息的设备的位置信息。 .要查找位置信息,我首先找到以输入设备的硬件 ID(供应商 ID 或 VID 和产品 ID 或 PID)命名的子项,该子项也是原始设备路径的一部分,然后枚举其所有子项(实例 ID),直到找到一个带有 ParentIdPrefix具有与实例 ID 匹配的值,该 ID 也是原始设备路径的一部分。对于那个子键,我查找 LocationInformation 的值(格式化 Port_#000X.Hub_#000Y )。这适用于连接到我的笔记本电脑或扩展坞的键盘和鼠标,即使以随机顺序重新连接设备,我从输入消息中获得的端口和集线器编号也是一致且可靠的,但是当我以随机方式重新连接设备时,它不再一致和可靠在它们之间添加 USB 集线器。集线器编号似乎取决于集线器连接到系统的顺序,例如先连接 A,然后连接 B,结果 A 为 Port_#0001.Hub_#0004,B 为 Port_#0001.Hub_#0005,但连接它们反过来会导致 A 的 Port_#0001.Hub_#0005 和 B 的 Port_#0001.Hub_#0004(这是我的应用程序在下次收到输入消息时报告的位置信息)。尽管 USBview 示例应用程序报告这些设备的集线器和端口号一致(即使重新连接和重新启动),所以我查找位置信息时肯定有错误......但是什么?显然我不能仅仅依靠注册表来获取位置信息(我知道 USBview 使用 SetupDi* 调用和它自己的枚举例程)。所以如何可靠地找到与生成 WM_INPUT 消息的设备对应的 USBview 中的位置信息? 例如,我可以将在 WM_INPUT 消息中获得的原始输入设备句柄与我可以用来获取位置信息的任何内容进行匹配吗?

这是我到目前为止的代码......

...在 InitInstance 中:

// register for raw input device input messages
RAWINPUTDEVICE rid[2];
rid[0].usUsagePage = 0x01;
rid[0].usUsage = 0x06; // keyboard
rid[0].dwFlags =
RIDEV_DEVNOTIFY | // receive device arrival / removal messages
RIDEV_INPUTSINK; // receive messages even if not in foreground
rid[0].hwndTarget = hWnd;
rid[1].usUsagePage = 0x01;
rid[1].usUsage = 0x02; // mouse
rid[1].dwFlags =
RIDEV_DEVNOTIFY |
RIDEV_INPUTSINK;
rid[1].hwndTarget = hWnd;

if (RegisterRawInputDevices(rid, 2, sizeof(rid[0])) == FALSE)
{
DisplayLastError(TEXT("Failed to register for raw input devices"), hWnd);
return FALSE;
}

return TRUE;

...在 WndProc 中:
case WM_INPUT:
{
LONG lResult = Input(hWnd, lParam, ++ulCount);
if (lResult != 0) PostQuitMessage(lResult);
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;

...并在输入消息处理程序中:
LONG Input(HWND hWnd, LPARAM lParam, ULONG ulCount)
{
UINT dwSize = 0;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
LPBYTE lpb = new BYTE[dwSize];
if (lpb == NULL)
{
MessageBox(hWnd, TEXT("Unable to allocate buffer for raw input data!"), TEXT("Error"), MB_OK);
return 1;
}
std::unique_ptr<BYTE, void(*)(LPBYTE)> lpbd(lpb, [](LPBYTE p) { delete[] p; });

if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize)
{
MessageBox(hWnd, TEXT("GetRawInputData returned incorrect size!"), TEXT("Error"), MB_OK);
return 1;
}

RAWINPUT* raw = (RAWINPUT*)lpb;
if (raw->header.dwType == RIM_TYPEKEYBOARD && raw->data.keyboard.VKey == 0x51)
{
OutputDebugString(TEXT("Q for Quit was pressed, exiting application\n"));
return 1;
}

TCHAR ridDeviceName[256];
dwSize = 256;
UINT dwResult = GetRawInputDeviceInfo(raw->header.hDevice, RIDI_DEVICENAME, &ridDeviceName, &dwSize);
if (dwResult == 0 || dwResult == UINT(-1))
{
return DisplayLastError(TEXT("Failed to get raw input device info"), hWnd);
}

const std::wstring devicePath(ridDeviceName);
OutputDebugString((std::to_wstring(ulCount) + L": Received WM_INPUT for USB device with path: " + devicePath + L"\n").c_str());

HKEY hKey;
std::wstring keypath = L"SYSTEM\\CurrentControlSet\\Enum\\USB";
LONG lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keypath.c_str(), 0, KEY_READ, &hKey);
if (lResult != ERROR_SUCCESS)
{
keypath = keypath.insert(0, L"Failed to open registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}
std::unique_ptr<HKEY__, void(*)(HKEY)> hkeyd(hKey, [](HKEY h) { RegCloseKey(h); });

DWORD dwIndex = 0;
TCHAR subKeyName[256];
do
{
DWORD dwSubKeyNameLength = 256;
lResult = RegEnumKeyEx(hKey, dwIndex++, subKeyName, &dwSubKeyNameLength, NULL, NULL, NULL, NULL);
if (lResult != ERROR_SUCCESS && lResult != ERROR_NO_MORE_ITEMS)
{
keypath = keypath.insert(0, L"Failed to enumerate registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}

if (lResult == ERROR_SUCCESS && devicePath.find(subKeyName) != -1)
{
const std::wstring hardwareId(subKeyName);
keypath += L"\\" + hardwareId;
HKEY hSubKey;
lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, keypath.c_str(), 0, KEY_READ, &hSubKey);
if (lResult != ERROR_SUCCESS)
{
keypath = keypath.insert(0, L"Failed to open registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}
std::unique_ptr<HKEY__, void(*)(HKEY)> hsubkeyd(hSubKey, [](HKEY h) { RegCloseKey(h); });

// \\?\HID#VID_046D&PID_C016#7&d0f899c&0&0000#{378de44c-56ef-11d1-bc8c-00a0c91405dd}
// vendorID productID ParentIdPrefix (without the &0000)
// \\?\HID#VID_413C&PID_2003#7&2a634b73&0&0000#{884b96c3-56ef-11d1-bc8c-00a0c91405dd}
// DeviceClass Guid, leads to prefixed info in registry

DWORD dwSubIndex = 0;
do
{
dwSubKeyNameLength = 256;
lResult = RegEnumKeyEx(hSubKey, dwSubIndex++, subKeyName, &dwSubKeyNameLength, NULL, NULL, NULL, NULL);
if (lResult != ERROR_SUCCESS && lResult != ERROR_NO_MORE_ITEMS)
{
keypath = keypath.insert(0, L"Failed to enumerate registry key");
return DisplayLastError(&keypath[0], lResult, hWnd);
}

if (lResult == ERROR_SUCCESS)
{
std::wstring targetkeypath = keypath + L"\\" + subKeyName;
HKEY hTargetKey;
lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, targetkeypath.c_str(), 0, KEY_READ, &hTargetKey);
if (lResult != ERROR_SUCCESS)
{
targetkeypath = targetkeypath.insert(0, L"Failed to open registry key");
return DisplayLastError(&targetkeypath[0], lResult, hWnd);
}
std::unique_ptr<HKEY__, void(*)(HKEY)> htargetkeyd(hTargetKey, [](HKEY h) { RegCloseKey(h); });

TCHAR valueBuffer[256];
DWORD dwBufferSize = 256;
lResult = RegQueryValueEx(hTargetKey, L"ParentIdPrefix", 0, NULL, (LPBYTE)valueBuffer, &dwBufferSize);
if (lResult != ERROR_SUCCESS && lResult != ERROR_FILE_NOT_FOUND)
{
targetkeypath = targetkeypath.insert(0, L"Failed to get registry value of ParentIdPrefix for key");
return DisplayLastError(&targetkeypath[0], lResult, hWnd);
}

if (lResult == ERROR_SUCCESS && devicePath.find(valueBuffer) != -1)
{
dwBufferSize = 256;
lResult = RegQueryValueEx(hTargetKey, L"LocationInformation", 0, NULL, (LPBYTE)valueBuffer, &dwBufferSize);
if (lResult != ERROR_SUCCESS)
{
targetkeypath = targetkeypath.insert(0, L"Failed to get registry value of LocationInformation for key");
return DisplayLastError(&targetkeypath[0], lResult, hWnd);
}
OutputDebugString((std::to_wstring(ulCount) + L": " + hardwareId + L" is located at: " + valueBuffer + L"\n").c_str());
}
}
}
while (lResult == ERROR_SUCCESS || lResult == ERROR_FILE_NOT_FOUND);
}
}
while (lResult == ERROR_SUCCESS);

return ERROR_SUCCESS; // non-0 return codes indicate failure
}

更新:我设法使用 SetupDiGetDeviceRegistryProperty 获取位置信息但这只是向我显示了我之前从注册表中获得的相同位置信息。 USBview 示例应用程序必须滚动它自己的枚举,一旦我发现它基于什么,我将使用它作为位置信息,而不是注册表报告的位置信息。我非常想知道为什么 USBview 示例应用程序报告 USB 连接的可靠端口编号(以及基于什么?)但系统在注册表中维护的位置信息似乎取决于连接顺序?

最佳答案

如果集线器的驱动程序正确设置了设备所连接的 USB 端口号,则可以在地址值的注册表中找到它。早期的瑞萨 USB3 驱动程序没有。您可以通过我的增强版USBview,UsbTreeView检查地址值是否设置正确。 .
我不知道它究竟来自哪里,但对于任何 USB 设备和 USB 集线器 CM_Get_DevInst_Registry_Property(CM_DRP_ADDRESS) 或 SetupDiGetDeviceRegistryProperty(SPDRP_ADDRESS) 提供 USB 端口号。

USBview 使用 USB API 使用自下而上的方法。由于您从设备的 DevicePath 开始,使用设置 API 的自下而上的方法更方便:

您需要设备的 DEVINST 通过 CM_Get_Parent 向上走设备树并读取每个设备的地址,直到您点击 USB 根集线器或其主机 Controller 。因为只有 DevicePath,所以首先需要设备的 InterfaceClassGuid。您可以通过 SetupDiOpenDeviceInterface 获得它。

使用 InterfaceClassGuid,您可以通过 SetupDiGetClassDevs 获得设备列表。通过 SetupDiEnumDeviceInterfaces(提供 DevicePath)请求每个设备索引,直到您点击您的设备或它返回 FALSE。

如果你点击你的设备,你也会得到 DevInst 并到达 CM_ API 的世界,在那里你可以通过 CM_Get_Parent(&DevInstParent, DevInst) 向上走设备树。您的 HID 设备的第一个父设备可能是它的 USB 设备,其父设备是 USB 标准集线器或 USB 根集线器。

我从未见过具有 USB 硬件序列号的 USB 标准集线器,因此当它连接到新位置时,Windows 会为其创建一个新设备实例。
您所能得到的只是它的设备实例 ID (CM_Get_Device_ID),其中有一个固定部分,如 USB\VID_05E3&PID_0608,以及每个新实例的末尾生成部分,如 5&130B8FC2&0&3。如果您的设备树没有改变,那么这应该足够了。

USB 端口号不是任意的,它们是固定的,因此 UsbTreeView 显示为“端口链”的内容是固定的,并给出了几乎完整的位置信息。作为主机 Controller 的编号,UsbTreeView 在 GUID_DEVINTERFACE_USB_HOST_CONTROLLER SetupDi 枚举中使用其索引。添加或删除主机 Controller 时,这可能会发生变化。因此,与其枚举索引,其设备实例 ID 在这里可能是更好的选择。

USB 端口号在硬件中,因此它们不会更改。

关于c++ - 查找生成 WM_INPUT 消息的设备的位置信息,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23954997/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com