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

find Location Information of device that generated WM_INPUT message

本文关键字:位置 信息 消息 INPUT WM 查找      更新时间:2023-10-16

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

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

我已经订阅了从键盘和鼠标接收WM_INPUT消息,我得到了那些。我还通过从消息中获取原始设备名称(或路径,更多信息在这里)并使用该信息从HKLMSYSTEMCurrentControlSetEnumUSB查找注册表中的位置信息来获取生成消息的设备的位置信息。为了找到位置信息,我首先找到以输入设备的硬件ID(供应商ID或VID和产品ID或PID)命名的子键,这也是原始设备路径的一部分,然后枚举其所有子键(实例ID),直到我找到一个具有ParentIdPrefix的特征,其值与也是原始设备路径的一部分的实例ID相匹配。对于该子键,我查找LocationInformation的值(格式为Port_#000X.Hub_#000Y)。这适用于连接到笔记本电脑或坞站的键盘和鼠标,即使以随机顺序重新连接设备,我从输入信息中获得的端口和集线器号码也是一致和可靠的,但当我在两者之间添加USB集线器时,它就不一致和可靠了。集线器编号似乎取决于集线器连接到系统的顺序,例如,先连接A,然后连接B,结果是Port_#0001。hub #0004用于A和port #0001。B的Hub_#0005,但反过来连接它们会得到Port_#0001。hub #0005用于A和port #0001。Hub_#0004表示B(这是我的应用程序下次接收到它们的输入消息时报告的位置信息)。USBview示例应用程序报告这些设备的一致集线器和端口号(即使重新连接和重新启动),所以我查找位置信息的某些东西一定是错误的。但是什么?显然,我不能仅仅依靠注册表来获取位置信息(我知道USBview使用SetupDi*调用和它自己的枚举例程)。所以我如何可靠地找到位置信息,就像在USBview对应于产生WM_INPUT消息的设备?例如,我可以匹配的原始输入设备句柄,我得到在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;

…在指向:

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

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

设备连接到的USB端口号可以在注册表的Address值中找到,前提是集线器的驱动程序正确设置它。早期的瑞萨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(&DevInstParent, DevInst)的方式向上走设备树。您的HID设备的第一个父设备可能是它的USB设备,其父设备是USB标准集线器或USB根集线器。

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

USB端口号不是任意的,它们是固定的,所以UsbTreeView显示为"端口链"是固定的,并给出了几乎完整的位置信息。作为主机控制器的编号,UsbTreeView在GUID_DEVINTERFACE_USB_HOST_CONTROLLER SetupDi枚举中使用它的索引。当添加或移除主机控制器时,这可能会发生变化。因此,与其使用枚举索引,不如使用设备实例ID。

USB端口编号在硬件中,所以它们不会改变。