【问题标题】:How do I get the friendly name of a COM port in Windows?如何在 Windows 中获取 COM 端口的友好名称?
【发布时间】:2008-11-20 11:12:46
【问题描述】:

我有一个通过 USB 连接的 GSM 调制解调器。调制解调器创建 2 个串行端口。第一个自动连接到调制解调器,第二个在设备管理器中显示为“HUAWEI Mobile Connect - 3G PC UI Interface (COM6)”

第二个端口用于从调制解调器获取重要信息,例如信号质量;发送和接收短信;以及许多其他功能。

我正在编写一个应用程序,它将封装第二个端口提供的一些功能。我需要的是一种确定哪个 COM 端口是备用端口的可靠方法。迭代端口并检查对“ATE0”的响应是不够的。调制解调器的端口通常是编号较小的端口,当拨号连接不活动时,它将与第二个端口一样响应“ATE0”。

我想做的是迭代端口并检查它们的友好名称,如设备管理器中所示。这样我就可以将我的应用程序中的端口链接到设备管理器中标有“HUAWEI Mobile Connect - 3G PC UI Interface (COM6)”的端口。我还没有找到任何可以让我以编程方式获取该名称的信息。

【问题讨论】:

    标签: windows serial-port device-manager


    【解决方案1】:

    很久以前,我为客户端编写了一个实用程序来执行此操作,但用于 GPS 而不是调制解调器。

    我刚刚看过它,可能有帮助的部分是:

        GUID guid = GUID_DEVCLASS_PORTS;
    
    SP_DEVICE_INTERFACE_DATA interfaceData;
    ZeroMemory(&interfaceData, sizeof(interfaceData));
    interfaceData.cbSize = sizeof(interfaceData);
    
    SP_DEVINFO_DATA devInfoData;
    ZeroMemory(&devInfoData, sizeof(devInfoData));
    devInfoData.cbSize = sizeof(devInfoData);
    
    if(SetupDiEnumDeviceInfo(
        hDeviceInfo,            // Our device tree
        nDevice,            // The member to look for
        &devInfoData
        ))
    {
        DWORD regDataType;
    
        BYTE hardwareId[300];
        if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, hardwareId, sizeof(hardwareId), NULL))
        {
    ...
    

    (您在循环中调用该位并增加 nDevice)

    然后

    BYTE friendlyName[300];
            if(SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, NULL, friendlyName, sizeof(friendlyName), NULL))
            {
                strFriendlyNames += (LPCTSTR)friendlyName;
                strFriendlyNames += '\n';
            }
    

    查找设备的名称。

    希望这会帮助您朝着正确的方向前进。

    【讨论】:

    • 这看起来可能会奏效。我现在正在写一个测试。谢谢一百万:)
    【解决方案2】:

    确定串行端口设备是您想要的设备后(通过查看其友好名称、检查其父设备等),获取端口名称的正确方法可能是:

    • 调用SetupDiOpenDevRegKey(hDevInfo, devInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ)HKEY 获取到所谓的设备密钥
    • 在此注册表项中查询 REG_SZ 值“PortName”
    • 别忘了关闭HKEY :)

    但是,这可能需要在 C# 中进行如此多的互操作,这甚至都不好笑,所以如果你坚持字符串解析解决方案,我不会怪你。

    【讨论】:

    • 互操作很少。这对我来说似乎是正确的答案。在 C# 中工作就好了。
    【解决方案3】:

    Will Dean 发布的信息最有帮助。这是最终对我有用的代码。 PInvoke 类中的所有内容都是从 http://www.pinvoke.net 逐字提取的。我确实必须在这里或那里更改数据类型以使其工作(例如使用枚举而不是 uint 时),但应该很容易弄清楚。

    internal static string GetComPortByDescription(string Description)
    {
        string Result = string.Empty;
        Guid guid = PInvoke.GUID_DEVCLASS_PORTS;
        uint nDevice = 0;
        uint nBytes = 300;
        byte[] retval = new byte[nBytes];
        uint RequiredSize = 0;
        uint PropertyRegDataType = 0;
    
        PInvoke.SP_DEVINFO_DATA devInfoData = new PInvoke.SP_DEVINFO_DATA();
        devInfoData.cbSize = Marshal.SizeOf(typeof(PInvoke.SP_DEVINFO_DATA));
    
        IntPtr hDeviceInfo = PInvoke.SetupDiGetClassDevs(
            ref guid, 
            null, 
            IntPtr.Zero, 
            PInvoke.DIGCF.DIGCF_PRESENT);
    
        while (PInvoke.SetupDiEnumDeviceInfo(hDeviceInfo, nDevice++, ref devInfoData))
        {
            if (PInvoke.SetupDiGetDeviceRegistryProperty(
                    hDeviceInfo, 
                    ref devInfoData, 
                    PInvoke.SPDRP.SPDRP_FRIENDLYNAME,
                    out PropertyRegDataType, 
                    retval, 
                    nBytes, 
                    out RequiredSize))
            {
                if (System.Text.Encoding.Unicode.GetString(retval).Substring(0, Description.Length).ToLower() ==
                    Description.ToLower())
                {
                    string tmpstring = System.Text.Encoding.Unicode.GetString(retval);
                    Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM"));
                } // if retval == description
            } // if (PInvoke.SetupDiGetDeviceRegistryProperty( ... SPDRP_FRIENDLYNAME ...
        } // while (PInvoke.SetupDiEnumDeviceInfo(hDeviceInfo, nDevice++, ref devInfoData))
    
        PInvoke.SetupDiDestroyDeviceInfoList(hDeviceInfo);
        return Result;
    }
    

    我认为Result = tmpstring.Substring(tmpstring.IndexOf("COM"),tmpstring.IndexOf(')') - tmpstring.IndexOf("COM")); 行有点笨拙,不胜感激有关如何清理它的建议。

    感谢您对此事的帮助 Will,没有您,我仍然会在 google 上搜索。

    【讨论】:

      【解决方案4】:

      基于@Will Dean 回答的 C++ 版本。

      #include <windows.h>
      #include <initguid.h>
      #include <devguid.h>
      #include <setupapi.h>
      
      void enumerateSerialPortsFriendlyNames()
      {
          SP_DEVINFO_DATA devInfoData = {};
          devInfoData.cbSize = sizeof(devInfoData);
      
          // get the tree containing the info for the ports
          HDEVINFO hDeviceInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_PORTS,
                                                     0,
                                                     nullptr,
                                                     DIGCF_PRESENT
                                                     );
          if (hDeviceInfo == INVALID_HANDLE_VALUE)
          {
              return;
          }
      
          // iterate over all the devices in the tree
          int nDevice = 0;
          while (SetupDiEnumDeviceInfo(hDeviceInfo,            // Our device tree
                                       nDevice++,            // The member to look for
                                       &devInfoData))
          {
              DWORD regDataType;
              DWORD reqSize = 0;
      
              // find the size required to hold the device info
              SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, nullptr, nullptr, 0, &reqSize);
              BYTE* hardwareId = new BYTE[(reqSize > 1) ? reqSize : 1];
              // now store it in a buffer
              if (SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_HARDWAREID, &regDataType, hardwareId, sizeof(hardwareId) * reqSize, nullptr))
              {
                  // find the size required to hold the friendly name
                  reqSize = 0;
                  SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, nullptr, 0, &reqSize);
                  BYTE* friendlyName = new BYTE[(reqSize > 1) ? reqSize : 1];
                  // now store it in a buffer
                  if (!SetupDiGetDeviceRegistryProperty(hDeviceInfo, &devInfoData, SPDRP_FRIENDLYNAME, nullptr, friendlyName, sizeof(friendlyName) * reqSize, nullptr))
                  {
                      // device does not have this property set
                      memset(friendlyName, 0, reqSize > 1 ? reqSize : 1);
                  }
                  // use friendlyName here
                  delete[] friendlyName;
              }
              delete[] hardwareId;
          }
      }
      

      【讨论】:

      • “BYTE hardwareId[reqSize > 1 ? reqSize : 1];”--> “错误 C2131:表达式未计算为常数”。要解决它,请使用 --> BYTE* hardwareId = new BYTE[(reqSize > 1) ?请求大小:1];
      • @KamranBigdely 我做了编辑谢谢。代码依赖 VLA,在 Visual Studio 中不可用。
      • "sizeof(hardwareId)"--> 应该是 --> sizeof(hardwareId) * reqSize"。另外 "sizeof(friendlyName) * reqSize"--> 应该是 "sizeof(friendlyName) * reqSize"(这些更改应适用于 Visual Studio C++ 编译器)。
      【解决方案5】:

      很高兴它成功了。

      你可以试试:

      Regex.Match(tmpstring, @"COM\s\d+").ToString()

      为你的字符串匹配。

      作为 .NET 风格点,我会添加一个“使用 System.Text”,并且我不会以大写字母开头的局部变量名称,如果我觉得自己很善良,我可能会将 SetupDiDestroyDeviceInfoList 放在 finally {} 子句。

      【讨论】:

      • 正则表达式看起来更优雅。但是我必须更改一件事:“COM\s?\d+” COM 和数字之间并不总是有空格,因此它需要匹配 0 或更多。再次感谢。
      【解决方案6】:

      我为串行端口控制构建了一个库。它可以在注册表中搜索友好名称。这是链接。

      https://github.com/kcwongjoe/serial_port

      std::vector<SerialPortInfo> comPorts = SerialPort::getSerialPortList();
      std::cout << comPorts[0].friendlyName << std::endl;
      

      【讨论】:

        【解决方案7】:

        使用LiGenChen发布的方法。 ComPortSetupAPISetupDiClassGuids 方法给出了最好的时间和友好的名称。

        【讨论】:

        • 如果您使用链接中的更多相关信息修改您的答案将会很有帮助
        【解决方案8】:

        基于这里的答案组合是一个获取 COM 号、VID / PID 和友好名称等的解决方案。

        这里是一些获取连接设备列表的示例代码。

        public static class SerialPortUtils
        {
            private static Guid GUID_DEVCLASS_PORTS = new Guid(0x4d36e978u, 0xe325, 0x11ce, 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18);
        
            private unsafe static bool GetPortRegistryProperty(HDEVINFO classHandle, SP_DEVINFO_DATA* deviceInfo, uint spdrp, out string result)
            {
                DWORD size;
                SetupAPI.SetupDiGetDeviceRegistryPropertyW(classHandle, deviceInfo, spdrp, null, null, 0, &size);
                if (size == 0)
                {
                    result = null;
                    return false;
                }
        
                var resultBuffer = new byte[(int)size];
                fixed (byte* resultBufferPtr = resultBuffer)
                {
                    if (SetupAPI.SetupDiGetDeviceRegistryPropertyW(classHandle, deviceInfo, spdrp, null, resultBufferPtr, size, null))
                    {
                        result = Encoding.Unicode.GetString(resultBufferPtr, (int)size - sizeof(char));
                        return true;
                    }
                    else
                    {
                        result = null;
                        return false;
                    }
                }
            }
        
            public unsafe static List<SerialPortDeviceDesc> GetSerialPortDevices()
            {
                var results = new List<SerialPortDeviceDesc>();
        
                // get present ports handle
                var classHandle = SetupAPI.SetupDiGetClassDevsW(ref GUID_DEVCLASS_PORTS, null, IntPtr.Zero, SetupAPI.DIGCF_PRESENT);
                if (classHandle == Common.INVALID_HANDLE_VALUE || classHandle == HDEVINFO.Zero) throw new Exception("SetupDiGetClassDevsW failed");
        
                // enumerate all ports
                var deviceInfo = new SP_DEVINFO_DATA();
                uint deviceInfoSize = (uint)Marshal.SizeOf<SP_DEVINFO_DATA>();
                deviceInfo.cbSize = deviceInfoSize;
                uint index = 0;
                while (SetupAPI.SetupDiEnumDeviceInfo(classHandle, index, &deviceInfo))
                {
                    // get port name
                    string portName;
                    HKEY regKey = SetupAPI.SetupDiOpenDevRegKey(classHandle, &deviceInfo, SetupAPI.DICS_FLAG_GLOBAL, 0, SetupAPI.DIREG_DEV, WinNT.KEY_READ);
                    if (regKey == Common.INVALID_HANDLE_VALUE || regKey == IntPtr.Zero) continue;
                    using (var regHandle = new SafeRegistryHandle(regKey, true))
                    using (var key = RegistryKey.FromHandle(regHandle))
                    {
                        portName = key.GetValue("PortName") as string;
                        if (string.IsNullOrEmpty(portName)) continue;
                    }
        
                    // get registry values
                    if (!GetPortRegistryProperty(classHandle, &deviceInfo, SetupAPI.SPDRP_FRIENDLYNAME, out string friendlyName)) continue;
                    if (!GetPortRegistryProperty(classHandle, &deviceInfo, SetupAPI.SPDRP_HARDWAREID, out string hardwareID)) continue;
        
                    // add device
                    results.Add(new SerialPortDeviceDesc(friendlyName, portName, hardwareID));
        
                    // setup for next device
                    ++index;
                    deviceInfo = new SP_DEVINFO_DATA();
                    deviceInfo.cbSize = deviceInfoSize;
                }
        
                // finish
                SetupAPI.SetupDiDestroyDeviceInfoList(classHandle);
                return results;
            }
        }
        

        这里是 SerialPortDeviceDesc 类

        public enum SerialPortType
        {
            Unknown,
            COM
        }
        
        public class SerialPortDeviceDesc
        {
            public readonly string friendlyName, portName, hardwareID;
            public readonly string vid, pid;
            public readonly int portNumber = -1;
            public readonly SerialPortType portType = SerialPortType.Unknown;
        
            public SerialPortDeviceDesc(string friendlyName, string portName, string hardwareID)
            {
                this.friendlyName = friendlyName;
                this.portName = portName;
                this.hardwareID = hardwareID;
        
                if (portName.StartsWith("COM") && int.TryParse(portName.Substring("COM".Length), out portNumber))
                {
                    portType = SerialPortType.COM;
                }
                else
                {
                    portNumber = -1;
                }
        
                var rx = Regex.Match(hardwareID, @"VID_(\w*)&PID_(\w*)", RegexOptions.IgnoreCase);
                if (rx.Success)
                {
                    vid = rx.Groups[1].Value;
                    pid = rx.Groups[2].Value;
                }
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2019-06-18
          • 1970-01-01
          • 2012-03-11
          • 1970-01-01
          • 1970-01-01
          • 2023-03-16
          • 1970-01-01
          • 2012-08-23
          • 1970-01-01
          相关资源
          最近更新 更多