如何从连接的USB串口设备获取iProduct与iSerialNumber?
获取USB串口设备的iProduct和iSerialNumber信息
问题背景
我有一块搭载带USB外设MCU的PCB,已连接至PC并正在编写配套UI。该设备的USB端口被配置为串口,连接后显示为“COMx”。我希望通过USB描述符块中的iProduct和iSerialNumber字符串来判断是否为正确的COM口。
现有实现
我已经通过WMI的Win32_PnPEntity类实现了查找所有串口的功能,代码如下:
查找所有COM端口
public string[] FindAllPorts() { List<string> ports = new List<string>(); foreach (ManagementObject obj in FindPorts()) { try { if (obj["Caption"].ToString().Contains("(COM")) { string ComName = ParseCommName(obj); if (ComName != null) { ports.Add(ComName); } } } catch (Exception e) { Console.WriteLine(e); } } return ports.ToArray(); } static ManagementObject[] FindPorts() { try { ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity"); List<ManagementObject> objects = new List<ManagementObject>(); foreach (ManagementObject obj in searcher.Get()) { objects.Add(obj); } return objects.ToArray(); } catch (Exception e) { Console.WriteLine(e); return new ManagementObject[] { }; } }
获取串口名称信息
public List<string> ListPortsByName() { List<string> nameList = new List<string>(); foreach (ManagementObject obj in FindPorts()) { try { if (obj["Caption"].ToString().Contains("(COM")) nameList.Add(obj["Name"].ToString()); } catch (Exception e) { Console.WriteLine(e); } } return nameList; }
但直接把Name替换为iProduct无法获取对应字符串,需要其他方法提取USB描述符块中的iProduct和iSerialNumber信息。
解决方案
方法1:通过WMI关联USB设备属性
Win32_PnPEntity本身不直接暴露USB描述符的iProduct和iSerialNumber,需要通过关联Win32_USBControllerDevice找到对应的USB设备实例,再从Win32_USBDevice中获取相关属性:
public class UsbSerialDeviceInfo { public string ComPort { get; set; } public string ProductName { get; set; } public string SerialNumber { get; set; } } public List<UsbSerialDeviceInfo> GetUsbSerialDevices() { List<UsbSerialDeviceInfo> devices = new List<UsbSerialDeviceInfo>(); // 先获取所有串口设备的PNP设备ID Dictionary<string, string> portToDeviceId = new Dictionary<string, string>(); foreach (ManagementObject pnpEntity in new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PnPEntity WHERE Caption LIKE '%(COM%'").Get()) { string comPort = ParseCommName(pnpEntity); if (!string.IsNullOrEmpty(comPort)) { string deviceId = pnpEntity["DeviceID"].ToString(); portToDeviceId[deviceId] = comPort; } } // 关联USB控制器和设备,找到对应的USB设备 foreach (ManagementObject usbControllerDevice in new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_USBControllerDevice").Get()) { string dependent = usbControllerDevice["Dependent"].ToString(); // 提取USB设备的实例路径 string usbDeviceId = dependent.Substring(dependent.IndexOf("DeviceID=") + 8).TrimEnd('"'); if (portToDeviceId.ContainsKey(usbDeviceId)) { ManagementObject usbDevice = new ManagementObject(dependent); usbDevice.Get(); devices.Add(new UsbSerialDeviceInfo { ComPort = portToDeviceId[usbDeviceId], ProductName = usbDevice["ProductName"]?.ToString(), SerialNumber = usbDevice["SerialNumber"]?.ToString() }); } } return devices; }
方法2:使用SetupAPI直接读取USB描述符
如果WMI的属性仍无法满足需求,可以调用Windows的SetupAPI直接读取USB设备的字符串描述符,需要引入P/Invoke:
using System.Runtime.InteropServices; public static class UsbDescriptorHelper { [DllImport("setupapi.dll", SetLastError = true)] private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, string Enumerator, IntPtr hwndParent, uint Flags); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInfoData); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiGetDeviceRegistryProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, uint Property, out uint PropertyRegDataType, IntPtr PropertyBuffer, uint PropertyBufferSize, out uint RequiredSize); [StructLayout(LayoutKind.Sequential)] private struct SP_DEVINFO_DATA { public uint cbSize; public Guid ClassGuid; public uint DevInst; public IntPtr Reserved; } private const uint DIGCF_PRESENT = 0x00000002; private const uint DIGCF_DEVICEINTERFACE = 0x00000010; private const uint SPDRP_FRIENDLYNAME = 0x0000000C; private const uint SPDRP_DEVICEDESC = 0x00000000; private const uint SPDRP_SERIALNUMBER = 0x00000010; public static Dictionary<string, (string ProductName, string SerialNumber)> GetUsbSerialDevices() { Dictionary<string, (string, string)> result = new Dictionary<string, (string, string)>(); Guid usbSerialGuid = new Guid("{86E0D1E0-8089-11D0-9CE4-08003E301F73}"); // USB串口类GUID IntPtr deviceInfoSet = SetupDiGetClassDevs(ref usbSerialGuid, null, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (deviceInfoSet == IntPtr.Zero) return result; try { SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA(); deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData); for (uint i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, ref deviceInfoData); i++) { // 获取COM端口 string comPort = GetDeviceProperty(deviceInfoSet, ref deviceInfoData, SPDRP_FRIENDLYNAME); if (string.IsNullOrEmpty(comPort) || !comPort.Contains("(COM")) continue; comPort = comPort.Substring(comPort.IndexOf("(COM") + 1).TrimEnd(')'); // 获取产品名称和序列号 string productName = GetDeviceProperty(deviceInfoSet, ref deviceInfoData, SPDRP_DEVICEDESC); string serialNumber = GetDeviceProperty(deviceInfoSet, ref deviceInfoData, SPDRP_SERIALNUMBER); result[comPort] = (productName, serialNumber); } } finally { // 注意:需调用SetupDiDestroyDeviceInfoSet释放资源,可根据实际场景添加 } return result; } private static string GetDeviceProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint propertyId) { uint requiredSize = 0; SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, propertyId, out _, IntPtr.Zero, 0, out requiredSize); if (requiredSize == 0) return null; IntPtr buffer = Marshal.AllocHGlobal((int)requiredSize); try { if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, propertyId, out _, buffer, requiredSize, out _)) { return Marshal.PtrToStringAuto(buffer); } return null; } finally { Marshal.FreeHGlobal(buffer); } } }
说明
- 方法1基于WMI,代码更简洁,但依赖系统WMI服务的正确性,部分设备可能不会暴露
SerialNumber属性。 - 方法2直接调用Windows底层API,兼容性更强,能更可靠地获取USB描述符信息,但需要处理P/Invoke的细节。
内容的提问来源于stack exchange,提问作者VinDag
相关产品推荐
相关产品推荐

