我有一个c#winforms程序,它打开了一个串口。当最终用户拔下USB电缆然后设备消失时,就会出现问题。在此之后,程序将崩溃,并希望将错误报告给microsoft。

有没有办法捕获此事件并优雅地关闭?

有帮助吗?

解决方案

您可以使用WMI(Windows Management Instrumentation)接收有关USB事件的通知。 两年前我完成了这项工作,监控插入和拔出特定的USB设备。
不幸的是,代码留在我的前雇主那里,但我在 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
        }
}

我不记得我是否修改了查询以仅接收特定设备的事件,或者我是否从事件处理程序中的其他设备过滤掉了事件。有关详细信息,您可能需要查看 MSDN WMI .NET代码目录

修改 我在事件处理程序上找到了更多信息,它看起来大致如下:

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

但是,您可能希望添加一些异常处理。

其他提示

是的,有一种方法可以捕获事件。不幸的是,在删除设备和程序收到任何通知的时间之间可能会有很长的延迟。

该方法是捕获诸如ErrorReceived之类的com端口事件并捕获WM_DEVICECHANGE消息。

不确定程序崩溃的原因;你应该看看堆栈,看看发生了什么。

在注册表中:
HKEY_LOCAL_MACHINE \ HARDWARE \ DEVICEMAP \ SERIALCOMM结果 是实际的端口列表。如果你的端口消失了,就意味着它被拔掉了。

真实示例:(尝试删除USB并在注册表编辑器中按F5)

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

COM10 - 我的传真调制解调器
COM1 - USB - moxa USB串行转换器
COM13 - USB - Profilic串行转换器

此致

虽然已经给出的答案提供了一个很好的起点,但我想为.net 4.5添加一些工作示例,以及捕获usb设备的类型的示例。

在Treb的回答中,他使用了'Win32_USBControllerDevice'。这可能是您查询的最佳条件,也可能不是,这取决于您想要完成的任务。 Win32_USBControllerDevice中的设备ID对每个设备都是唯一的。因此,如果您正在寻找识别单个设备的唯一ID,那么这正是您想要的。但是,如果您正在寻找设备的某个类型,则可以使用'Win32_PnPEntity'并访问 Description 属性。以下是通过描述获取设备的某个类型的示例:

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

一些链接可能对研究在sql语句中使用哪些类有用:

Win32课程 - 在上面的例子中,使用了'Win32_PnPEntity'类。

WMI系统类 - 在上面的示例中,使用了 __ InstanceCreationEvent __ InstanceDeletionEvent 类。

您可以尝试处理 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
}

此外,您可以在继续之前检查端口是否存在。您可能希望偶尔检查一下,也许只是在阅读/写作之前。

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

您还应该为所有串行端口操作放置 try-catch 块。它应该有助于防止意外终止。

您可能想尝试在IDE下以调试模式运行应用程序并模拟错误。如果抛出异常,您将能够确定问题变得最明显的位置。从那里,您可能会尝试找到更具体的解决方案。

如果你的try语句没有捕获异常,那么我们希望微软将检查转储。

有一些SetupDi API(我认为......已经有一段时间了)允许您被告知设备到达和删除,但如果您已经崩溃,因为删除的设备位于中间,它将无济于事你的读写操作。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top