short version: in the system I am testing the USB devices and cabling should always be connected at the same connectors, so when viewed in the USBview application the USB tree should always look the same. but since I will not have the info to identify the devices from that tree I still won't be able to tell if device X is actually connected at the spot for X. however, I can make device X start sending input messages. so I'd like to be able to verify that all the devices and cabling are connected properly from input messages generated by the USB devices.
long version with more details: I want to test if all USB cabling is properly connected to pre-specified connectors in a system. to do this properly I need info on the ports to which the USB input devices in the system are connected. I know this is doable because I have debugged the USBview sample application (it can be found here). unfortunately I will have no knowledge of the connected devices beforehand so I can only test on port numbers and let the devices generate input messages to help me check if the cabling is connected properly. in order to do this I need to find out where the generated message is from (it's device location information). this is where I get lost.
I have subscribed to receiving WM_INPUT messages from keyboards and mice and I get those. I also get the location information of the device that generated the message by getting the raw device name (or path, more info here) from the message and using that to lookup the Location Information in the registry from HKLM\SYSTEM\CurrentControlSet\Enum\USB
. to find the Location Information I first find the subkey named with the input device's hardware ID (vendor ID or VID and product ID or PID) that is also part of the raw device path and then enumerate all its subkeys (instance IDs) until I find one that features a ParentIdPrefix
with a value that matches the instance ID that is also part of the raw device path. for that subkey I lookup the value of LocationInformation
(formatted Port_#000X.Hub_#000Y
). this works for keyboards and mice attached to my laptop or to my docking station, the port and hub numbers I get from input messages are consistent and reliable even when re-attaching the devices in random order, but it stops being consistent and reliable when I add USB hubs in between. the hub numbers seem to depend on the order in which the hubs are connected to the system, for example connecting A first and B next results in Port_#0001.Hub_#0004 for A and Port_#0001.Hub_#0005 for B but connecting them the other way around results in Port_#0001.Hub_#0005 for A and Port_#0001.Hub_#0004 for B (that's the location information my application reports when next receiving their input messages). the USBview sample application reports consistent hub and port numbers for these devices though (even with re-attaching and restarting), so something in my lookup of location information must be wrong.. but what? apparently I cannot rely on the registry alone to get the location information (I know USBview uses SetupDi* calls and its own enumeration routines). so how do I reliably find the location information like that in USBview corresponding to the device that generated the WM_INPUT message? for example, can I match the raw input device handle that I get in the WM_INPUT message to anything that I can then use to get the location information like USBview does?
here is the code I have so far...
... in 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;
... in WndProc:
case WM_INPUT:
{
LONG lResult = Input(hWnd, lParam, ++ulCount);
if (lResult != 0) PostQuitMessage(lResult);
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
... and in the Input message handler:
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
}
UPDATE: I managed to get the location information using SetupDiGetDeviceRegistryProperty
but that just shows me the same location information that I got from the registry myself earlier. the USBview sample application must be rolling its own enumeration, once I find out what it's based on I'll use that as location information instead of the location information reported by the registry. I'd very much like to know why the USBview sample application reports reliable port numbering for USB connections (and based on what?) but location information maintained in the registry by the system seems to depend on connection order?