如何通过友好名称打开串口?

友好名称=在“设备pipe理器”(Ports(COM&LPT))下出现的名称。

编辑:下面提供了两个解决scheme。 一个与WMI和另一个与SetupAPI。

尝试在Win32_SerialPort类上运行WMI查询。 下载WmiCodeCreator来实验和自动生成C#代码。

发布今晚的代码,为大家的享受:

 public class SetupDiWrap { static public string ComPortNameFromFriendlyNamePrefix(string friendlyNamePrefix) { const string className = "Ports"; Guid[] guids = GetClassGUIDs(className); System.Text.RegularExpressions.Regex friendlyNameToComPort = new System.Text.RegularExpressions.Regex(@".? \((COM\d+)\)$"); // "..... (COMxxx)" -> COMxxxx foreach (Guid guid in guids) { // We start at the "root" of the device tree and look for all // devices that match the interface GUID of a disk Guid guidClone = guid; IntPtr h = SetupDiGetClassDevs(ref guidClone, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_PROFILE); if (h.ToInt32() != INVALID_HANDLE_VALUE) { int nDevice = 0; while (true) { SP_DEVINFO_DATA da = new SP_DEVINFO_DATA(); da.cbSize = (uint)Marshal.SizeOf(da); if (0 == SetupDiEnumDeviceInfo(h, nDevice++, ref da)) break; uint RegType; byte[] ptrBuf = new byte[BUFFER_SIZE]; uint RequiredSize; if (SetupDiGetDeviceRegistryProperty(h, ref da, (uint)SPDRP.FRIENDLYNAME, out RegType, ptrBuf, BUFFER_SIZE, out RequiredSize)) { const int utf16terminatorSize_bytes = 2; string friendlyName = System.Text.UnicodeEncoding.Unicode.GetString(ptrBuf, 0, (int)RequiredSize - utf16terminatorSize_bytes); if (!friendlyName.StartsWith(friendlyNamePrefix)) continue; if (!friendlyNameToComPort.IsMatch(friendlyName)) continue; return friendlyNameToComPort.Match(friendlyName).Groups[1].Value; } } // devices SetupDiDestroyDeviceInfoList(h); } } // class guids return null; } /// <summary> /// The SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set. /// </summary> [StructLayout(LayoutKind.Sequential)] private struct SP_DEVINFO_DATA { /// <summary>Size of the structure, in bytes.</summary> public uint cbSize; /// <summary>GUID of the device interface class.</summary> public Guid ClassGuid; /// <summary>Handle to this device instance.</summary> public uint DevInst; /// <summary>Reserved; do not use.</summary> public uint Reserved; } [StructLayout(LayoutKind.Sequential)] private struct SP_DEVICE_INTERFACE_DATA { public Int32 cbSize; public Guid interfaceClassGuid; public Int32 flags; private UIntPtr reserved; } const int BUFFER_SIZE = 1024; [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] private struct SP_DEVICE_INTERFACE_DETAIL_DATA { public int cbSize; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = BUFFER_SIZE)] public string DevicePath; } private enum SPDRP { DEVICEDESC = 0x00000000, HARDWAREID = 0x00000001, COMPATIBLEIDS = 0x00000002, NTDEVICEPATHS = 0x00000003, SERVICE = 0x00000004, CONFIGURATION = 0x00000005, CONFIGURATIONVECTOR = 0x00000006, CLASS = 0x00000007, CLASSGUID = 0x00000008, DRIVER = 0x00000009, CONFIGFLAGS = 0x0000000A, MFG = 0x0000000B, FRIENDLYNAME = 0x0000000C, LOCATION_INFORMATION = 0x0000000D, PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E, CAPABILITIES = 0x0000000F, UI_NUMBER = 0x00000010, UPPERFILTERS = 0x00000011, LOWERFILTERS = 0x00000012, MAXIMUM_PROPERTY = 0x00000013, } [DllImport("setupapi.dll", SetLastError = true)] static extern bool SetupDiClassGuidsFromName(string ClassName, ref Guid ClassGuidArray1stItem, UInt32 ClassGuidArraySize, out UInt32 RequiredSize); [DllImport("setupapi.dll")] internal static extern IntPtr SetupDiGetClassDevsEx(IntPtr ClassGuid, [MarshalAs(UnmanagedType.LPStr)]String enumerator, IntPtr hwndParent, Int32 Flags, IntPtr DeviceInfoSet, [MarshalAs(UnmanagedType.LPStr)]String MachineName, IntPtr Reserved); [DllImport("setupapi.dll")] internal static extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern Boolean SetupDiEnumDeviceInterfaces( IntPtr hDevInfo, IntPtr optionalCrap, //ref SP_DEVINFO_DATA devInfo, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData ); [DllImport("setupapi.dll")] private static extern Int32 SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, Int32 MemberIndex, ref SP_DEVINFO_DATA DeviceInterfaceData); [DllImport("setupapi.dll")] private static extern Int32 SetupDiClassNameFromGuid(ref Guid ClassGuid, StringBuilder className, Int32 ClassNameSize, ref Int32 RequiredSize); [DllImport("setupapi.dll")] private static extern Int32 SetupDiGetClassDescription(ref Guid ClassGuid, StringBuilder classDescription, Int32 ClassDescriptionSize, ref Int32 RequiredSize); [DllImport("setupapi.dll")] private static extern Int32 SetupDiGetDeviceInstanceId(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, StringBuilder DeviceInstanceId, Int32 DeviceInstanceIdSize, ref Int32 RequiredSize); [DllImport("setupapi.dll", CharSet = CharSet.Auto)] static extern IntPtr SetupDiGetClassDevs( // 1st form using a ClassGUID only, with null Enumerator ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags ); [DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern Boolean SetupDiGetDeviceInterfaceDetail( IntPtr hDevInfo, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData, UInt32 deviceInterfaceDetailDataSize, out UInt32 requiredSize, ref SP_DEVINFO_DATA deviceInfoData ); /// <summary> /// The SetupDiGetDeviceRegistryProperty function retrieves the specified device property. /// This handle is typically returned by the SetupDiGetClassDevs or SetupDiGetClassDevsEx function. /// </summary> /// <param Name="DeviceInfoSet">Handle to the device information set that contains the interface and its underlying device.</param> /// <param Name="DeviceInfoData">Pointer to an SP_DEVINFO_DATA structure that defines the device instance.</param> /// <param Name="Property">Device property to be retrieved. SEE MSDN</param> /// <param Name="PropertyRegDataType">Pointer to a variable that receives the registry data Type. This parameter can be NULL.</param> /// <param Name="PropertyBuffer">Pointer to a buffer that receives the requested device property.</param> /// <param Name="PropertyBufferSize">Size of the buffer, in bytes.</param> /// <param Name="RequiredSize">Pointer to a variable that receives the required buffer size, in bytes. This parameter can be NULL.</param> /// <returns>If the function succeeds, the return value is nonzero.</returns> [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool SetupDiGetDeviceRegistryProperty( IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, uint Property, out UInt32 PropertyRegDataType, byte[] PropertyBuffer, uint PropertyBufferSize, out UInt32 RequiredSize); const int DIGCF_DEFAULT = 0x1; const int DIGCF_PRESENT = 0x2; const int DIGCF_ALLCLASSES = 0x4; const int DIGCF_PROFILE = 0x8; const int DIGCF_DEVICEINTERFACE = 0x10; const int INVALID_HANDLE_VALUE = -1; private static Guid[] GetClassGUIDs(string className) { UInt32 requiredSize = 0; Guid[] guidArray = new Guid[1]; bool status = SetupDiClassGuidsFromName(className, ref guidArray[0], 1, out requiredSize); if (true == status) { if (1 < requiredSize) { guidArray = new Guid[requiredSize]; SetupDiClassGuidsFromName(className, ref guidArray[0], requiredSize, out requiredSize); } } else throw new System.ComponentModel.Win32Exception(); return guidArray; } } 

这篇文章的代码为我做了这份工作(他链接到这个post,但似乎没有提供一个答案在这里自己)。 这是作者的代码:

 using System.Management; internal class ProcessConnection { public static ConnectionOptions ProcessConnectionOptions() { ConnectionOptions options = new ConnectionOptions(); options.Impersonation = ImpersonationLevel.Impersonate; options.Authentication = AuthenticationLevel.Default; options.EnablePrivileges = true; return options; } public static ManagementScope ConnectionScope(string machineName, ConnectionOptions options, string path) { ManagementScope connectScope = new ManagementScope(); connectScope.Path = new ManagementPath(@"\\" + machineName + path); connectScope.Options = options; connectScope.Connect(); return connectScope; } } public class COMPortInfo { public string Name { get; set; } public string Description { get; set; } public COMPortInfo() { } public static List<COMPortInfo> GetCOMPortsInfo() { List<COMPortInfo> comPortInfoList = new List<COMPortInfo>(); ConnectionOptions options = ProcessConnection.ProcessConnectionOptions(); ManagementScope connectionScope = ProcessConnection.ConnectionScope(Environment.MachineName, options, @"\root\CIMV2"); ObjectQuery objectQuery = new ObjectQuery("SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0"); ManagementObjectSearcher comPortSearcher = new ManagementObjectSearcher(connectionScope, objectQuery); using (comPortSearcher) { string caption = null; foreach (ManagementObject obj in comPortSearcher.Get()) { if (obj != null) { object captionObj = obj["Caption"]; if (captionObj != null) { caption = captionObj.ToString(); if (caption.Contains("(COM")) { COMPortInfo comPortInfo = new COMPortInfo(); comPortInfo.Name = caption.Substring(caption.LastIndexOf("(COM")).Replace("(", string.Empty).Replace(")", string.Empty); comPortInfo.Description = caption; comPortInfoList.Add(comPortInfo); } } } } } return comPortInfoList; } } 

用法:

 foreach (COMPortInfo comPort in COMPortInfo.GetCOMPortsInfo()) { Console.WriteLine(string.Format("{0} – {1}", comPort.Name, comPort.Description)); } 

我知道这是张贴在C#,但我相信这可以很容易地转换…

  Public Function foo() As Integer Try Dim searcher As New ManagementObjectSearcher( _ "root\CIMV2", _ "SELECT * FROM Win32_SerialPort") For Each queryObj As ManagementObject In searcher.Get() Debug.WriteLine(queryObj("Caption")) Debug.WriteLine(queryObj("Description")) Debug.WriteLine(queryObj("DeviceID")) Debug.WriteLine(queryObj("Name")) Debug.WriteLine(queryObj("PNPDeviceID")) Next Catch err As ManagementException Stop End Try End Function Public Function bar() As Integer Try Dim searcher As New ManagementObjectSearcher( _ "root\CIMV2", _ "SELECT * FROM Win32_PnPEntity WHERE ConfigManagerErrorCode = 0") For Each queryObj As ManagementObject In searcher.Get() If queryObj("Caption").ToString.Contains("(COM") Then Debug.WriteLine(queryObj("Caption")) Debug.WriteLine(queryObj("Description")) Debug.WriteLine(queryObj("DeviceID")) Debug.WriteLine(queryObj("Name")) Debug.WriteLine(queryObj("PNPDeviceID")) End If Next Catch err As ManagementException Stop End Try End Function 

它find我所有的COM端口,调制解调器,串行,USB和蓝牙。

Pavel的类SetupDiWrap工作得很好,只需要对Windows 7进行一些小的调整。

希望这个更新能够帮助其他人挣扎(像我一样)从Windows 7的VCP名称中获取COM端口号。

1)在Windows 7中SP_DEVINFO_DATA已更改,总长度不再是28个字节,长度为32个字节。 这对我有用:

  private struct SP_DEVINFO_DATA { /// <summary>Size of the structure, in bytes.</summary> public int cbSize; /// <summary>GUID of the device interface class.</summary> public Guid ClassGuid; /// <summary>Handle to this device instance.</summary> public int DevInst; /// <summary>Reserved; do not use.</summary> public ulong Reserved; } 

注意ulong保留而不是int。 将uint cbSize更改为int cbSize稍后保存了我,否则可以将其保留为uint。

2)我也写了一行:

  da.cbSize = (uint)Marshal.SizeOf(da); 

有一点不同,为了清楚起见,将cbSize取为32位:

 da.cbSize = Marshal.SizeOf(typeof(SP_DEVINFO_DATA)); 

3)我改变了

  [DllImport("setupapi.dll", CharSet = CharSet.Auto)] static extern IntPtr SetupDiGetClassDevs( ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags ); 

  [DllImport("setupapi.dll", CharSet = CharSet.Auto)] private static extern IntPtr SetupDiGetClassDevs( ref Guid ClassGuid, UInt32 Enumerator, IntPtr hwndParent, UInt32 Flags ); 

