Подтвердить что ты не робот

Обнаружение ввода/удаления последовательного порта

Я взаимодействую с портом 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;
        }

    }
}
4b9b3361

Ответ 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