如何在C++/C#中获取COM端口列表及对应设备序列号
在C#中列出带设备ID(序列号)的活跃COM端口
针对你提到的多PC间串口设备流转、需要自动识别COM端口的需求,以下两种方案可实现获取COM端口对应设备ID/序列号的功能,均支持Windows 7及以上系统,适配FTDI、Silicon Labs等USB转RS232转换器。
方法一:使用WMI(快速实现)
通过Windows Management Instrumentation查询系统硬件信息,关联COM端口与设备属性,代码实现简单,适合大多数场景。需先添加System.Management引用。
代码示例
using System; using System.Management; using System.IO.Ports; class Program { static void Main(string[] args) { string[] portNames = SerialPort.GetPortNames(); foreach (string portName in portNames) { // 查询包含当前COM端口的设备实体 var query = new ObjectQuery($"SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%{portName}%'"); using (var searcher = new ManagementObjectSearcher(query)) { foreach (ManagementObject obj in searcher.Get()) { string deviceId = obj["DeviceID"]?.ToString(); string serialNumber = obj["SerialNumber"]?.ToString(); string friendlyName = obj["Name"]?.ToString(); Console.WriteLine($"COM端口: {portName}"); Console.WriteLine($"设备名称: {friendlyName}"); Console.WriteLine($"设备ID: {deviceId}"); Console.WriteLine($"序列号: {serialNumber ?? "未找到"}"); Console.WriteLine("---"); } } } Console.ReadLine(); } }
适配说明
- FTDI设备:序列号通常包含在
DeviceID中(格式示例:FTDIBUS\VID_0403+PID_6001+A123456A\0000,其中A123456A即为序列号) - Silicon Labs设备:可直接读取
SerialNumber属性,或从DeviceID中提取对应字段
方法二:使用SetupAPI(底层可靠)
若WMI存在属性缺失或延迟问题,可直接调用Windows底层SetupAPI函数,兼容性更强,适合对设备信息精度要求高的场景。
代码示例
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.IO.Ports; public class ComPortInfo { public string PortName { get; set; } public string DeviceId { get; set; } public string SerialNumber { get; set; } } class Program { // P/Invoke 声明SetupAPI函数 [DllImport("setupapi.dll", SetLastError = true)] private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr 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, StringBuilder PropertyBuffer, uint PropertyBufferSize, out uint RequiredSize); [DllImport("setupapi.dll", SetLastError = true)] private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); // 设备信息结构体 [StructLayout(LayoutKind.Sequential)] private struct SP_DEVINFO_DATA { public uint cbSize; public Guid ClassGuid; public uint DevInst; public IntPtr Reserved; } // SetupAPI常量 private const uint DIGCF_PRESENT = 0x00000002; private const uint DIGCF_DEVICEINTERFACE = 0x00000010; private const uint SPDRP_HARDWAREID = 0x00000001; private const uint SPDRP_FRIENDLYNAME = 0x0000000C; private const uint SPDRP_SERIALNUMBER = 0x00000014; // 串口设备类GUID private static readonly Guid GUID_DEVINTERFACE_COMPORT = new Guid("{86e0d1e0-8089-11d0-9ce4-08003e301f73}"); static void Main(string[] args) { List<ComPortInfo> comPorts = GetComPortsWithDeviceInfo(); foreach (ComPortInfo port in comPorts) { Console.WriteLine($"COM端口: {port.PortName}"); Console.WriteLine($"设备ID: {port.DeviceId}"); Console.WriteLine($"序列号: {port.SerialNumber ?? "未找到"}"); Console.WriteLine("---"); } Console.ReadLine(); } private static List<ComPortInfo> GetComPortsWithDeviceInfo() { List<ComPortInfo> comPorts = new List<ComPortInfo>(); IntPtr deviceInfoSet = IntPtr.Zero; try { // 获取当前存在的所有串口设备 deviceInfoSet = SetupDiGetClassDevs(ref GUID_DEVINTERFACE_COMPORT, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (deviceInfoSet == IntPtr.Zero) throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error()); SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA(); deviceInfoData.cbSize = (uint)Marshal.SizeOf(deviceInfoData); for (uint i = 0; SetupDiEnumDeviceInfo(deviceInfoSet, i, ref deviceInfoData); i++) { ComPortInfo portInfo = new ComPortInfo(); // 提取COM端口号 StringBuilder friendlyName = new StringBuilder(256); if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_FRIENDLYNAME, out _, friendlyName, (uint)friendlyName.Capacity, out _)) { string name = friendlyName.ToString(); int comIndex = name.IndexOf("(COM"); if (comIndex != -1) { portInfo.PortName = name.Substring(comIndex + 1, name.Length - comIndex - 2); } } // 获取硬件ID(设备ID) StringBuilder hardwareId = new StringBuilder(1024); if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_HARDWAREID, out _, hardwareId, (uint)hardwareId.Capacity, out _)) { portInfo.DeviceId = hardwareId.ToString(); // 提取FTDI序列号 if (portInfo.DeviceId.Contains("FTDIBUS")) { string[] parts = portInfo.DeviceId.Split('+'); if (parts.Length >= 3) { portInfo.SerialNumber = parts[2].Split('\\')[0]; } } } // 获取序列号(适配Silicon Labs等设备) StringBuilder serialNumber = new StringBuilder(256); if (SetupDiGetDeviceRegistryProperty(deviceInfoSet, ref deviceInfoData, SPDRP_SERIALNUMBER, out _, serialNumber, (uint)serialNumber.Capacity, out _)) { portInfo.SerialNumber = serialNumber.ToString(); } if (!string.IsNullOrEmpty(portInfo.PortName)) comPorts.Add(portInfo); } } finally { if (deviceInfoSet != IntPtr.Zero) SetupDiDestroyDeviceInfoList(deviceInfoSet); } return comPorts; } }
适配说明
- FTDI设备:从硬件ID的
+分隔段中提取序列号 - Silicon Labs设备:优先读取
SPDRP_SERIALNUMBER属性,该属性会直接返回手动设置的设备SN - 两种设备手动设置的SN都会直接反映在对应字段中,无需额外处理
内容的提问来源于stack exchange,提问作者mrkefca
相关产品推荐
相关产品推荐

