2010-11-16 24 views
18

Estoy interactuando con un puerto USB a serie que se puede insertar o extraer en cualquier momento. Descubrí que puedo usar WMI (particularmente con el uso de WMI Code Creator) para consultar los cambios del dispositivo en la PC.Detectar inserción/extracción del puerto serie

En el fragmento generado a continuación, se suscribió al Win32_DeviceChangeEvent. Sin embargo, este evento no revela qué dispositivo (por ejemplo, USB, puerto serie, etc.) causó el evento. ¿Hay alguna forma de recibir notificaciones solo cuando se insertan o eliminan puertos serie?

Para aclarar, el punto de que el código es no para detectar de apertura/cierre de puertos serie, es para detectar si un nuevo puerto ha sido añadido a la máquina o un puerto anterior se retira.

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; 
     } 

    } 
} 

Respuesta

20

Terminé usando WMI y consejos @Hans' para comprobar qué puertos serie son nuevos/faltante.

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; 
     } 
    } 
} 

El método MonitorDeviceChanges realmente ve todos los cambios de dispositivo (como el Administrador de dispositivos), pero la comprobación de los puertos serie nos permite presentar solamente un evento cuando los han cambiado.

Para usar el código, simplemente suscríbase al evento PortsChanged, p. SerialPortService.PortsChanged += (sender1, changedArgs) => DoSomethingSerial(changedArgs.SerialPorts);

Ah, y el método .Raise es sólo un método de extensión Recogí alguna parte:

/// <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

Hola, traté de usar este código y descubrí que la pieza eliminada no es lo mejor que puedes obtener. Cuando la aplicación tenga conexión con el dispositivo y desconecte el cable, su código lo notará. Sin embargo, no puede ver ningún cambio porque el programa no limpió los puertos aún y 'GetPortNames' devolverá el comportamiento que ya no está disponible. Solo estoy interesado en el evento de eliminación, por lo que estoy comprobando si el 'SerialPort' está abierto o no. Si el puerto está cerrado, se ha producido un evento de eliminación. – 2pietjuh2

+1

@ 2pietjuh2 Si te entiendo, estás en lo cierto. El objetivo del código no es detectar la apertura/cierre de los puertos serie, sino detectar si se ha agregado un * nuevo * puerto a la máquina o si se eliminó * un puerto anterior *. Entonces, ¿podría ser que estás viendo un problema diferente? – Pat

+0

¿Puede explicar qué son los tipos de evento 2 y 3 y qué otros tipos de eventos existen? –

2

No. Ve a ver qué pasó con SerialPort.GetPortNames(). Escuchar el mensaje WM_DEVICECHANGE en una ventana puede brindarle una mejor información.

1

Aquí hay una versión simplificada de una clase de notificación DeviceChangeEvents que escribí hace algún tiempo, aunque nunca la completé por completo. Eliminé todo, excepto el evento PortArrived, ya que de lo contrario es bastante fugitivo.

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); 
     } 
    } 
} 
+0

Gracias por aclarar lo que @Hans quiso decir con "Escuchando el mensaje WM_DEVICECHANGE en una ventana" - No tenía ni idea.Pero tener que tener una NativeWindow y un código no administrado realmente no me atrae. – Pat

+1

Cada línea de su código hace llamadas al código no administrado. No existe una aplicación .NET 'pura'. Ninguna aplicación puede realizar un trabajo útil sin interactuar con el sistema operativo. – Tergiver

+0

Si no le gusta NativeWindow (lo cual no tiene sentido ya que todos los objetos System.Windows.Forms.Control se basan en NativeWindow), puede simplemente anular el WndProc de su ventana principal. El propósito de la clase anterior es encapsular el mensaje por sí mismo. – Tergiver

0

Su evento de cambio de dispositivo se puede utilizar con el WMI - PNP Entidad. A continuación, se mostrarán los detalles del dispositivo: en el siguiente código, se muestra el nombre del dispositivo.

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 

También véase el código para acceder a otras propiedades de la PNP que podrían ser utilizados para filtrada o monitoreados para el cambio:

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 
2

NB: He intentado publicar esto como un comentario en @ respuesta de la patente, pero don No tengo suficiente reputación para hacer eso.

Además de comentario de @ 2pietjuh2, la RaisePortsChangedIfNecessary() se puede cambiar a lo siguiente:

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)); 
     } 
    } 
} 

eventos eleva entonces incluyen el puerto serie insertado/extraído, en lugar de la lista de puertos serie disponibles después de la inserción /eliminación.

Cuestiones relacionadas