在 C# 中查找有关通过 USB 连接的所有串行设备的信息

Posted

技术标签:

【中文标题】在 C# 中查找有关通过 USB 连接的所有串行设备的信息【英文标题】:Finding information about all serial devices connected through USB in C# 【发布时间】:2012-07-12 14:47:46 【问题描述】:

我的项目需要在连接到 USB 时检测特定设备。我可以识别此设备的唯一方法是通过它的描述/设备名称,而不是 com 端口。我发现执行正确功能的是使用 WMI 查询并检查 name 属性:

ManagementObjectSearcher searcher = new ManagementObjectSearcher("Select * from WIN32_SerialPort");
            foreach (ManagementObject port in searcher.Get())
            
                deviceName = (string)foundPort.GetPropertyValue("Name"); 
                ...

我最初通过连接我的手机对此进行了测试,查询返回了在 COM3 上找到的手机,如预期的那样。然后,我连接了另一个设备(一个 USB 到串行转换器,它更类似于我需要这个项目的设备)并且查询根本没有找到它。它只会找到电话。但是,此设备确实显示在设备管理器中的端口 COM4 上。更让我恼火的是,SerialPort 类找到了这两个设备,但它没有提供识别设备所需的信息:

    string[] tempPorts = SerialPort.GetPortNames();

我已经阅读了关于 SO 和其他地方的大量主题,但找不到令人满意的解决方案。有人可以澄清为什么 WIN32_SerialPort 查询找不到我的其他设备吗?由于某种原因,它不被认为是win32串口吗? 而且,有人可以指出解决这个问题的方向吗?

【问题讨论】:

此线程***.com/questions/2548631/… 中的一条评论说WMI 查询不包括USB 到串行适配器。有人愿意详细说明吗? 您是否尝试过查看它是否检测到您是否将串行设备插入转换器? 我没有,但我的目标是使用被检测为 USB 转串口转换器的设备来简单地读取一个模拟引脚。 我找到了解决此问题的方法,方法是使用此处所述的 Win32_PnPEntity 查询thunderfist-podium.blogspot.com/2009/10/… @sebo 所描述的查询效率不是很高,因为查询了所有设备,然后使用了字符串比较。您只需要像我在回答中描述的那样使用特定的 guid。 【参考方案1】:

如何列出所有串口:

有几个System-Defined Device Setup Classes 可供硬件供应商使用。为COM-Ports 正确编写的驱动程序应使用Ports (COM & LPT ports) 类(guid:4d36e978-e325-11ce-bfc1-08002be10318)。可能这个类也被设备管理器使用。

因此您可以使用以下查询列出您在设备管理器中看到的每个串行端口:

ManagementObjectSearcher searcher = new ManagementObjectSearcher(
    "root\\CIMV2",
    "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"4d36e978-e325-11ce-bfc1-08002be10318\""
);
foreach (ManagementObject queryObj in searcher.Get())

    // do what you like with the Win32_PnpEntity

查看Win32_PnPEntity-class 的详细描述。您应该拥有识别设备所需的一切。

为了确定端口号,我检查了 name 属性并将其提取出来。到目前为止,这工作正常,但我不知道端口号是否保证包含在名称中。到目前为止,我还没有找到任何串口设备,它的名称中没有包含端口号。

上面的查询找到每一个串口设备,无论是蓝牙SPP、FTDI芯片、主板上的端口、扩展卡还是某些调制解调器驱动程序(即Globetrotter GTM66xxW)生成的虚拟串口.

要确定连接类型(蓝牙、USB 等),您可以检查 deviceid(查看 deviceid 的第一部分)。在那里您还可以提取 bt-mac 地址(请注意:至少在 Windows 7 和 Windows XP 上 deviceid 看起来不同)。

关于为什么有些设备没有用 Win32_SerialPort 列出:

我怀疑这取决于驱动程序的实现,因为我有一些 USB 设备会列出其端口,而另一些则没有。

【讨论】:

要获得列出的所有个设备,请改用此查询:"SELECT * FROM Win32_PnPEntity WHERE Name LIKE '%(COM[0-9]%'" @Pithikos 我已经在回答中提到了这一点,尽管我没有为此提供任何代码。由于我没有找到任何官方声明,即穷人号码将始终包含在名称中,因此我决定不使用LIKE 进行过滤。 确实如此。我想最好的解决方案是两者的结合。 @Pithikos 可能LIKE-条件会比过滤ClassGuid 慢,而LIKE-条件不是必需的。 如果您连接了 4 台相同的扫描仪,如果它们被用于 4 次操作,您如何分辨哪个是哪个(因为 Windows 经常无缘无故地重新分配这些虚拟 COM 端口)?【参考方案2】:

