7

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

有没有办法捕捉这个事件并优雅地关闭?

4

6 回答 6

8

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

该方法是捕获 COM 端口事件(例如 ErrorReceived)并捕获 WM_DEVICECHANGE 消息。

不知道你的程序为什么会崩溃;您应该查看堆栈以了解发生这种情况的位置。

于 2008-11-13T04:34:22.023 回答
5

您可以使用 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
            }
        }
    }
}

不过,您可能想要添加一些异常处理。

于 2011-03-12T08:02:55.157 回答
3

在注册表中:
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 串口转换器

问候

于 2009-08-25T10:32:40.187 回答
2

尽管已经给出的答案提供了一个很好的起点,但我想为 .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类。

于 2014-02-04T22:14:58.840 回答
1

你可以尝试处理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 下以调试模式运行应用程序并模拟错误。如果抛出异常,您将能够确定问题在哪里变得最明显。从那里,您可能会尝试找到更具体的解决方案。

于 2011-03-11T07:37:04.163 回答
0

如果您的 try 语句没有捕获异常,那么我们希望 Microsoft 会检查转储。

有一些 SetupDi API(我认为......已经有一段时间了)允许您被告知设备到达和移除,但如果您已经崩溃,因为移除的设备在您的读取过程中或写操作。

于 2008-11-13T03:48:09.200 回答