2010-11-16 35 views
18

Tôi đang giao tiếp với cổng nối tiếp USB có thể được chèn vào hoặc xóa bất kỳ lúc nào. Tôi đã tìm thấy rằng tôi có thể sử dụng WMI (đặc biệt với việc sử dụng WMI Code Creator) để truy vấn các thay đổi thiết bị trong PC.Phát hiện chèn/xóa cổng nối tiếp

Trong đoạn mã được tạo bên dưới, Win32_DeviceChangeEvent được đăng ký. Tuy nhiên, sự kiện này không tiết lộ thiết bị nào (ví dụ: USB, cổng nối tiếp, v.v.) đã gây ra sự kiện. Có cách nào để chỉ nhận thông báo khi các cổng nối tiếp được chèn vào hoặc bị xóa không?

Để làm rõ, điểm của mã là không để phát hiện mở/đóng của cổng nối tiếp, nó là để phát hiện xem một cổng mới đã được thêm vào máy hoặc một cổng trước được loại bỏ.

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

    } 
} 

Trả lời

20

Tôi đã kết thúc bằng cách sử dụng lời khuyên WMI và @Hans 'để kiểm tra xem cổng nối tiếp nào mới/bị thiếu.

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

Phương pháp MonitorDeviceChanges thực sự nhìn thấy tất cả những thay đổi thiết bị (như Device Manager), nhưng kiểm tra các cổng nối tiếp cho phép chúng ta chỉ nâng cao một sự kiện khi những đã thay đổi.

Để sử dụng mã, chỉ cần đăng ký sự kiện PortsChanged, ví dụ: SerialPortService.PortsChanged += (sender1, changedArgs) => DoSomethingSerial(changedArgs.SerialPorts);

Oh, và phương pháp .Raise chỉ là một phương pháp khuyến nông tôi nhặt ở đâu đó:

/// <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); 
    } 
} 
+2

Xin chào, tôi đã cố gắng sử dụng mã này và tôi thấy rằng phần xóa không phải là tốt nhất bạn có thể nhận được. Khi ứng dụng có kết nối với thiết bị và bạn ngắt kết nối cáp, mã của bạn sẽ nhận thấy điều này. Tuy nhiên, bạn không thể thấy bất kỳ thay đổi nào vì chương trình đã không dọn sạch các cổng và 'GetPortNames' sẽ trả về phần mềm không còn khả dụng nữa. Tôi chỉ quan tâm đến sự kiện xóa, vì vậy tôi đang kiểm tra xem 'SerialPort' có đang mở hay không. Nếu cổng bị đóng, sự kiện xóa đã xảy ra. – 2pietjuh2

+1

@ 2pietjuh2 Nếu tôi hiểu bạn, bạn đã đúng. Điểm của mã không phải là để phát hiện mở/đóng cổng nối tiếp, nó là để phát hiện xem một cổng * mới * đã được thêm vào máy hay cổng trước đã được * gỡ bỏ *. Vì vậy, nó có thể được rằng bạn đang tìm kiếm một vấn đề khác nhau? – Pat

+0

Bạn có thể giải thích eventtype 2 và 3 là gì và các loại sự kiện khác tồn tại? –

2

No. Hãy tìm hiểu điều gì đã xảy ra với SerialPort.GetPortNames(). Lắng nghe thông báo WM_DEVICECHANGE trong cửa sổ có thể cung cấp cho bạn thông tin tốt hơn.

1

Dưới đây là phiên bản rút gọn của lớp thông báo DeviceChangeEvents tôi đã viết cách đây một thời gian, mặc dù tôi chưa bao giờ hoàn thành đầy đủ. Tôi đã loại bỏ tất cả mọi thứ ngoại trừ sự kiện PortArrived vì nó khá là hợp lý.

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); 
     } 
    } 
} 
+0

Cảm ơn bạn đã làm rõ ý nghĩa của @Hans bằng cách "Nghe tin nhắn WM_DEVICECHANGE trong cửa sổ" - Tôi không biết.Nhưng phải có một NativeWindow và mã không được quản lý thực sự không hấp dẫn tôi. – Pat

+1

Mỗi dòng mã của bạn thực hiện cuộc gọi đến mã không được quản lý. Không có thứ gì như một ứng dụng .NET thuần túy. Không ứng dụng nào có thể thực hiện công việc hữu ích mà không tương tác với hệ điều hành. – Tergiver

+0

Nếu bạn không thích NativeWindow (mà không có ý nghĩa như tất cả các đối tượng System.Windows.Forms.Control được dựa trên NativeWindow), bạn chỉ có thể ghi đè lên cửa sổ chính của bạn WndProc. Mục đích của lớp trên là tự gói gọn thông điệp. – Tergiver

0

kiện thay đổi thiết bị của bạn có thể được sử dụng với WMI - PNP Entity. Sau đây sẽ trả về chi tiết thiết bị - trong đoạn mã bên dưới, nó hiển thị tên thiết bị.

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 

Xem thêm mã để truy cập vào các thuộc tính PNP khác có thể được sử dụng để lọc hoặc theo dõi sự thay đổi:

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 
2

NB: Tôi cố gắng để đăng bài này như một bình luận trên @ câu trả lời Pat, nhưng don không có đủ danh tiếng để làm điều đó.

Tiếp tục để bình luận @ 2pietjuh2, những RaisePortsChangedIfNecessary() có thể được thay đổi như sau:

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

sự kiện Lớn lên sau đó bao gồm các cổng nối tiếp chèn/loại bỏ, chứ không phải là danh sách các cổng nối tiếp có sẵn sau khi chèn /gỡ bỏ.

Các vấn đề liên quan