.net 检测串口插拔

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4199083/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 14:56:08  来源:igfitidea点击:

Detect serial port insertion/removal

.netwmiserial-port

提问by Pat

I am interfacing with a USB-to-serial port that can be inserted or removed at any time. I've found that I can use WMI(particularly with the use of WMI Code Creator) to query for device changes in the PC.

我正在连接一个可以随时插入或移除的 USB 转串口。我发现我可以使用WMI(特别是使用WMI Code Creator)来查询 PC 中的设备更改。

In the generated snippet below, the Win32_DeviceChangeEventis subscribed to. However, this event doesn't reveal which device(e.g. USB, serial port, etc) caused the event. Is there a way to only receive notifications when serial ports are inserted or removed?

在下面生成的代码段中,订阅了Win32_DeviceChangeEvent。但是,此事件不会显示是哪个设备(例如 USB、串行端口等)导致了该事件。有没有办法只在插入或移除串行端口时接收通知?

To clarify, the point of the code is notto detect opening/closingof serial ports, it is to detect whether a newport has been added to the machine or a previous port was removed.

澄清一下,代码的重点不是检测串行端口的打开/关闭,而是检测是否已将端口添加到机器或先前的端口已被删除

using System;
using System.Management;
using System.Windows.Forms;

namespace WMISample
{
    public class WMIReceiveEvent
    {
        public WMIReceiveEvent()
        {
            try
            {
                WqlEventQuery query = new WqlEventQuery(
                    "SELECT * FROM Win32_DeviceChangeEvent");

                ManagementEventWatcher watcher = new ManagementEventWatcher(query);
                Console.WriteLine("Waiting for an event...");

                watcher.EventArrived += 
                    new EventArrivedEventHandler(
                    HandleEvent);

                // Start listening for events
                watcher.Start();

                // Do something while waiting for events
                System.Threading.Thread.Sleep(10000);

                // Stop listening for events
                watcher.Stop();
                return;
            }
            catch(ManagementException err)
            {
                MessageBox.Show("An error occurred while trying to receive an event: " + err.Message);
            }
        }

        private void HandleEvent(object sender,
            EventArrivedEventArgs e)
        {
            Console.WriteLine("Win32_DeviceChangeEvent event occurred.");
        }

        public static void Main()
        {
            WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
            return;
        }

    }
}

采纳答案by Pat

I ended up using WMI and @Hans' advice to check what serial ports are new/missing.

我最终使用 WMI 和 @Hans 的建议来检查哪些串行端口是新的/缺失的。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.Contracts;
using System.IO.Ports;
using System.Management;

public static class SerialPortService
{
    private static SerialPort _serialPort;

    private static string[] _serialPorts;

    private static ManagementEventWatcher arrival;

    private static ManagementEventWatcher removal;

    static SerialPortService()
    {
        _serialPorts = GetAvailableSerialPorts();
        MonitorDeviceChanges();
    }

    /// <summary>
    /// If this method isn't called, an InvalidComObjectException will be thrown (like below):
    /// System.Runtime.InteropServices.InvalidComObjectException was unhandled
    ///Message=COM object that has been separated from its underlying RCW cannot be used.
    ///Source=mscorlib
    ///StackTrace:
    ///     at System.StubHelpers.StubHelpers.StubRegisterRCW(Object pThis, IntPtr pThread)
    ///     at System.Management.IWbemServices.CancelAsyncCall_(IWbemObjectSink pSink)
    ///     at System.Management.SinkForEventQuery.Cancel()
    ///     at System.Management.ManagementEventWatcher.Stop()
    ///     at System.Management.ManagementEventWatcher.Finalize()
    ///InnerException: 
    /// </summary>
    public static void CleanUp()
    {
        arrival.Stop();
        removal.Stop();
    }

    public static event EventHandler<PortsChangedArgs> PortsChanged;

    private static void MonitorDeviceChanges()
    {
        try
        {
            var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
            var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");

            arrival = new ManagementEventWatcher(deviceArrivalQuery);
            removal = new ManagementEventWatcher(deviceRemovalQuery);

            arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
            removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);

            // Start listening for events
            arrival.Start();
            removal.Start();
        }
        catch (ManagementException err)
        {

        }
    }

    private static void RaisePortsChangedIfNecessary(EventType eventType)
    {
        lock (_serialPorts)
        {
            var availableSerialPorts = GetAvailableSerialPorts();
            if (!_serialPorts.SequenceEqual(availableSerialPorts))
            {
                _serialPorts = availableSerialPorts;
                PortsChanged.Raise(null, new PortsChangedArgs(eventType, _serialPorts));
            }
        }
    }

    public static string[] GetAvailableSerialPorts()
    {
        return SerialPort.GetPortNames();
    }
}

