Pregunta

Tengo un programa c # winforms y se abre un puerto serie. El problema ocurre cuando el usuario final desconecta el cable usb y luego el dispositivo desaparece. Después de esto, el programa se bloqueará y desea informar el error a Microsoft.

¿Hay alguna manera de capturar este evento y apagarlo con gracia?

¿Fue útil?

Solución

Puede usar WMI (Instrumental de administración de Windows) para recibir notificaciones sobre eventos USB. Hice exactamente eso hace dos años, monitoreando la conexión y desconexión de un dispositivo USB específico.
Desafortunadamente, el código permanece con mi antiguo empleador, pero encontré un ejemplo en bytes.com :

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Management;
class UsbWatcher 
{
    public static void Main() 
    {
        WMIEvent wEvent = new WMIEvent();
        ManagementEventWatcher watcher = null;
        WqlEventQuery query;
        ManagementOperationObserver observer = new ManagementOperationObserver();

        ManagementScope scope = new ManagementScope("root\\CIMV2");
        scope.Options.EnablePrivileges = true; 
        try 
        {
            query = new WqlEventQuery();
            query.EventClassName = "__InstanceCreationEvent";
            query.WithinInterval = new TimeSpan(0,0,10);

            query.Condition = @"TargetInstance ISA 'Win32_USBControllerDevice' ";
            watcher = new ManagementEventWatcher(scope, query);

            watcher.EventArrived 
                += new EventArrivedEventHandler(wEvent.UsbEventArrived);
            watcher.Start();
        }
        catch (Exception e)
        {
            //handle exception
        }
}

No recuerdo si modifiqué la consulta para recibir eventos solo para un dispositivo específico, o si filtré eventos de otros dispositivos en mi controlador de eventos. Para obtener más información, puede consultar el MSDN WMI .NET Code Directory .

EDITAR Encontré más información sobre el controlador de eventos, se ve más o menos así:

protected virtual void OnUsbConnected(object Sender, EventArrivedEventArgs Arguments)
{
    PropertyData TargetInstanceData = Arguments.NewEvent.Properties["TargetInstance"];

    if (TargetInstanceData != null)
    {
        ManagementBaseObject TargetInstanceObject = (ManagementBaseObject)TargetInstanceData.Value;
        if (TargetInstanceObject != null)
        {
            string dependent = TargetInstanceObject.Properties["Dependent"].Value.ToString();
            string deviceId = dependent.Substring(dependent.IndexOf("DeviceID=") + 10);

            // device id string taken from windows device manager
            if (deviceId = "USB\\\\VID_0403&PID_6001\\\\12345678\"")
            {
                // Device is connected
            }
        }
    }
}

Sin embargo, es posible que desee agregar algo de manejo de excepciones.

Otros consejos

Sí, hay una manera de capturar el evento. Desafortunadamente, puede haber una gran demora entre el momento en que se retira el dispositivo y el momento en que el programa recibe cualquier notificación.

El enfoque es atrapar eventos del puerto de comunicaciones como ErrorReceived y capturar el mensaje WM_DEVICECHANGE.

No estoy seguro de por qué su programa se bloquea; deberías echar un vistazo a la pila para ver dónde está sucediendo esto.

En el registro en:
HKEY_LOCAL_MACHINE \ HARDWARE \ DEVICEMAP \ SERIALCOMM
es la lista real de puertos. Si su puerto desapareció significa que estaba desconectado.

Ejemplo real: (Intente quitar su USB y presione F5 en el editor de registro)

Windows Registry Editor Version 5.00
HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM]
"Winachsf0"="COM10"
"\\Device\\mxuport0"="COM1"
"\\Device\\Serial2"="COM13"

COM10 - Mi fax módem
COM1 - USB - convertidor serial moxa usb
COM13 - USB - Convertidor serial Profilic

Saludos

Aunque las respuestas ya proporcionadas proporcionan un buen punto de partida, me gustaría agregar algunos ejemplos de trabajo para .net 4.5 y también un ejemplo de capturar un tipo de dispositivo usb.

En la respuesta de Treb, utilizó el 'Win32_USBControllerDevice' . Esta puede o no ser la mejor condición para su consulta, dependiendo de lo que quiera lograr. La identificación del dispositivo del Win32_USBControllerDevice es única para cada dispositivo. Entonces, si está buscando una identificación única que identifique un solo dispositivo, eso es exactamente lo que desea. Pero si está buscando un cierto tipo de dispositivo, puede usar 'Win32_PnPEntity' y acceder a la propiedad Descripción . Aquí hay un ejemplo de cómo obtener un cierto tipo de dispositivo por su descripción:

using System;
using System.ComponentModel.Composition;
using System.Management;

public class UsbDeviceMonitor
{
    private ManagementEventWatcher plugInWatcher;
    private ManagementEventWatcher unPlugWatcher;
    private const string MyDeviceDescription = @"My Device Description";

    ~UsbDeviceMonitor()
    {
        Dispose();
    }

    public void Dispose()
    {
        if (plugInWatcher != null)
            try
            {
                plugInWatcher.Dispose();
                plugInWatcher = null;
            }
            catch (Exception) { }

        if (unPlugWatcher == null) return;
        try
        {
            unPlugWatcher.Dispose();
            unPlugWatcher = null;
        }
        catch (Exception) { }
    }

    public void Start()
    {
        const string plugInSql = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'";
        const string unpluggedSql = "SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_PnPEntity'";

        var scope = new ManagementScope("root\\CIMV2") {Options = {EnablePrivileges = true}};

        var pluggedInQuery = new WqlEventQuery(plugInSql);
        plugInWatcher = new ManagementEventWatcher(scope, pluggedInQuery);
        plugInWatcher.EventArrived += HandlePluggedInEvent;
        plugInWatcher.Start();

        var unPluggedQuery = new WqlEventQuery(unpluggedSql);
        unPlugWatcher = new ManagementEventWatcher(scope, unPluggedQuery);
        unPlugWatcher.EventArrived += HandleUnPluggedEvent;
        unPlugWatcher.Start();
    }

    private void HandleUnPluggedEvent(object sender, EventArrivedEventArgs e)
    {
        var description = GetDeviceDescription(e.NewEvent);
        if (description.Equals(MyDeviceDescription))
            // Take actions here when the device is unplugged
    }

    private void HandlePluggedInEvent(object sender, EventArrivedEventArgs e)
    {
        var description = GetDeviceDescription(e.NewEvent);
        if (description.Equals(MyDeviceDescription))
            // Take actions here when the device is plugged in
    }

    private static string GetDeviceDescription(ManagementBaseObject newEvent)
    {
        var targetInstanceData = newEvent.Properties["TargetInstance"];
        var targetInstanceObject = (ManagementBaseObject) targetInstanceData.Value;
        if (targetInstanceObject == null) return "";

        var description = targetInstanceObject.Properties["Description"].Value.ToString();
        return description;
    }
}

Algunos enlaces que pueden ser útiles para investigar qué clases usar en sus declaraciones sql:

Clases Win32 - En En el ejemplo anterior, se utilizó la clase 'Win32_PnPEntity' .

Clases del sistema WMI - En el ejemplo anterior, se usaron las clases __InstanceCreationEvent y __InstanceDeletionEvent .

Podría intentar manejar ErrorReceived .

private void buttonStart_Click(object sender, EventArgs e)
{
    port.ErrorReceived += new System.IO.Ports.SerialErrorReceivedEventHandler(port_ErrorReceived);
}

void port_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e)
{
    // TODO: handle the problem here
}

Además, puede verificar si el puerto existe antes de continuar. Es posible que desee comprobarlo de vez en cuando, tal vez justo antes de leer / escribir.

string[] ports = System.IO.Ports.SerialPort.GetPortNames();
if (ports.Contains("COM7:"))
{
    // TODO: Can continue
}
else
{
    // TODO: Cannot, terminate properly
}

También debe colocar bloques try-catch para todas sus operaciones de puerto serie. Debería ayudar a evitar terminaciones inesperadas.

Puede intentar ejecutar la aplicación en modo de depuración bajo su IDE y simular el error. Si se lanza una excepción, podrá identificar dónde se hace más evidente el problema. A partir de ahí, probablemente podría intentar encontrar soluciones más específicas.

Si su declaración de prueba no detecta la excepción, esperemos que Microsoft inspeccione los volcados.

Hay algunas API de SetupDi (creo ... ha pasado un tiempo) que le permiten ser informado sobre la llegada y extracción de dispositivos, pero no ayudará si ya se bloqueó porque el dispositivo eliminado estaba en el medio de su operación de lectura o escritura.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top