Pergunta

Eu tenho um programa C # winforms e abre uma porta serial. O problema acontece quando o usuário final desconecta o cabo USB e, em seguida, o dispositivo desaparece. Depois disso, o programa irá falhar e quer denunciar o erro à Microsoft.

Existe uma maneira de capturar este evento e desligar normalmente?

Foi útil?

Solução

Você pode usar o WMI (Windows Management Instrumentation) para receber a notificação sobre eventos USB. Eu fiz exatamente isso há dois anos, o monitoramento para ligar e desligar de um dispositivo USB específico.
Infelizmente, as estadias de código com o meu antigo empregador, mas eu encontrei um exemplo em 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
        }
}

Eu não me lembro se eu modifiquei a consulta para receber apenas os eventos para o dispositivo e específico, ou se eu filtrados eventos de outros dispositivos no meu manipulador de eventos. Para mais informações você pode querer ter um olhar para o href="http://msdn.microsoft.com/en-us/library/ms257338.aspx" rel="nofollow"> MSDN Repertório .NET WMI .

Editar Eu encontrei mais algumas informações sobre o manipulador de eventos, parece mais ou menos assim:

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

Você pode querer adicionar um pouco de tratamento de exceções, no entanto.

Outras dicas

Sim, há uma maneira de capturar o evento. Infelizmente, pode haver um longo atraso entre o tempo que o dispositivo é removido eo tempo que o programa recebe qualquer notificação.

A abordagem é para eventos de porta de COM como ErrorReceived e para pegar a mensagem WM_DEVICECHANGE.

Não sei por que seu programa está falhando; você deve dar uma olhada na pilha para ver onde isso está acontecendo.

No registro em:
HKEY_LOCAL_MACHINE \ HARDWARE \ DEVICEMAP \ SERIALCOMM
é verdadeira lista de portas. Se a sua porta desapareceu isso significa que ele foi desconectado.

Exemplo real: (Tente remover o seu USB e pressione F5 no editor de registro)

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

COM10 - Meu fax modem
COM1 - USB - moxa conversor USB série
COM13 - USB - Profilic série conversor

Saudações

Embora as respostas já dadas proporcionar um bom ponto de partida, gostaria de acrescentar alguns exemplos de trabalho para .NET 4.5 e também um exemplo de captura de um tipo do dispositivo USB.

A resposta de De Treb, ele usou a 'Win32_USBControllerDevice'. Isto pode ou não pode ser a melhor condição para a sua consulta, dependendo do que você quer realizar. O ID do dispositivo a partir do Win32_USBControllerDevice é exclusivo para cada dispositivo. Então, se você está procurando uma identificação única que identifica um único dispositivo, então isso é exatamente o que você quer. Mas se você estiver procurando por um determinado tipo do dispositivo, você poderia usar 'Win32_PnPEntity' e acesso a propriedade Description. Aqui está um exemplo de obter um certo tipo do dispositivo por sua descrição:

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

Alguns links que podem ser úteis para pesquisar quais as classes para usar em suas instruções SQL:

Win32 Classes - Em o exemplo acima, foi usado o classe 'Win32_PnPEntity'.

sistema WMI - no exemplo acima, foram usadas as classes __InstanceCreationEvent e __InstanceDeletionEvent.

Você poderia tentar ErrorReceived alça.

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
}

Além disso, você pode verificar se a porta existe antes de prosseguir. Você pode querer verificá-lo de vez em quando, talvez um pouco antes de leitura / escrita.

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

Você também deve colocar blocos try-catch para todas as suas operações de porta serial. Deve ajudar a prevenir desligamentos inesperados.

Você pode querer tentar executar o aplicativo no modo de depuração sob o seu IDE e simular o erro. Se uma exceção é jogar, você seria capaz de identificar onde o problema se torna mais evidente. De lá, você provavelmente poderia tentar encontrar soluções mais específicas.

Se o seu try não está pegando a exceção, então vamos esperar que a Microsoft vai inspecionar as lixeiras.

Existem algumas APIs SetupDi (acho ... Tem sido um tempo) que permitem que você seja avisado da chegada de dispositivo e remoções, mas não vai ajudar se você já caiu porque o dispositivo removido estava no meio de sua operação de leitura ou escrita.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top