public enum EventType
{
    Insertion,
    Removal,
}

public class PortsChangedArgs : EventArgs
{
    private readonly EventType _eventType;

    private readonly string[] _serialPorts;

    public PortsChangedArgs(EventType eventType, string[] serialPorts)
    {
        _eventType = eventType;
        _serialPorts = serialPorts;
    }

    public string[] SerialPorts
    {
        get
        {
            return _serialPorts;
        }
    }

    public EventType EventType
    {
        get
        {
            return _eventType;
        }
    }
}

The MonitorDeviceChangesmethod actually sees all device changes (like Device Manager), but checking the serial ports allows us to only raise an event when those have changed.

MonitorDeviceChanges方法实际上会看到所有设备更改(如设备管理器),但检查串行端口允许我们仅在更改时引发事件。

To use the code, simply subscribe to the PortsChangedevent, e.g. SerialPortService.PortsChanged += (sender1, changedArgs) => DoSomethingSerial(changedArgs.SerialPorts);

要使用代码,只需订阅PortsChanged事件,例如 SerialPortService.PortsChanged += (sender1, changedArgs) => DoSomethingSerial(changedArgs.SerialPorts);

Oh, and the .Raisemethod is just an extension method I picked up somewhere:

哦,该.Raise方法只是我在某处找到的扩展方法:

/// <summary>
/// Tell subscribers, if any, that this event has been raised.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="handler">The generic event handler</param>
/// <param name="sender">this or null, usually</param>
/// <param name="args">Whatever you want sent</param>
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
    // Copy to temp var to be thread-safe (taken from C# 3.0 Cookbook - don't know if it's true)
    EventHandler<T> copy = handler;
    if (copy != null)
    {
        copy(sender, args);
    }
}

回答by Hans Passant

No. Go find out what happened to SerialPort.GetPortNames(). Listening for the WM_DEVICECHANGEmessage in a window can give you better info.

不。去看看 SerialPort.GetPortNames() 发生了什么。在窗口中收听WM_DEVICECHANGE消息可以为您提供更好的信息。

回答by Journeyman

NB: I tried to post this as a comment on @Pat's answer, but don't have enough reputation to do that.

注意:我试图将此作为对@Pat 答案的评论发布,但没有足够的声誉来做到这一点。

Further to @2pietjuh2's comment, the RaisePortsChangedIfNecessary() can be changed to the following:

继@2pietjuh2 的评论之后,RaisePortsChangedIfNecessary() 可以更改为以下内容:

private static void RaisePortsChangedIfNecessary(EventType eventType)
{
    lock (_serialPorts)
    {
        var availableSerialPorts = GetAvailableSerialPorts();
        if (eventType == EventType.Insertion)
        {
            var added = availableSerialPorts.Except(_serialPorts).ToArray();
            _serialPorts = availableSerialPorts;
            PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
        }
        else if (eventType == EventType.Removal)
        {
            var removed = _serialPorts.Except(availableSerialPorts).ToArray();
            _serialPorts = availableSerialPorts;
            PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
        }
    }
}

Raised events then include the serial port inserted/removed, rather than the list of serial ports available after the insertion/removal.

引发的事件包括插入/移除的串行端口,而不是插入/移除后可用的串行端口列表。

回答by Tergiver

Here is a stripped down version of a DeviceChangeEventsnotification class I wrote some time ago, though I never fully completed it. I stripped out everything except the PortArrived event as it's quite fugly otherwise.

这是DeviceChangeEvents我前段时间写的一个通知类的精简版本,尽管我从未完全完成它。我去掉了除了 PortArrived 事件之外的所有内容,否则它会很糟糕。

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public sealed class PortArrivalEventArgs : EventArgs
{
    public string Name { get; private set; }
    public PortArrivalEventArgs(string name) { Name = name; }
}

public static class DeviceChangeEvents
{
    #region Events

    #region PortArrived
    private static object PortArrivedEvent = new Object();
    public static event EventHandler<PortArrivalEventArgs> PortArrived
    {
        add { AddEvent(PortArrivedEvent, value); }
        remove { RemoveEvent(PortArrivedEvent, value); }
    }
    private static void FirePortArrived(IntPtr lParam)
    {
        EventHandler<PortArrivalEventArgs> handler
            = (EventHandler<PortArrivalEventArgs>)events[PortArrivedEvent];
        if (handler != null)
        {
            string portName = Marshal.PtrToStringAuto((IntPtr)((long)lParam + 12));
            handler(null, new PortArrivalEventArgs(portName));
        }
    }
    #endregion