我想我明白你在做什么了,看看这个使用 WMICodeCreator 制作的代码(链接到 WMICodeCreator http://www.microsoft.com/en-us/download/details.aspx?id=8572) 来自这篇文章http://www.codeproject.com/Articles/32330/A-Useful-WMI-Tool-How-To-Find-USB-to-Serial-Adapto

//Below is code pasted from WMICodeCreator
try

    ManagementObjectSearcher searcher =
        new ManagementObjectSearcher("root\\WMI",
        "SELECT * FROM MSSerial_PortName");

    foreach (ManagementObject queryObj in searcher.Get())
    
        Console.WriteLine("-----------------------------------");
        Console.WriteLine("MSSerial_PortName instance");
        Console.WriteLine("-----------------------------------");
        Console.WriteLine("InstanceName: 0", queryObj["InstanceName"]);

        Console.WriteLine("-----------------------------------");
        Console.WriteLine("MSSerial_PortName instance");
        Console.WriteLine("-----------------------------------");
        Console.WriteLine("PortName: 0", queryObj["PortName"]);

        //If the serial port's instance name contains USB 
        //it must be a USB to serial device
        if (queryObj["InstanceName"].ToString().Contains("USB"))
        
            Console.WriteLine(queryObj["PortName"] + " 
            is a USB to SERIAL adapter/converter");
        
    

catch (ManagementException e)

    MessageBox.Show("An error occurred while querying for WMI data: " + e.Message);
 

【讨论】:

我之前遇到过这篇文章,但是使用此查询调用 Get() 失败并出现一些奇怪的错误。我想我得到了“不支持管理”之类的东西,然后在调整它之后我得到了“权限被拒绝”。正如我在上面的评论中所说,我已经找到了我的解决方案,我通过使用对 Win32_PnPEntity 的查询来做到这一点,然后通过查找名称属性包含“USB 串行端口 (COM”) 的实体来缩小搜索范围。【参考方案3】:

看到您要按“名称”搜索,我认为您需要遍历所有连接的设备并查询它们以获取产品名称。

下面是遍历 WinUSB 设备的代码:

https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Device.Net/Windows/WindowsDeviceFactoryBase.cs

  public async Task<IEnumerable<DeviceDefinition>> GetConnectedDeviceDefinitions(uint? vendorId, uint? productId)
    
        return await Task.Run<IEnumerable<DeviceDefinition>>(() =>
        
            var deviceDefinitions = new Collection<DeviceDefinition>();
            var spDeviceInterfaceData = new SpDeviceInterfaceData();
            var spDeviceInfoData = new SpDeviceInfoData();
            var spDeviceInterfaceDetailData = new SpDeviceInterfaceDetailData();
            spDeviceInterfaceData.CbSize = (uint)Marshal.SizeOf(spDeviceInterfaceData);
            spDeviceInfoData.CbSize = (uint)Marshal.SizeOf(spDeviceInfoData);

            var guidString = ClassGuid.ToString();
            var copyOfClassGuid = new Guid(guidString);

            var i = APICalls.SetupDiGetClassDevs(ref copyOfClassGuid, IntPtr.Zero, IntPtr.Zero, APICalls.DigcfDeviceinterface | APICalls.DigcfPresent);

            if (IntPtr.Size == 8)
            
                spDeviceInterfaceDetailData.CbSize = 8;
            
            else
            
                spDeviceInterfaceDetailData.CbSize = 4 + Marshal.SystemDefaultCharSize;
            

            var x = -1;

            while (true)
            
                x++;

                var isSuccess = APICalls.SetupDiEnumDeviceInterfaces(i, IntPtr.Zero, ref copyOfClassGuid, (uint)x, ref spDeviceInterfaceData);
                if (!isSuccess)
                
                    var errorCode = Marshal.GetLastWin32Error();
                    if (errorCode == APICalls.ERROR_NO_MORE_ITEMS)
                    
                        break;
                    

                    throw new Exception($"Could not enumerate devices. Error code: errorCode");
                

                isSuccess = APICalls.SetupDiGetDeviceInterfaceDetail(i, ref spDeviceInterfaceData, ref spDeviceInterfaceDetailData, 256, out _, ref spDeviceInfoData);
                WindowsDeviceBase.HandleError(isSuccess, "Could not get device interface detail");

                //Note this is a bit nasty but we can filter Vid and Pid this way I think...
                var vendorHex = vendorId?.ToString("X").ToLower().PadLeft(4, '0');
                var productIdHex = productId?.ToString("X").ToLower().PadLeft(4, '0');
                if (vendorId.HasValue && !spDeviceInterfaceDetailData.DevicePath.ToLower().Contains(vendorHex)) continue;
                if (productId.HasValue && !spDeviceInterfaceDetailData.DevicePath.ToLower().Contains(productIdHex)) continue;

                var deviceDefinition = GetDeviceDefinition(spDeviceInterfaceDetailData.DevicePath);

                deviceDefinitions.Add(deviceDefinition);
            

            APICalls.SetupDiDestroyDeviceInfoList(i);

            return deviceDefinitions;
        );
    

对于这些设备中的每一个,您都可以像这样查询设备:

https://github.com/MelbourneDeveloper/Device.Net/blob/master/src/Usb.Net/Windows/WindowsUsbDevice.cs

        var isSuccess = WinUsbApiCalls.WinUsb_Initialize(_DeviceHandle, out var defaultInterfaceHandle);
        HandleError(isSuccess, "Couldn't initialize device");

        var bufferLength = (uint)Marshal.SizeOf(typeof(USB_DEVICE_DESCRIPTOR));
        isSuccess = WinUsbApiCalls.WinUsb_GetDescriptor(defaultInterfaceHandle, WinUsbApiCalls.DEFAULT_DESCRIPTOR_TYPE, 0, EnglishLanguageID, out _UsbDeviceDescriptor, bufferLength, out var lengthTransferred);
        HandleError(isSuccess, "Couldn't get device descriptor");

        if (_UsbDeviceDescriptor.iProduct > 0)
        
            //Get the product name
            var buffer = new byte[256];
            isSuccess = WinUsbApiCalls.WinUsb_GetDescriptor(defaultInterfaceHandle, WinUsbApiCalls.USB_STRING_DESCRIPTOR_TYPE, _UsbDeviceDescriptor.iProduct, 1033, buffer, (uint)buffer.Length, out var transfered);
            HandleError(isSuccess, "Couldn't get product name");

            Product = new string(Encoding.Unicode.GetChars(buffer, 2, (int)transfered));
            Product = Product.Substring(0, Product.Length - 1);
        


public static partial class WinUsbApiCalls

    public const uint DEVICE_SPEED = 1;
    public const byte USB_ENDPOINT_DIRECTION_MASK = 0X80;
    public const int WritePipeId = 0x80;

    /// <summary>
    /// Not sure where this constant is defined...
    /// </summary>
    public const int DEFAULT_DESCRIPTOR_TYPE = 0x01;
    public const int USB_STRING_DESCRIPTOR_TYPE = 0x03;

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ControlTransfer(IntPtr InterfaceHandle, WINUSB_SETUP_PACKET SetupPacket, byte[] Buffer, uint BufferLength, ref uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern bool WinUsb_GetAssociatedInterface(SafeFileHandle InterfaceHandle, byte AssociatedInterfaceIndex, out SafeFileHandle AssociatedInterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, ushort LanguageID, out USB_DEVICE_DESCRIPTOR deviceDesc, uint BufferLength, out uint LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_GetDescriptor(SafeFileHandle InterfaceHandle, byte DescriptorType, byte Index, UInt16 LanguageID, byte[] Buffer, UInt32 BufferLength, out UInt32 LengthTransfered);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Free(SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_Initialize(SafeFileHandle DeviceHandle, out SafeFileHandle InterfaceHandle);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryDeviceInformation(IntPtr InterfaceHandle, uint InformationType, ref uint BufferLength, ref byte Buffer);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryInterfaceSettings(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, out USB_INTERFACE_DESCRIPTOR UsbAltInterfaceDescriptor);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_QueryPipe(SafeFileHandle InterfaceHandle, byte AlternateInterfaceNumber, byte PipeIndex, out WINUSB_PIPE_INFORMATION PipeInformation);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_ReadPipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_SetPipePolicy(IntPtr InterfaceHandle, byte PipeID, uint PolicyType, uint ValueLength, ref uint Value);

    [DllImport("winusb.dll", SetLastError = true)]
    public static extern bool WinUsb_WritePipe(SafeFileHandle InterfaceHandle, byte PipeID, byte[] Buffer, uint BufferLength, out uint LengthTransferred, IntPtr Overlapped);

【讨论】:

【参考方案4】:

Alex 的回答真的帮了我大忙。但我不得不根据我的机器调整我的查询......

我必须查询“root\CIMV2”,但我必须从不同的表中选择所有行:“SELECT * FROM Win32_SerialPort”。

对于任何努力弄清楚如何使用 WMI 或管理对象搜索器的人来说,Microsoft 的 WMI Code Creator 真的救了我。使用该应用程序,您可以查询 WMI 以获取不同的信息,以确定将哪些内容放入管理对象搜索器查询中。

链接可能会在未来过期,但它是在 2020 年:

https://www.microsoft.com/en-us/download/details.aspx?id=8572

【讨论】:

以上是关于在 C# 中查找有关通过 USB 连接的所有串行设备的信息的主要内容,如果未能解决你的问题,请参考以下文章

C# COM 端口通信 - USB 到串行适配器的错误

如何在 Mac OS 上查找用于串行读取的 USB 设备的 /dev/ 名称?

Arduino:禁用USB时传入的USB串行数据软管连接

无法通过 USB 串行 python 库与数字百分表通信

Linux 上的串行通信连接

如何确定正在使用哪个 USB 端口?