You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从连接的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 12:57:34