Domanda

Ho un programma winforms c # e mi apre una porta seriale. Il problema si verifica quando l'utente finale scollega il cavo USB e quindi il dispositivo scompare. Successivamente, il programma si arresta in modo anomalo e desidera segnalare l'errore a Microsoft.

C'è un modo per catturare questo evento e spegnerlo con grazia?

È stato utile?

Soluzione

È possibile utilizzare WMI (Strumentazione gestione Windows) per ricevere notifiche su eventi USB. L'ho fatto esattamente due anni fa, monitorando il collegamento e lo scollegamento di un dispositivo USB specifico.
Sfortunatamente, il codice rimane con il mio ex datore di lavoro, ma ho trovato un esempio in 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
        }
}

Non ricordo se ho modificato la query per ricevere eventi solo per un dispositivo specifico o se ho filtrato eventi da altri dispositivi nel mio gestore eventi. Per ulteriori informazioni, ti consigliamo di dare un'occhiata alla Directory del codice .NET WMI di MSDN .

Modifica Ho trovato qualche informazione in più sul gestore dell'evento, sembra più o meno così:

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

Tuttavia, potresti voler aggiungere una gestione delle eccezioni.

Altri suggerimenti

Sì, c'è un modo per catturare l'evento. Sfortunatamente, potrebbe esserci un lungo ritardo tra il momento in cui il dispositivo viene rimosso e il momento in cui il programma riceve una notifica.

L'approccio è intercettare eventi della porta com come ErrorReceived e catturare il messaggio WM_DEVICECHANGE.

Non so perché il tuo programma si sta arrestando in modo anomalo; dovresti dare un'occhiata allo stack per vedere dove sta succedendo.

Nel registro su:
HKEY_LOCAL_MACHINE \ HARDWARE \ DEVICEMAP \ SERIALCOMM
è l'elenco effettivo delle porte. Se la tua porta è scomparsa significa che è stata scollegata.

Esempio reale: (prova a rimuovere l'USB e premi F5 nell'editor del registro)

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

COM10 - Il mio modem fax
COM1 - USB - convertitore seriale USB moxa
COM13 - USB - Convertitore seriale profilico

Saluti

Sebbene le risposte già fornite forniscano un buon punto di partenza, vorrei aggiungere alcuni esempi funzionanti per .net 4.5 e anche un esempio di acquisizione di un tipo di dispositivo USB.

Nella risposta di Treb, ha usato il 'Win32_USBControllerDevice' . Questa potrebbe essere o meno la migliore condizione per la tua query, a seconda di ciò che vuoi realizzare. L'ID dispositivo da Win32_USBControllerDevice è univoco per ciascun dispositivo. Quindi, se stai cercando un ID univoco che identifichi un singolo dispositivo, allora è esattamente quello che vuoi. Ma se stai cercando un certo tipo di dispositivo, puoi usare 'Win32_PnPEntity' e accedere alla proprietà Descrizione . Ecco un esempio di come ottenere un certo tipo di dispositivo tramite la sua descrizione:

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

Alcuni collegamenti che potrebbero essere utili per la ricerca delle classi da utilizzare nelle istruzioni sql:

Classi Win32 - In nell'esempio sopra, è stata utilizzata la classe 'Win32_PnPEntity' .

Classi di sistema WMI - Nell'esempio sopra, sono state utilizzate le classi __InstanceCreationEvent e __InstanceDeletionEvent .

Potresti provare a gestire 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
}

Inoltre, è possibile verificare se la porta esiste prima di procedere. Potresti volerlo controllare una volta ogni tanto, forse poco prima di leggere / scrivere.

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

Dovresti anche posizionare i blocchi try-catch per tutte le operazioni della porta seriale. Dovrebbe aiutare a prevenire terminazioni impreviste.

Potresti provare a eseguire l'app in modalità debug sotto il tuo IDE e simulare l'errore. Se viene generata un'eccezione, sarai in grado di identificare dove il problema diventa più evidente. Da lì, probabilmente potresti provare a trovare soluzioni più specifiche.

Se l'istruzione try non rileva l'eccezione, speriamo che Microsoft ispezionerà i dump.

Esistono alcune API SetupDi (penso ... è passato un po 'di tempo) che ti consentono di essere avvisato degli arrivi e delle rimozioni del dispositivo, ma non ti aiuteranno se sei già andato in crash perché il dispositivo rimosso era nel mezzo di l'operazione di lettura o scrittura.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top