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

mti*_*ijn 5 c++ usb winapi windows-7-x64

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

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

我已经订阅了从键盘和鼠标接收WM_INPUT消息,我得到了这些消息.我还通过从消息中获取原始设备名称(或路径,此处提供更多信息)并使用该设备查找注册表中的位置信息来获取生成消息的设备的位置信息HKLM\SYSTEM\CurrentControlSet\Enum\USB.找到位置信息我首先找到以输入设备的硬件ID(供应商ID或VID和产品ID或PID)命名的子项,它也是原始设备路径的一部分,然后枚举其所有子项(实例ID),直到找到一个具有ParentIdPrefix与实例ID匹配的值的一个,该实例ID也是原始设备路径的一部分.对于该子项,我查找LocationInformation(格式化Port_#000X.Hub_#000Y)的值.这适用于连接到我的笔记本电脑或我的扩展坞的键盘和鼠标,即使以随机顺序重新连接设备,我从输入消息中获得的端口和集线器编号也是一致且可靠的,但是当我以后它停止一致且可靠在它们之间添加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中:

// 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;
Run Code Online (Sandbox Code Playgroud)

...在WndProc中:

case WM_INPUT:
    {
        LONG lResult = Input(hWnd, lParam, ++ulCount);
        if (lResult != 0) PostQuitMessage(lResult);
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break;
Run Code Online (Sandbox Code Playgroud)

...并在Input消息处理程序中:

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
}
Run Code Online (Sandbox Code Playgroud)

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

Uwe*_*ber 3

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

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

您需要设备的 DEVINST 通过 CM_Get_Parent 向上遍历设备树并读取每个设备的地址,直到您到达 USB 根集线器或其主机控制器。由于 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显示的“端口链”是固定的,并给出了几乎完整的位置信息。作为主机控制器的编号,UsbTreeView 使用其在 GUID_DEVINTERFACE_USB_HOST_CONTROLLER SetupDi 枚举中的索引。当添加或删除主机控制器时,这可能会发生变化。因此,设备实例 ID 可能是更好的选择,而不是枚举索引。

USB 端口号位于硬件中,因此不会改变。