我有 ac# winforms 程序,它打开了一个串行端口。当最终用户拔下 USB 电缆然后设备消失时,就会出现问题。在此之后程序将崩溃并想向微软报告错误。
有没有办法捕捉这个事件并优雅地关闭?
我有 ac# winforms 程序,它打开了一个串行端口。当最终用户拔下 USB 电缆然后设备消失时,就会出现问题。在此之后程序将崩溃并想向微软报告错误。
有没有办法捕捉这个事件并优雅地关闭?
是的,有一种方法可以捕获事件。不幸的是,从设备被移除到程序收到任何通知的时间之间可能会有很长的延迟。
该方法是捕获 COM 端口事件(例如 ErrorReceived)并捕获 WM_DEVICECHANGE 消息。
不知道你的程序为什么会崩溃;您应该查看堆栈以了解发生这种情况的位置。
您可以使用 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
}
}
}
}
不过,您可能想要添加一些异常处理。
在注册表中:
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 语句没有捕获异常,那么我们希望 Microsoft 会检查转储。
有一些 SetupDi API(我认为......已经有一段时间了)允许您被告知设备到达和移除,但如果您已经崩溃,因为移除的设备在您的读取过程中或写操作。