简短版: 在系统中,我正在测试USB设备,并且应始终在相同的连接器上连接电缆,因此在USBview应用程序中查看时,USB树应始终保持相同。但由于我没有信息来识别该树中的设备,我仍然无法判断设备X是否实际连接到X的位置。但是,我可以让设备X开始发送输入消息。所以我希望能够验证USB设备生成的输入消息是否正确连接了所有设备和电缆。
包含更多详细信息的长版本:我想测试所有USB电缆是否已正确连接到系统中的预先指定的连接器。要正确执行此操作,我需要有关系统中USB输入设备所连接的端口的信息。我知道这是可行的,因为我已经调试了USBview示例应用程序(可以找到它here)。不幸的是,我事先不知道所连接的设备,所以我只能测试端口号,让设备生成输入消息,以帮助我检查布线是否正确连接。为了做到这一点,我需要找出生成的消息来自哪里(它的设备位置信息)。这是我迷路的地方。
我订阅了从键盘和鼠标接收WM_INPUT消息,我得到了这些消息。我还通过从消息中获取原始设备名称(或路径,更多信息here)并使用该设备从{{1}查找注册表中的位置信息来获取生成消息的设备的位置信息。 }。找到位置信息我首先找到以输入设备的硬件ID(供应商ID或VID和产品ID或PID)命名的子项,它也是原始设备路径的一部分,然后枚举其所有子项(实例ID),直到找到一个具有HKLM\SYSTEM\CurrentControlSet\Enum\USB
的值,其值与实例ID相匹配,该实例ID也是原始设备路径的一部分。对于该子项,我查找ParentIdPrefix
的值(格式为LocationInformation
)。这适用于连接到我的笔记本电脑或我的扩展坞的键盘和鼠标,即使以随机顺序重新连接设备,我从输入消息中获得的端口和集线器编号也是一致且可靠的,但是当我以后它停止保持一致和可靠在它们之间添加USB集线器。集线器号码似乎取决于集线器连接到系统的顺序,例如将A和B的下一个结果连接到Port_#0001.Hub_#0004用于A和Port_#0001.Hub_#0005用于B但连接它们反过来导致A的端口#0001.Hub_#0005和B的端口_#0001.Hub_#0004(这是我的应用程序在下次接收输入消息时报告的位置信息)。 USBview示例应用程序报告这些设备的一致的集线器和端口号(即使重新连接并重新启动),因此查找位置信息的某些内容必定是错误的...但是什么?显然我不能单独依赖注册表来获取位置信息(我知道USBview使用SetupDi *调用和它自己的枚举例程)。所以如何可靠地找到与生成WM_INPUT消息的设备对应的USBview中的位置信息?例如,我可以将WM_INPUT消息中的原始输入设备句柄与任何内容相匹配那我可以用它来获取像USBview这样的位置信息吗?
这是我到目前为止的代码......
...在InitInstance中:
Port_#000X.Hub_#000Y
...在WndProc:
// 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;
...并在输入消息处理程序中:
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
}
获取位置信息,但这只是向我显示了我之前从注册表中获得的相同位置信息。 USBview示例应用程序必须滚动自己的枚举,一旦我发现它是基于我将使用它作为位置信息而不是注册表报告的位置信息。 我非常想知道为什么USBview示例应用程序报告USB连接的可靠端口编号(并基于什么?)但是系统在注册表中维护的位置信息似乎取决于连接顺序?
答案 0 :(得分:2)
如果集线器的驱动程序正确设置,则可在注册表中的“地址”值中找到设备所连接的USB端口号。早期的瑞萨USB3驱动程序没有。您可以通过我的增强版USBview UsbTreeView检查地址值是否设置正确。 我不知道它究竟来自哪里,但是对于任何USB设备和USB集线器,CM_Get_DevInst_Registry_Property(CM_DRP_ADDRESS)或SetupDiGetDeviceRegistryProperty(SPDRP_ADDRESS)都会提供USB端口号。
USBview使用USB API使用自下而下的方法。由于您从设备的DevicePath开始,使用Setup API的自下而上方法更方便:
您需要设备的DEVINST通过CM_Get_Parent向上走设备树并读取每个设备的地址,直到您点击USB根集线器或其主控制器。由于您拥有的是DevicePath,因此您首先需要设备的InterfaceClassGuid。你可以通过SetupDiOpenDeviceInterface获得它。
使用InterfaceClassGuid,您可以通过SetupDiGetClassDevs获取设备列表。通过SetupDiEnumDeviceInterfaces(提供DevicePath)请求每个设备索引,直到您点击设备或它返回FALSE。
如果你点击了你的设备,你也得到了DevInst,并且到达了CM_ API的世界,在那里你可以通过CM_Get_Parent(&amp; DevInstParent,DevInst)向上走设备树。您的HID设备的第一个父设备可能是其USB设备,其父设备是USB标准集线器或USB根集线器。
我从未见过具有USB硬件序列号的USB标准集线器,因此Windows在连接到新位置时会为其创建新的设备实例。 所有你能得到的是它的设备实例id(CM_Get_Device_ID),其具有固定部分,如USB \ VID_05E3&amp; PID_0608,以及最后生成的部分,如5&amp; 130B8FC2&amp; 0&amp; 3,用于每个新实例。如果您的设备树没有改变,那么这应该足够了。
USB端口号不是任意的,它们是固定的,因此UsbTreeView显示为“端口链”的是固定的,并提供几乎完整的位置信息。作为主机控制器的编号,UsbTreeView在GUID_DEVINTERFACE_USB_HOST_CONTROLLER SetupDi枚举中使用其索引。添加或删除主机控制器时,这可能会发生变化。因此,它的设备实例ID可能是更好的选择,而不是它的枚举索引。
USB端口号在硬件中,因此不会更改。