【问题标题】:Detect serial port insertion/removal检测串口插入/移除
【发布时间】:2011-05-11 02:08:51
【问题描述】:

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

在下面生成的 sn-p 中,订阅了 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;
        }

    }
}

【问题讨论】:

    标签: .net wmi serial-port


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

      这是我前段时间编写的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);
              }
          }
      }
      

      【讨论】:

      • 感谢您澄清@Hans 所说的“在窗口中侦听 WM_DEVICECHANGE 消息”的含义 - 我不知道。但是必须有一个 NativeWindow 和非托管代码并没有真正吸引我。
      • 代码的每一行都会调用非托管代码。没有所谓的“纯”.NET 应用程序。如果不与操作系统交互,任何应用程序都无法执行有用的工作。
      • 如果您不喜欢 NativeWindow(这没有任何意义,因为所有 System.Windows.Forms.Control 对象都基于 NativeWindow),您可以简单地覆盖主窗口的 WndProc。上述类的目的是自己封装消息。
      • 我无意冒犯,但使用 Marshal 类中的任何内容都意味着使用非托管代码 (msdn.microsoft.com/en-us/library/…)。另外,我正在使用控制台应用程序,因此没有“主窗口”。
      • 如果您想在控制台应用程序中使用 WM_DEVICECHANGE,您可以使用消息泵和窗口启动线程。我没有冒犯您的评论,我的问题是人们对“不安全”代码的厌恶。 .NET Framework 充满了不安全的代码。这是无法避免的,因此任何对它的厌恶都是不合逻辑的。
      【解决方案3】:

      我最终使用 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) =&gt; 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);
          }
      }
      

      【讨论】:

      • 您好,我尝试使用此代码,发现删除部分不是您能得到的最好的。当应用程序与设备连接并且您断开电缆时,您的代码会注意到这一点。但是,您看不到任何更改,因为程序尚未清理端口,GetPortNames 将返回不再可用的 comport。我只对删除事件感兴趣,所以我正在检查SerialPort 是否打开。如果端口关闭,则发生了移除事件。
      • @2pietjuh2 如果我理解你,你是对的。代码的重点不是检测串行端口的打开/关闭,而是检测是否向机器添加了端口或之前的端口被删除。那么可能是您正在查看不同的问题吗?
      • 您能解释一下事件类型 2 和 3 是什么以及存在哪些其他事件类型吗?
      • @JohnDemetriou 请参阅msdn.microsoft.com/en-us/library/windows/desktop/…,具体来说:配置已更改 (1) 设备到达 (2) 设备移除 (3) 对接 (4)
      • 是否有人使用上述代码面临多个事件触发问题?
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        注意:我试图将此作为对@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));
                }
            }
        }
        

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-07-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-05-03
          • 1970-01-01
          • 2018-06-12
          相关资源
          最近更新 更多