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

如何通过WMI获取搭载CH340芯片的Arduino的COM端口?

问题场景

在Windows 10系统下使用C#开发串口设备检测功能,基于System.Management通过WMI实现:

  • 初始采用Win32_SerialPort类,查询语句:
    SELECT DeviceID, PNPDeviceID FROM Win32_SerialPort
    
    同时通过ManagementEventWatcher监听设备连接/断开事件,代码如下:
    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);
    }
    
    该方案可识别原生Arduino及自带USB串口的PIC设备,但无法识别搭载CH340芯片的Arduino克隆板(已装驱动,Arduino IDE中显示为COM6)。
  • 将查询对象替换为Win32_PnPEntity后,查询语句改为:
    SELECT DeviceID, PNPDeviceID FROM Win32_PnPEntity
    
    可检测到CH340设备的连接/断开事件,但DeviceID返回硬件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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 03:13:15