webdevqa.jp.net

シリアルポートの挿入/取り外しを検出する

いつでも挿入または取り外しできるUSB-シリアルポートに接続しています。 [〜#〜] wmi [〜#〜] (特に WMI Code Creator を使用)を使用して、デバイスの変更をクエリできることがわかりました。 PC。

以下で生成されたスニペットでは、 Win32_DeviceChangeEvent がサブスクライブされています。ただし、このイベントでは、どのデバイス(USB、シリアルポートなど)がイベントを引き起こしたかはわかりません。シリアルポートが挿入または削除されたときにのみ通知を受信する方法はありますか?

明確にするために、コードのポイントは not シリアルポートの開閉を検出することであり、新しいポートがマシンに追加されたか、以前のポートが削除されました

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

    }
}
21
Pat

最終的に、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メソッドは実際にはすべてのデバイスの変更(デバイスマネージャーなど)を確認しますが、シリアルポートを確認すると、変更された場合にのみイベントを発生させることができます。

コードを使用するには、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's true)
    EventHandler<T> copy = handler;
    if (copy != null)
    {
        copy(sender, args);
    }
}
_
21
Pat

注意:@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));
        }
    }
}

発生したイベントには、挿入/削除後に使用可能なシリアルポートのリストではなく、挿入/削除されたシリアルポートが含まれます。

2
Journeyman

いいえ。SerialPort.GetPortNames()に何が起こったのか調べてください。ウィンドウで WM_DEVICECHANGE メッセージをリッスンすると、より良い情報が得られます。

2
Hans Passant

これは、私が完全に完了したことはありませんが、しばらく前に書いたDeviceChangeEvents通知クラスの簡略版です。 PortArrivedイベント以外は非常に曖昧なので、すべてを削除しました。

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);
        }
    }
}
1
Tergiver

デバイス変更イベントは、WMI-PNPエンティティで使用できます。以下はデバイスの詳細を返します-以下のコードではデバイス名を示しています。

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
0
alyeomans