如何通过WMI获取搭载CH340芯片的Arduino的COM端口?
问题场景
在Windows 10系统下使用C#开发串口设备检测功能,基于System.Management通过WMI实现:
- 初始采用
Win32_SerialPort类,查询语句:
同时通过SELECT DeviceID, PNPDeviceID FROM Win32_SerialPortManagementEventWatcher监听设备连接/断开事件,代码如下:
该方案可识别原生Arduino及自带USB串口的PIC设备,但无法识别搭载CH340芯片的Arduino克隆板(已装驱动,Arduino IDE中显示为COM6)。private ManagementEventWatcher USBWatcherSetUp(string eventType) { _watcherQuery = new WqlEventQuery(); _watcherQuery.EventClassName = eventType; _watcherQuery.WithinInterval = new TimeSpan(0, 0, 2); _watcherQuery.Condition = @"TargetInstance ISA 'Win32_SerialPort'"; return new ManagementEventWatcher(_scope, _watcherQuery); } - 将查询对象替换为
Win32_PnPEntity后,查询语句改为:
可检测到CH340设备的连接/断开事件,但SELECT DeviceID, PNPDeviceID FROM Win32_PnPEntityDeviceID返回硬件ID(如USB\VID_2341&PID_0042\8554856875F132),无法直接获取COM端口号。现有代码通过CreatePortArgs从PNPDeviceID提取VID、PID和序列号,但依赖DeviceID获取COM口的逻辑失效:private void HandlePortAdded(object sender, EventArrivedEventArgs e) { var instance = e.NewEvent.GetPropertyValue("TargetInstance") as ManagementBaseObject; SerialPortEventArgs serialPortEventArgs = CreatePortArgs(instance); if (CheckIDMatch(serialPortEventArgs)) { OnPortAddedEvent?.Invoke(this, serialPortEventArgs); } }private SerialPortEventArgs CreatePortArgs(ManagementBaseObject queryObj) { string PNPDeviceID = ((string)queryObj.GetPropertyValue("PNPDeviceID")).ToUpper(); int vid = 0; int pid = 0; string serialNumber = ""; int index = PNPDeviceID.IndexOf("VID_"); if (index > -1 && PNPDeviceID.Length >= index + 8) { string id = PNPDeviceID.Substring(index + 4, 4); vid = Convert.ToInt32(id, 16); } index = PNPDeviceID.IndexOf("PID_"); if (index > -1 && PNPDeviceID.Length >= index + 8) { string id = PNPDeviceID.Substring(index + 4, 4); pid = Convert.ToInt32(id, 16); } index += 9; if (PNPDeviceID.Length > index) serialNumber = PNPDeviceID.Substring(index); return new SerialPortEventArgs((string)queryObj["DeviceID"], vid, pid, serialNumber, PNPDeviceID); }
需求:通过WMI实现从PNPDeviceID转换为对应COM端口号,避免使用注册表未公开特性。
解决方案
方法1:通过PNPDeviceID关联查询Win32_SerialPort
当从Win32_PnPEntity获取到设备的PNPDeviceID后,执行WMI查询匹配Win32_SerialPort中对应的条目,从而获取COM端口号。
修改CreatePortArgs方法,添加查询逻辑:
private SerialPortEventArgs CreatePortArgs(ManagementBaseObject queryObj) { string PNPDeviceID = ((string)queryObj.GetPropertyValue("PNPDeviceID")).ToUpper(); int vid = 0; int pid = 0; string serialNumber = ""; string comPort = string.Empty; // 提取VID、PID、序列号逻辑不变 int index = PNPDeviceID.IndexOf("VID_"); if (index > -1 && PNPDeviceID.Length >= index + 8) { string id = PNPDeviceID.Substring(index + 4, 4); vid = Convert.ToInt32(id, 16); } index = PNPDeviceID.IndexOf("PID_"); if (index > -1 && PNPDeviceID.Length >= index + 8) { string id = PNPDeviceID.Substring(index + 4, 4); pid = Convert.ToInt32(id, 16); } index += 9; if (PNPDeviceID.Length > index) serialNumber = PNPDeviceID.Substring(index); // 通过PNPDeviceID查询Win32_SerialPort获取COM端口 using (var searcher = new ManagementObjectSearcher(_scope, new ObjectQuery($"SELECT DeviceID FROM Win32_SerialPort WHERE PNPDeviceID = '{PNPDeviceID}'"))) { foreach (var obj in searcher.Get()) { comPort = obj["DeviceID"]?.ToString(); break; // 通常一个PNPDeviceID对应一个COM口 } } // 如果Win32_SerialPort没找到,尝试从Win32_PnPEntity的Name属性提取COM号 if (string.IsNullOrEmpty(comPort)) { string deviceName = queryObj["Name"]?.ToString(); if (!string.IsNullOrEmpty(deviceName)) { var match = System.Text.RegularExpressions.Regex.Match(deviceName, @"\(COM(\d+)\)"); if (match.Success) { comPort = $"COM{match.Groups[1].Value}"; } } } return new SerialPortEventArgs(comPort, vid, pid, serialNumber, PNPDeviceID); }
方法2:优化事件监听的过滤条件
修改事件监听的查询条件,只关注串口相关的Win32_PnPEntity设备,减少无效事件触发:
private ManagementEventWatcher USBWatcherSetUp(string eventType) { _watcherQuery = new WqlEventQuery(); _watcherQuery.EventClassName = eventType; _watcherQuery.WithinInterval = new TimeSpan(0, 0, 2); // 过滤串口设备(Name包含COM标识) _watcherQuery.Condition = @"TargetInstance ISA 'Win32_PnPEntity' AND TargetInstance.Name LIKE '%COM%'"; return new ManagementEventWatcher(_scope, _watcherQuery); }
之后在HandlePortAdded中调用修改后的CreatePortArgs方法即可。
注意事项
- 确保
_scope使用正确的WMI命名空间,通常为new ManagementScope(@"\\.\root\cimv2")。 - 使用
using语句包裹ManagementObjectSearcher,避免WMI资源泄漏。 - 部分特殊驱动设备可能无法在
Win32_SerialPort中找到记录,此时从Name属性提取COM号是可靠的备用方案。
内容的提问来源于stack exchange,提问作者Hans Billiet
相关产品推荐
相关产品推荐

