c# - 在 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 返回的相同?
解决方案
要获得有关此主题的最佳知识来源,我可以推荐USB Complete The Developer's Guide book、USBView和HClient 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;
}
}
}
推荐阅读
- python - 如何获得检查的最终结果 [PYTHON]
- office-js - Office Word 加载项 JS - 如何避免加载项执行的操作出现在撤消历史记录中
- python - 查找文件夹中所有文件的行数
- podman - 在不运行容器的情况下如何确定 UID?
- javascript - 将数组中的相同元素组合为数组中的一个字符串的更简洁的方法?
- javascript - Vue UI 不会随着状态变化而更新,直到我将鼠标悬停在它上面
- python - 如何在 anaconda 上安装 tensorflow 2.4>?
- c++ - std::iterator_traits 中的类型别名的“默认值”是否总是正确的?
- google-app-engine - GAE 数据存储区“java.lang.IllegalArgumentException:属性‘${property}’包含无效的嵌套实体。”
- r - rbindlist 列表长度和性能