首页 > 解决方案 > 在 C# 中获取连接/断开的 USB HID 设备的“路径”

问题描述

作为一名飞行模拟爱好者,我正在制作自己的驾驶舱硬件。我使用 USB HID 接口(基于 PIC 微控制器的硬件)将此硬件与我的计算机连接。所有硬件都具有相同的 VID/PID,产品描述符字符串是“FMGS HW Device”(仅供参考:FMGS 是我正在使用的空客 A320 模拟器软件的名称)。

启动时,我的应用程序会扫描所有 USB 设备,并且只将带有“VID/PID/产品描述符”组合的设备放入字典中。作为关键,我正在使用通过 PSP_DEVICE_INTERFACE_DETAIL_DATA 结构检索到的“DevicePath”,并调用 SetupDiGetDeviceInterfaceDetail (setupapi.dll)。

下面是一个小代码摘录,展示了这个操作的核心——只是为了让你明白:

// Retrieving the detailDataBuffer
SetupApi.SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref deviceInterfaceData, detailDataBuffer, bufferSize, ref bufferSize, IntPtr.Zero);
 
// Skip over cbsize (4 bytes) to get the address of the devicePathName
var pDevicePath = new IntPtr(detailDataBuffer.ToInt32() + 4);

// Get the String containing the devicePath
AddDevice(Marshal.PtrToStringAuto(pDevicePath));

此 DevicePath 具有以下格式:

\?\hid#vid_04d8&pid_003f#9&599cfdc&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}

问题1: 我可以清楚地看到VID/PID部分(vid_04d8&pid_003f)和HidGuid(4d1e55b2-f16f-11cf-88cb-001111000030),但是“9&599cfdc&0&0000”的部分是什么?每个设备都是独一无二的吗?换句话说,我的 2 台设备是否存在完全相同的 DevicePath 的风险?

现在我还想检测应用程序运行时设备是否连接/断开。如果设备断开连接,我需要将它们从我的字典中删除。如果设备已连接,我需要将它们放入我的字典并开始与它们通信。

我正在使用“WMI 方法”(我知道 WndProc 中还有一个 WM_DEVICECHANGE 方法,不知道哪个更好?)。下面是代码(尚未完成,但到目前为止有效)。

    private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
    {
        Debug.WriteLine($"DeviceInsertEvent");
        ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];

        foreach(PropertyData p in instance.Properties )
        {
            Debug.WriteLine($"{p.Name} = {p.Value }");
        }

        Debug.WriteLine($"{instance.Properties["Dependent"].Value }");
    }

    private void DeviceRemovedEvent(object sender, EventArrivedEventArgs e)
    {
        Debug.WriteLine($"DeviceRemovedEvent");
        ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];

        foreach (PropertyData p in instance.Properties)
        {
            Debug.WriteLine($"{p.Name} = {p.Value }");
        }

        Debug.WriteLine($"{instance.Properties["Dependent"].Value}");
    }

    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        var scope = new ManagementScope("root\\CIMV2");
        scope.Options.EnablePrivileges = true;

        try
        {
            WqlEventQuery insertQuery = new WqlEventQuery();
            insertQuery.EventClassName = "__InstanceCreationEvent";
            insertQuery.WithinInterval = new TimeSpan(0, 0, 1);
            insertQuery.Condition = @"TargetInstance ISA 'Win32_USBControllerdevice'";
            ManagementEventWatcher insertWatcher = new ManagementEventWatcher(insertQuery);
            insertWatcher.EventArrived += new EventArrivedEventHandler(DeviceInsertedEvent);
            insertWatcher.Start();

            WqlEventQuery removeQuery = new WqlEventQuery();
            removeQuery.EventClassName = "__InstanceDeletionEvent";
            removeQuery.WithinInterval = new TimeSpan(0, 0, 1);
            removeQuery.Condition = @"TargetInstance ISA 'Win32_USBControllerdevice'";
            ManagementEventWatcher removeWatcher = new ManagementEventWatcher(removeQuery);
            removeWatcher.EventArrived += new EventArrivedEventHandler(DeviceRemovedEvent);
            removeWatcher.Start();
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"backgroundWorker_DoWork Exception: {ex}");
        }

        while (true) ;
    }

我不是 WMI 专家,所以老实说我不知道​​我在做什么(仍在学习) - 只是从几个谷歌搜索中获得了代码。我发现“instance.Properties[“Dependent”].Value”也给了我某种DevicePath,格式如下。

\DESKTOP-HANS\root\cimv2:Win32_PnPEntity.DeviceID="HID\VID_04D8&PID_003F\9&2E7AE93E&0&0000"

我看到了相同的 VID/PID 组合,以及我在问题 1中要求的未知“9&2E7AE93E&0&0000”部分。所以基本上,通过一些字符串操作,我可以重建我在字典中用作键的相同 DevicePath。

问题 2: 是否有另一种方法来发现设备连接/断开连接事件,这些事件给我的 DevicePath 与 SetupDiGetDeviceInterfaceDetail 返回的相同?

标签: c#visual-studiousbhid

解决方案


要获得有关此主题的最佳知识来源,我可以推荐USB Complete The Developer's Guide book、USBViewHClient GitHub 上的官方 MS 示例。

\?\hid#vid_04d8&pid_003f#9&599cfdc&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}是所谓的设备接口路径。MS 没有记录其格式(因此可能随时更改)并且解析它不安全。每个设备都可以提供多个接口与之交互。例如,每个 USB HID 设备也有与GUID_DEVINTERFACE_USB_DEVICE-的接口{A5DCBF10-6530-11D2-901F-00C04FB951ED}

有两个主要的 Win32 API 可用于查询连接的设备:SetupDi*(定义在 中SetupAPI.h)和CM_*(定义在 中cfgmgr32.h)。您也可以使用 WMI,但我不熟悉它。

关于如何将设备接口路径转换为 ​​VID/PID:

您可以使用CM_Get_Device_Interface_PropertyW(或SetupDiGetDevicePropertyW)与DEVPKEY_Device_InstanceId参数来请求“设备实例标识符”。之后,您可以通过CM_Locate_DevNodeW(或SetupDiGetClassDevsW)使用您获得的设备实例 ID 打开该设备并DEVPKEY_Device_HardwareIds在其上查询(字符串列表)属性。并返回您可以解析的内容,因为它记录在案:

Each interface has a device ID of the following form:
USB\VID_v(4)&PID_d(4)&MI_z(2)

Where:
  v(4) is the 4-digit vendor code that the USB committee assigns to the vendor.
  d(4) is the 4-digit product code that the vendor assigns to the device.
  z(2) is the interface number that is extracted from the bInterfaceNumber field of the interface descriptor.

PS:在我提到的一本书中找到了带有 VID/PID 过滤的 WMI 示例代码:

void FindDevice()
{
    const String deviceIdString = @”USB\VID_0925&PID_150C”;
    _deviceReady = false;
    var searcher = new ManagementObjectSearcher(“root\\CIMV2”, “SELECT PNPDeviceID FROM Win32_PnPEntity”);
    foreach (ManagementObject queryObj in searcher.Get()) {
        if (queryObj[“PNPDeviceID”].ToString().Contains(deviceIdString)) {
            _deviceReady = true;
        }
    }
}

推荐阅读