代码之家  ›  专栏  ›  技术社区  ›  Pat

检测串行端口插入/删除

  •  21
  • Pat  · 技术社区  · 15 年前

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

    在下面生成的代码片段中 Win32_DeviceChangeEvent 已订阅。不过,这件事并没有透露 哪个设备 (例如USB、串行端口等)导致了该事件。有没有办法只在插入或删除串行端口时接收通知?

    为了澄清,代码的要点是 检测 开/关 对于串行端口,它是为了检测 新的 端口已添加到计算机,或者以前的端口是 远离的 .

    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;
            }
    
        }
    }
    
    5 回复  |  直到 13 年前
        1
  •  21
  •   Pat    15 年前

    最后我使用了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;
            }
        }
    }
    

    这个 MonitorDeviceChanges 方法实际上可以看到所有设备更改(如设备管理器),但检查串行端口只允许在这些更改发生时引发事件。

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

    哦,还有 .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);
        }
    }
    
        2
  •  2
  •   Journeyman    11 年前

    注:我试着把这篇文章作为对@Pat答案的评论,但没有足够的声誉来做这件事。

    根据@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));
            }
        }
    }
    

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

        3
  •  2
  •   Wolf    10 年前

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

        4
  •  1
  •   Tergiver    15 年前

    这是一个精简版的 DeviceChangeEvents 通知类我写了一段时间以前,虽然我从来没有完全完成它。我把所有的东西都去掉了,除了移植的事件,因为它完全不同。

    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);
            }
        }
    }
    
        5
  •  0
  •   alyeomans    12 年前

    您的设备更改事件可以与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
    

    另请参阅访问可用于筛选或监视更改的其他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
    
    推荐文章