Come aprire una porta seriale con un nome descrittivo?

nome descrittivo = il nome che appare in “Gestione periferiche” sotto “Porte (COM e LPT).

EDIT: due soluzioni fornite di seguito. Uno con WMI e un altro con SetupAPI.

Prova a eseguire una query WMI sulla class Win32_SerialPort. Scarica WmiCodeCreator per sperimentare e generare automaticamente il codice C #.

Pubblicare il codice di stasera, per il divertimento di tutti:

 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; } ///  /// The SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set. ///  [StructLayout(LayoutKind.Sequential)] private struct SP_DEVINFO_DATA { /// Size of the structure, in bytes. public uint cbSize; /// GUID of the device interface class. public Guid ClassGuid; /// Handle to this device instance. public uint DevInst; /// Reserved; do not use. 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 ); ///  /// The SetupDiGetDeviceRegistryProperty function retrieves the specified device property. /// This handle is typically returned by the SetupDiGetClassDevs or SetupDiGetClassDevsEx function. ///  /// Handle to the device information set that contains the interface and its underlying device. /// Pointer to an SP_DEVINFO_DATA structure that defines the device instance. /// Device property to be retrieved. SEE MSDN /// Pointer to a variable that receives the registry data Type. This parameter can be NULL. /// Pointer to a buffer that receives the requested device property. /// Size of the buffer, in bytes. /// Pointer to a variable that receives the required buffer size, in bytes. This parameter can be NULL. /// If the function succeeds, the return value is nonzero. [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; } } 

Il codice di questo articolo ha fatto il lavoro per me (si collega a questo post, ma non sembra aver fornito una risposta qui da solo). Ecco il codice dell’autore:

 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 GetCOMPortsInfo() { List comPortInfoList = new List(); 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; } } 

Uso:

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

So che questo è stato pubblicato in C #, ma sono certo che questo può essere facilmente convertito …

  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 

Trova tutte le mie porte com, modem, seriale, usb e bluetooth.

La class di Pavel SetupDiWrap funziona alla grande, ha solo bisogno di alcune piccole modifiche per Windows 7.

Speriamo che questo aggiornamento possa aiutare altre persone che stanno lottando (come me) per ottenere numeri di porta COM dai nomi VCP in Windows 7.

1) SP_DEVINFO_DATA è cambiato in Windows 7, la lunghezza totale non è più 28 byte, è lunga 32 byte. Questo è ciò che funziona per me:

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

Nota a lungo per Riservato invece di int. Cambiando uint cbSize in int cbSize mi ha salvato un cast in seguito, altrimenti puoi lasciarlo come uint.

2) Ho anche scritto la riga:

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

un po ‘diverso, per chiarezza, per ottenere cbSize a 32 bit:

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

3) Ho cambiato

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

a

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

L’Enumeratore non è più un IntPtr , quindi è necessario chiamare SetupDiGetClassDevs questo modo:

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

Nota “0” invece di IntPtr.Zero quando si passa l’Enumeratore.

Il codice ora funziona come un fascino in Windows 7!

Si potrebbe anche prendere in considerazione l’utilizzo del registro in quanto ho trovato WMI abbastanza lento (5 o 6 secondi)

Nel mio caso volevo identificare il nome della porta COM di un dispositivo con un nome descrittivo noto. Usando regedit ho cercato nel registro il nome descrittivo in una chiave che conteneva anche la porta COM. Perché la chiave che ho trovato era una sorta di ID casuale insieme ad altri 10 sono andato su alcuni livelli per trovare una chiave adatta a cercare all’interno.

Il codice che ho trovato è il seguente:

 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 

Ciò ovviamente dovrà essere modificato a seconda di come e dove si trova il tuo dispositivo nel registro, ma se stai cercando di “Rilevare automaticamente” la porta Com di un tipo specifico di risorse, dovresti riuscire a farlo funzionare.

Ricorda che se devi ricorsivamente cercare un sacco di chiavi, allora questo rallenterà la soluzione precedente, quindi cerca di trovare il posto giusto nel registro per cercare.

Ho anche incluso il codice WMI dopo la ricerca nel registro nel caso in cui la ricerca del registro si presentasse vuota:

 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 

Se si utilizzano specificamente dispositivi USB e non un altro tipo di porta COM, la libreria USB multipiattaforma libusbp ha un esempio che è ansible eseguire che mostra come trovare il nome della porta COM in base all’ID del prodotto USB e all’ID del fornitore del COM porta.

Questa è una scelta diversa ma probabilmente migliore rispetto al tentativo di utilizzare il nome descrittivo nel Device Manager. Forse libusbp potrebbe essere esteso per consentire l’accesso al nome descrittivo se lo vuoi davvero.

Io uso le porte virtuali generate da com0com . Con questo puoi nominare le porte come vuoi – quindi ho le didascalie delle porte come segue: "com0com - bus for serial port pair emulator 0 (COMA <-> COMB)"

Con il codice WMI sopra postato otterrai il nome "COMA <-> COMB" per quella porta. Sembra che tali costrutti non siano porte, ma il codice precedente lo tratterà come porta seriale …

A proposito, "COMA" è un nome totalmente valido per una porta … (ciò significa che cercare solo numeri alla fine non è sufficiente).

Quindi mi chiedo come sia ansible distinguere in modo affidabile tra un valido nome di porta seriale esistente e tali strani costrutti …