Dot-Net

檢測串口插入/移除

  • November 16, 2010

我正在連接一個可以隨時插入或移除的 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);
   }
}

引用自:https://stackoverflow.com/questions/4199083