    #endregion

    #region Internal

    private static EventHandlerList events = new EventHandlerList();
    private static MessageWindow messageWindow = null;

    private static void AddEvent(object key, Delegate value)
    {
        events.AddHandler(key, value);
        if (messageWindow == null)
            messageWindow = new MessageWindow();
    }

    private static void RemoveEvent(object key, Delegate value)
    {
        events.RemoveHandler(key, value);

        // In the more complete version of DeviceChangedEvents, System.ComponentModel.EventHandlerList
        //  is replaced by an identical event storage object which exposes a count of the number of
        //  handlers installed. It also removes empty handler stubs. Both of these are required
        //  to safely destroy the message window when the last handler is removed.

        //if (messageWindow != null && events.Count == 0)
        //    messageWindow.DestroyHandle();
    }

    #endregion

    private sealed class MessageWindow : NativeWindow
    {
        public MessageWindow()
        {
            CreateParams cp = new CreateParams();
            cp.Caption = GetType().FullName;
            // NOTE that you cannot use a "message window" for this broadcast message
            //if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            //  cp.Parent = (IntPtr)(-3); // HWND_MESSAGE
            //Debug.WriteLine("Creating MessageWindow " + cp.Caption);
            CreateHandle(cp);
        }

        const int WM_DESTROY = 0x02;
        const int WM_DEVICECHANGE = 0x219;

        enum DBT
        {
            DEVICEARRIVAL = 0x8000,
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_DESTROY)
            {
                messageWindow = null;
            }
            else if (m.Msg == WM_DEVICECHANGE)
            {
                DBT changeType = (DBT)m.WParam;
                int deviceType = m.LParam == IntPtr.Zero ? 0 : Marshal.ReadInt32(m.LParam, 4);

                Debug.WriteLine(String.Format("WM_DEVICECHANGE changeType = {0}, deviceType = {1}", changeType, deviceType));

                switch (changeType)
                {
                    case DBT.DEVICEARRIVAL:
                        switch (deviceType)
                        {
                            case 3: // DBT_DEVTYP_PORT
                                FirePortArrived(m.LParam);
                                break;
                        }
                        break;
                }
            }

            base.WndProc(ref m);
        }
    }
}

回答by alyeomans

Your device change event can be used with the WMI - PNP Entity. The following will return device details - in the code below it shows the device name.

您的设备更改事件可以与 WMI - PNP 实体一起使用。以下将返回设备详细信息 - 在下面的代码中显示设备名称。

Dim moReturn As Management.ManagementObjectCollection
Dim moSearch As Management.ManagementObjectSearcher
Dim mo As Management.ManagementObject
moSearch = New Management.ManagementObjectSearcher("Select * from Win32_PnPEntity")
moReturn = moSearch.Get

For Each mo In moReturn
If CStr(mo.Properties.Item("Name").Value).Contains("Prolific") Then
    returns something like: "Prolific USB-to-Serial Comm Port (COM17)"
    txtStatus.Text &= CStr(mo.Properties.Item("Name").Value) & vbCrLf
End If
Next

Also see code to access other PNP properties that could be used to filtered or monitored for change:

另请参阅代码以访问可用于过滤或监控更改的其他 PNP 属性:

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_PnPEntity",,48)
For Each objItem in colItems
    "Availability: " & objItem.Availability
    "Caption: " & objItem.Caption
    "ClassGuid: " & objItem.ClassGuid
    "ConfigManagerErrorCode: " & objItem.ConfigManagerErrorCode
    "ConfigManagerUserConfig: " & objItem.ConfigManagerUserConfig
    "CreationClassName: " & objItem.CreationClassName
    "Description: " & objItem.Description
    "DeviceID: " & objItem.DeviceID
    "ErrorCleared: " & objItem.ErrorCleared
    "ErrorDescription: " & objItem.ErrorDescription
    "InstallDate: " & objItem.InstallDate
    "LastErrorCode: " & objItem.LastErrorCode
    "Manufacturer: " & objItem.Manufacturer
    "Name: " & objItem.Name
    "PNPDeviceID: " & objItem.PNPDeviceID
    "PowerManagementCapabilities: " & objItem.PowerManagementCapabilities
    "PowerManagementSupported: " & objItem.PowerManagementSupported
    "Service: " & objItem.Service
    "Status: " & objItem.Status
    "StatusInfo: " & objItem.StatusInfo
    "SystemCreationClassName: " & objItem.SystemCreationClassName
    "SystemName: " & objItem.SystemName
Next