Обнаружение ввода/удаления последовательного порта
Я взаимодействую с портом USB-to-serial, который можно вставлять или удалять в любое время. Я обнаружил, что могу использовать WMI (особенно с использованием WMI Code Creator) для запроса изменений устройства на ПК.
В созданном ниже фрагменте подписывается Win32_DeviceChangeEvent. Однако это событие не показывает , какое устройство (например, USB, последовательный порт и т.д.) Вызвало событие. Есть ли способ получать уведомления только при вставке или удалении последовательных портов?
Чтобы уточнить, точка кода не для обнаружения открытия/закрытия последовательных портов, необходимо определить, был ли добавлен порт новый на машине или предыдущий порт был удален.
using System;
using System.Management;
using System.Windows.Forms;
namespace WMISample
{
public class WMIReceiveEvent
{
public WMIReceiveEvent()
{
try
{
WqlEventQuery query = new WqlEventQuery(
"SELECT * FROM Win32_DeviceChangeEvent");
ManagementEventWatcher watcher = new ManagementEventWatcher(query);
Console.WriteLine("Waiting for an event...");
watcher.EventArrived +=
new EventArrivedEventHandler(
HandleEvent);
// Start listening for events
watcher.Start();
// Do something while waiting for events
System.Threading.Thread.Sleep(10000);
// Stop listening for events
watcher.Stop();
return;
}
catch(ManagementException err)
{
MessageBox.Show("An error occurred while trying to receive an event: " + err.Message);
}
}
private void HandleEvent(object sender,
EventArrivedEventArgs e)
{
Console.WriteLine("Win32_DeviceChangeEvent event occurred.");
}
public static void Main()
{
WMIReceiveEvent receiveEvent = new WMIReceiveEvent();
return;
}
}
}
Ответы
Ответ 1
В итоге я использовал WMI и @Hans, чтобы проверить, какие новые порты новые/отсутствуют.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics.Contracts;
using System.IO.Ports;
using System.Management;
public static class SerialPortService
{
private static SerialPort _serialPort;
private static string[] _serialPorts;
private static ManagementEventWatcher arrival;
private static ManagementEventWatcher removal;
static SerialPortService()
{
_serialPorts = GetAvailableSerialPorts();
MonitorDeviceChanges();
}
/// <summary>
/// If this method isn't called, an InvalidComObjectException will be thrown (like below):
/// System.Runtime.InteropServices.InvalidComObjectException was unhandled
///Message=COM object that has been separated from its underlying RCW cannot be used.
///Source=mscorlib
///StackTrace:
/// at System.StubHelpers.StubHelpers.StubRegisterRCW(Object pThis, IntPtr pThread)
/// at System.Management.IWbemServices.CancelAsyncCall_(IWbemObjectSink pSink)
/// at System.Management.SinkForEventQuery.Cancel()
/// at System.Management.ManagementEventWatcher.Stop()
/// at System.Management.ManagementEventWatcher.Finalize()
///InnerException:
/// </summary>
public static void CleanUp()
{
arrival.Stop();
removal.Stop();
}
public static event EventHandler<PortsChangedArgs> PortsChanged;
private static void MonitorDeviceChanges()
{
try
{
var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
arrival = new ManagementEventWatcher(deviceArrivalQuery);
removal = new ManagementEventWatcher(deviceRemovalQuery);
arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = GetAvailableSerialPorts();
if (!_serialPorts.SequenceEqual(availableSerialPorts))
{
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, _serialPorts));
}
}
}
public static string[] GetAvailableSerialPorts()
{
return SerialPort.GetPortNames();
}
}
public enum EventType
{
Insertion,
Removal,
}
public class PortsChangedArgs : EventArgs
{
private readonly EventType _eventType;
private readonly string[] _serialPorts;
public PortsChangedArgs(EventType eventType, string[] serialPorts)
{
_eventType = eventType;
_serialPorts = serialPorts;
}
public string[] SerialPorts
{
get
{
return _serialPorts;
}
}
public EventType EventType
{
get
{
return _eventType;
}
}
}
Метод MonitorDeviceChanges
фактически видит все изменения устройства (например, Device Manager), но проверка последовательных портов позволяет нам только поднять событие, когда они изменились.
Чтобы использовать код, просто подпишитесь на событие PortsChanged
, например. SerialPortService.PortsChanged += (sender1, changedArgs) => DoSomethingSerial(changedArgs.SerialPorts);
О, а метод .Raise
- это только метод расширения, который я выбрал где-то:
/// <summary>
/// Tell subscribers, if any, that this event has been raised.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="handler">The generic event handler</param>
/// <param name="sender">this or null, usually</param>
/// <param name="args">Whatever you want sent</param>
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
// Copy to temp var to be thread-safe (taken from C# 3.0 Cookbook - don't know if it true)
EventHandler<T> copy = handler;
if (copy != null)
{
copy(sender, args);
}
}
Ответ 2
Нет. Пойдите, чтобы узнать, что случилось с SerialPort.GetPortNames(). Прослушивание сообщения WM_DEVICECHANGE в окне может дать вам лучшую информацию.
Ответ 3
NB: Я попытался опубликовать это как комментарий к ответу @Pat, но не имею достаточной репутации для этого.
В дополнение к комментарию @2pietjuh2 параметр RaisePortsChangedIfNecessary() можно изменить на следующее:
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = GetAvailableSerialPorts();
if (eventType == EventType.Insertion)
{
var added = availableSerialPorts.Except(_serialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
}
else if (eventType == EventType.Removal)
{
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
}
}
}
Поднятые события включают в себя последовательный порт, вставленный/удаленный, а не список последовательных портов, доступных после вставки/удаления.
Ответ 4
Вот урезанная версия класса уведомлений DeviceChangeEvents
, который я написал некоторое время назад, хотя я так и не завершил его полностью. Я удалил все, кроме события PortArrived, в противном случае это было бы довольно fugly.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public sealed class PortArrivalEventArgs : EventArgs
{
public string Name { get; private set; }
public PortArrivalEventArgs(string name) { Name = name; }
}
public static class DeviceChangeEvents
{
#region Events
#region PortArrived
private static object PortArrivedEvent = new Object();
public static event EventHandler<PortArrivalEventArgs> PortArrived
{
add { AddEvent(PortArrivedEvent, value); }
remove { RemoveEvent(PortArrivedEvent, value); }
}
private static void FirePortArrived(IntPtr lParam)
{
EventHandler<PortArrivalEventArgs> handler
= (EventHandler<PortArrivalEventArgs>)events[PortArrivedEvent];
if (handler != null)
{
string portName = Marshal.PtrToStringAuto((IntPtr)((long)lParam + 12));
handler(null, new PortArrivalEventArgs(portName));
}
}
#endregion
#endregion
#region Internal
private static EventHandlerList events = new EventHandlerList();
private static MessageWindow messageWindow = null;
private static void AddEvent(object key, Delegate value)
{
events.AddHandler(key, value);
if (messageWindow == null)
messageWindow = new MessageWindow();
}
private static void RemoveEvent(object key, Delegate value)
{
events.RemoveHandler(key, value);
// In the more complete version of DeviceChangedEvents, System.ComponentModel.EventHandlerList
// is replaced by an identical event storage object which exposes a count of the number of
// handlers installed. It also removes empty handler stubs. Both of these are required
// to safely destroy the message window when the last handler is removed.
//if (messageWindow != null && events.Count == 0)
// messageWindow.DestroyHandle();
}
#endregion
private sealed class MessageWindow : NativeWindow
{
public MessageWindow()
{
CreateParams cp = new CreateParams();
cp.Caption = GetType().FullName;
// NOTE that you cannot use a "message window" for this broadcast message
//if (Environment.OSVersion.Platform == PlatformID.Win32NT)
// cp.Parent = (IntPtr)(-3); // HWND_MESSAGE
//Debug.WriteLine("Creating MessageWindow " + cp.Caption);
CreateHandle(cp);
}
const int WM_DESTROY = 0x02;
const int WM_DEVICECHANGE = 0x219;
enum DBT
{
DEVICEARRIVAL = 0x8000,
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DESTROY)
{
messageWindow = null;
}
else if (m.Msg == WM_DEVICECHANGE)
{
DBT changeType = (DBT)m.WParam;
int deviceType = m.LParam == IntPtr.Zero ? 0 : Marshal.ReadInt32(m.LParam, 4);
Debug.WriteLine(String.Format("WM_DEVICECHANGE changeType = {0}, deviceType = {1}", changeType, deviceType));
switch (changeType)
{
case DBT.DEVICEARRIVAL:
switch (deviceType)
{
case 3: // DBT_DEVTYP_PORT
FirePortArrived(m.LParam);
break;
}
break;
}
}
base.WndProc(ref m);
}
}
}
Ответ 5
Ваше событие изменения устройства может использоваться с WMI - PNP Entity. После этого будут возвращены сведения о устройстве - в приведенном ниже коде отображается имя устройства.
Dim moReturn As Management.ManagementObjectCollection
Dim moSearch As Management.ManagementObjectSearcher
Dim mo As Management.ManagementObject
moSearch = New Management.ManagementObjectSearcher("Select * from Win32_PnPEntity")
moReturn = moSearch.Get
For Each mo In moReturn
If CStr(mo.Properties.Item("Name").Value).Contains("Prolific") Then
returns something like: "Prolific USB-to-Serial Comm Port (COM17)"
txtStatus.Text &= CStr(mo.Properties.Item("Name").Value) & vbCrLf
End If
Next
Также см. код для доступа к другим свойствам PNP, которые могут быть использованы для фильтрации или мониторинга для изменения:
On Error Resume Next
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_PnPEntity",,48)
For Each objItem in colItems
"Availability: " & objItem.Availability
"Caption: " & objItem.Caption
"ClassGuid: " & objItem.ClassGuid
"ConfigManagerErrorCode: " & objItem.ConfigManagerErrorCode
"ConfigManagerUserConfig: " & objItem.ConfigManagerUserConfig
"CreationClassName: " & objItem.CreationClassName
"Description: " & objItem.Description
"DeviceID: " & objItem.DeviceID
"ErrorCleared: " & objItem.ErrorCleared
"ErrorDescription: " & objItem.ErrorDescription
"InstallDate: " & objItem.InstallDate
"LastErrorCode: " & objItem.LastErrorCode
"Manufacturer: " & objItem.Manufacturer
"Name: " & objItem.Name
"PNPDeviceID: " & objItem.PNPDeviceID
"PowerManagementCapabilities: " & objItem.PowerManagementCapabilities
"PowerManagementSupported: " & objItem.PowerManagementSupported
"Service: " & objItem.Service
"Status: " & objItem.Status
"StatusInfo: " & objItem.StatusInfo
"SystemCreationClassName: " & objItem.SystemCreationClassName
"SystemName: " & objItem.SystemName
Next