Dot-Net
檢測串口插入/移除
我正在連接一個可以隨時插入或移除的 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; } } }
我最終使用 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); } }