如何捕获因 USB 电缆拔出而消失的串口

发布于 2024-07-09 10:57:10 字数 118 浏览 6 评论 0 原文

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

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

I have a c# winforms program and it opens up a serial port. The problem happens when the end user unplugs the usb cable and then the device disappears. After this the program will crash and want to report the error to microsoft.

Is there a way to capture this event and shut down gracefully?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

江湖正好 2024-07-16 10:57:10

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

方法是捕获 ErrorReceived 等 com 端口事件并捕获 WM_DEVICECHANGE 消息。

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

Yes, there is a way to capture the event. Unfortunately, there can be a long delay between the time the device is removed and the time the program receives any notification.

The approach is to trap com port events such as ErrorReceived and to catch the WM_DEVICECHANGE message.

Not sure why your program is crashing; you should take a look at the stack to see where this is happening.

樱&纷飞 2024-07-16 10:57:10

您可以使用 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 代码目录< /a>.

编辑
我找到了有关事件处理程序的更多信息,它大致如下所示:

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

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

You can use WMI (Windows Management Instrumentation) to receive notification on USB events.
I did exactly that two years ago, monitoring for plugging and unplugging of a specific usb device.
Unfortunately, the code stays with my former employer, but I found one example at 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
        }
}

I don't remember if I modified the query to receive events only for e specific device, or if I filtered out events from other devices in my event handler. For further information you may want to have a look at the MSDN WMI .NET Code Directory.

EDIT
I found some more info on the event handler, it looks roughly like this:

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

You may want to add some exception handling, though.

〆凄凉。 2024-07-16 10:57:10

注册表位于:
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 串行转换器

问候

In registry at:
HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM
is actual list of ports. If your port disappeared it means it was unplugged.

Real example: (Try to remove your USB and press F5 in registry editor)

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

COM10 - My fax modem
COM1 - USB - moxa usb serial converter
COM13 - USB - Profilic serial converter

Regards

抚笙 2024-07-16 10:57:10

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

Although the answers already given provide a good starting point, I would like to add some working examples for .net 4.5 and also an example of capturing a type of usb device.

In Treb's answer, he used the 'Win32_USBControllerDevice'. This may or may not be the best condition for your query, depending on what you want to accomplish. The device id from the Win32_USBControllerDevice is unique to each device. So if you're looking for a unique id that identifies a single device, then that's exactly what you want. But if you're looking for a certain type of device, you could use 'Win32_PnPEntity' and access the Description property. Here is an example of getting a certain type of device by its 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;
    }
}

Some links that might be useful for researching which classes to use in your sql statements:

Win32 Classes - In the example above, the 'Win32_PnPEntity' class was used.

WMI System Classes - In the example above, the __InstanceCreationEvent and __InstanceDeletionEvent classes were used.

秉烛思 2024-07-16 10:57:10

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

You could try to handle 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
}

Additionally, you could check whether the port exists before proceeding. You may want to check it once in a while, maybe just before reading/writing.

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

You should also place try-catch blocks for all your serial port operations. It should help to prevent unexpected terminations.

You may want to try to run the app in debug mode under your IDE and simulate the error. If an exception is throw, you would be able to identify where the problem becomes most evident. From there, you could probably try to find more specific solutions.

梦罢 2024-07-16 10:57:10

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

有一些SetupDi API(我想......已经有一段时间了)可以让您收到有关设备到达和删除的通知,但如果您已经崩溃了,那么它就无济于事了,因为删除的设备正在您的读取或删除过程中写操作。

If your try statement isn't catching the exception then let's hope Microsoft will inspect the dumps.

There are some SetupDi APIs (I think ... it's been a while) that permit you to be advised of device arrivals and removals, but it won't help if you already crashed because the removed device was in the middle of your read or write operation.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文