枚举器不再是一个IntPtr ,所以你需要像这样调用SetupDiGetClassDevs

 IntPtr h = SetupDiGetClassDevs(ref guidClone, 0, IntPtr.Zero, DIGCF_PRESENT | DIGCF_PROFILE); 

通过枚举IntPtr.Zero时注意“0”而不是IntPtr.Zero

现在代码在Windows 7中运行得像一个魅力!

你可能也想考虑使用registry,因为我发现WMI很慢(5或6秒)

在我的情况下,我想识别具有已知友好名称的设备的COM端口名称。 使用registry我searchregistry中的友好名称在一个密钥也包含COM端口。 因为我find的钥匙是某种随机ID和其他10个随机ID,我上了几个关卡find一个适合在里面search的钥匙。

我提出的代码如下:

 Dim searchFriendlyName = "Your Device Name".ToLower Dim k0 = Registry.LocalMachine.OpenSubKey("SYSTEM\CurrentControlSet\Enum\USB\", False) For Each k1Name In k0.GetSubKeyNames Dim k1 = k0.OpenSubKey(k1Name, False) For Each k2name In k1.GetSubKeyNames Dim k2 = k1.OpenSubKey(k2name, False) If k2.GetValueNames.Contains("FriendlyName") AndAlso k2.GetValue("FriendlyName").ToString.ToLower.Contains(searchFriendlyName) Then If k2.GetSubKeyNames.Contains("Device Parameters") Then Dim k3 = k2.OpenSubKey("Device Parameters", False) If k3.GetValueNames.Contains("PortName") Then For Each s In SerialPort.GetPortNames If k3.GetValue("PortName").ToString.ToLower = s.ToLower Then Return s End If Next End If End If End If Next Next 

这当然需要修改,这取决于你的设备在registry中的显示方式和位置,但是如果你试图“自动检测”特定types设备的Com端口,那么你应该能够做到这一点。

请记住,如果您必须recursionsearch大量的密钥,那么这将减慢上述解决scheme,所以请尝试在registry中find合适的位置。

我还在registrysearch后join了WMI代码,以防registrysearch变为空白:

 Dim mg As New System.Management.ManagementClass("Win32_SerialPort") Try For Each i In mg.GetInstances() Dim name = i.GetPropertyValue("Name") If name IsNot Nothing AndAlso name.ToString.ToLower.Contains(searchFriendlyName.ToLower) Then Return i.GetPropertyValue("DeviceId").ToString End If Next Finally mg.Dispose() End Try 

如果您是专门使用USB设备而不是某种其他types的COM端口,则可以运行跨平台USB库libusbp的一个示例 ,该示例显示如何根据COM的USB产品ID和供应商ID来查找COM端口名称港口。

这是与尝试在设备pipe理器中使用友好名称不同但可能更好的select。 也许libusbp可以扩展为允许访问友好名称,如果你真的想要的话。

我使用com0com生成的虚拟端口。 因此,您可以根据需要命名端口 – 所以我的端口标题如下所示: "com0com - bus for serial port pair emulator 0 (COMA <-> COMB)"

通过上面发布的WMI代码,您将获得该端口的名称 "COMA <-> COMB" 。 看来这样的结构是没有端口,但上面的代码将它视为串行端口…

顺便说一句, "COMA"是一个完全有效的港口名称…(这意味着,只在最后只查找数字是不够的)。

所以我想知道如何可靠地区分一个有效的,现有的串行端口名称和这种奇怪的结构…