Как получать уведомления устройства Plug & Play без формы Windows

Я пытаюсь написать библиотеку классов, которая может перехватывать сообщения Windows, чтобы уведомить меня, если устройство было подключено или удалено. Обычно в приложении Windows Forms я просто переопределяю метод WndProc, но в этом случае нет метода WndProc. Есть ли другой способ получить сообщения?


person PICyourBrain    schedule 13.01.2010    source источник


Ответы (3)


Вам понадобится окно, без него никуда. Вот пример реализации. Реализуйте обработчик события DeviceChangeNotifier.DeviceNotify для получения уведомлений. Вызовите метод DeviceChangeNotifier.Start() в начале вашей программы. Вызовите DeviceChangeNotifier.Stop() в конце вашей программы. Имейте в виду, что событие DeviceNotify вызывается в фоновом потоке, обязательно заблокируйте его по мере необходимости, чтобы ваш код был потокобезопасным.

using System;
using System.Windows.Forms;
using System.Threading;

class DeviceChangeNotifier : Form {
  public delegate void DeviceNotifyDelegate(Message msg);
  public static event DeviceNotifyDelegate DeviceNotify;
  private static DeviceChangeNotifier mInstance;

  public static void Start() {
    Thread t = new Thread(runForm);
    t.SetApartmentState(ApartmentState.STA);
    t.IsBackground = true;
    t.Start();
  }
  public static void Stop() {
    if (mInstance == null) throw new InvalidOperationException("Notifier not started");
    DeviceNotify = null;
    mInstance.Invoke(new MethodInvoker(mInstance.endForm));
  }
  private static void runForm() {
    Application.Run(new DeviceChangeNotifier());
  }

  private void endForm() {
    this.Close();
  }
  protected override void SetVisibleCore(bool value) {
    // Prevent window getting visible
    if (mInstance == null) CreateHandle();
    mInstance = this;
    value = false;
    base.SetVisibleCore(value);
  }
  protected override void WndProc(ref Message m) {
    // Trap WM_DEVICECHANGE
    if (m.Msg == 0x219) {
      DeviceNotifyDelegate handler = DeviceNotify;
      if (handler != null) handler(m);
    }
    base.WndProc(ref m);
  }
}
person Hans Passant    schedule 14.01.2010
comment
Я вижу, вы используете .Invoke. Это работает, если делегат не находится в пользовательском интерфейсе? - person Padu Merloti; 26.01.2010
comment
@nobugz, я использую это, и это работает, но по какой-то причине я получаю два сообщения о подключении устройства каждый раз, когда подключаю свое устройство. Есть идеи, почему? - person PICyourBrain; 25.02.2010
comment
@jordan - обратите внимание на значение wparam сообщения. - person Hans Passant; 25.02.2010
comment
Я идиот и дважды вызывал EventHandler... Извините, что потратил ваше время - person PICyourBrain; 25.02.2010
comment
Спасибо за это! Вы проходите снова! - person Brian Reinhold; 17.01.2014
comment
Обратите внимание, что это также работает, если на самом деле есть окно. Это удобно в библиотеке, которую можно вызывать из окон, консоли или любого другого приложения. - person tm1; 13.05.2015
comment
Просто добавим к этому, что если вы не заинтересованы в обработке HWND_BROADCAST, то наследования от System.Windows.Forms.Control будет достаточно. Однако, если вы хотите HWND_BROADCAST, вы должны использовать System.Windows.Forms.Form, чтобы гарантировать, что оно станет окном верхнего уровня в вашем приложении (иначе оно не будет получать никаких широковещательных сообщений). - person Sverrir Sigmundarson; 12.01.2016

У меня есть рабочий класс связи USB, который реализует уведомление об изменении устройства немного по-другому, если кому-то интересно. Он довольно компактный (без комментариев) и не зависит от Threading или OnSourceInitialized и HwndHandler в клиенте. Кроме того, вам не нужен Form или Window, как уже упоминалось. Можно использовать любой тип, в котором вы можете переопределить WndProc(). Я использую Control.

Образец содержит только код, необходимый для уведомления, и больше ничего. Пример кода — C++/CLI, и хотя я не поддерживаю практику помещения исполняемого кода в заголовочные файлы, для краткости я делаю это здесь.

#pragma once

#include <Windows.h>    // Declares required datatypes.
#include <Dbt.h>        // Required for WM_DEVICECHANGE messages.
#include <initguid.h>   // Required for DEFINE_GUID definition (see below).

namespace USBComms 
{
    using namespace System;
    using namespace System::Runtime::InteropServices;
    using namespace System::Windows;
    using namespace System::Windows::Forms;

    // This function is required for receieving WM_DEVICECHANGE messages.
    // Note: name is remapped "RegisterDeviceNotificationUM"
    [DllImport("user32.dll" , CharSet = CharSet::Unicode, EntryPoint="RegisterDeviceNotification")]                 
    extern "C" HDEVNOTIFY WINAPI RegisterDeviceNotificationUM(
        HANDLE hRecipient,
        LPVOID NotificationFilter,
        DWORD Flags);

    // Generic guid for usb devices (see e.g. http://msdn.microsoft.com/en-us/library/windows/hardware/ff545972%28v=vs.85%29.aspx).
    // Note: GUIDs are device and OS specific and may require modification. Using the wrong guid will cause notification to fail.
    // You may have to tinker with your device to find the appropriate GUID. "hid.dll" has a function `HidD_GetHidGuid' that returns
    // "the device interfaceGUID for HIDClass devices" (see http://msdn.microsoft.com/en-us/library/windows/hardware/ff538924%28v=vs.85%29.aspx).
    // However, testing revealed it does not always return a useful value. The GUID_DEVINTERFACE_USB_DEVICE value, defined as
    // {A5DCBF10-6530-11D2-901F-00C04FB951ED}, has worked with cell phones, thumb drives, etc. For more info, see e.g.
    // http://msdn.microsoft.com/en-us/library/windows/hardware/ff553426%28v=vs.85%29.aspx. 
    DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);

    /// <summary>
    /// Declare a delegate for the notification event handler.
    /// </summary>
    /// <param name="sender">The object where the event handler is attached.</param>
    /// <param name="e">The event data.</param>
    public delegate void NotificationEventHandler(Object^ sender, EventArgs^ e);

    /// <summary>
    /// Class that generetaes USB Device Change notification events.
    /// </summary>
    /// <remarks>
    /// A Form is not necessary. Any type wherein you can override WndProc() can be used.
    /// </remarks>
    public ref class EventNotifier : public Control
    {
    private:
        /// <summary>
        /// Raises the NotificationEvent.
        /// </summary>
        /// <param name="e">The event data.</param>
        void RaiseNotificationEvent(EventArgs^ e) {
            NotificationEvent(this, e);
        }

    protected:
        /// <summary>
        /// Overrides the base class WndProc method.
        /// </summary>
        /// <param name="message">The Windows Message to process. </param>
        /// <remarks>
        /// This method receives Windows Messages (WM_xxxxxxxxxx) and
        /// raises our NotificationEvent as appropriate. Here you should
        /// add any message filtering (e.g. for the WM_DEVICECHANGE) and
        /// preprocessing before raising the event (or not).
        /// </remarks>
        virtual void WndProc(Message% message) override {
            if(message.Msg == WM_DEVICECHANGE)
            {
                RaiseNotificationEvent(EventArgs::Empty);
            }
            __super::WndProc(message);
        }

    public:
        /// <summary>
        /// Creates a new instance of the EventNotifier class.
        /// </summary>
        EventNotifier(void) {
            RequestNotifications(this->Handle); // Register ourselves as the Windows Message processor.
        }

        /// <summary>
        /// Registers an object, identified by the handle, for
        /// Windows WM_DEVICECHANGE messages.
        /// </summary>
        /// <param name="handle">The object's handle.</param>
        bool RequestNotifications(IntPtr handle) {
            DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;

            ZeroMemory(&NotificationFilter, sizeof(NotificationFilter));
            NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
            NotificationFilter.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
            NotificationFilter.dbcc_reserved = 0;
            NotificationFilter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE;
            return RegisterDeviceNotificationUM((HANDLE)handle, &NotificationFilter, DEVICE_NOTIFY_WINDOW_HANDLE) != NULL;
        }

        /// <summary>
        /// Defines the notification event.
        /// </summary>
        virtual event NotificationEventHandler^ NotificationEvent;
    };
}

Затем в «приемнике» (объекте, который подписывается и потребляет наш NotificationEvent) все, что вам нужно сделать, это:

void Receiver::SomeFunction(void)
{
    USBComms::EventNotifier usb = gcnew USBComms::EventNotifier();

    usb->NotificationEvent += gcnew USBComms::NotificationEventHandler(this, &Receiver::USBEvent);
}

void Receiver::USBEvent(Object^ sender, EventArgs^ e)
{
    // Handle the event notification as appropriate.
}
person Michael Brodsky    schedule 02.10.2014
comment
Form происходит от Control, и Control в любом случае оборачивает экземпляр окна win32. Единственный метод, не основанный на окне, который я нашел, - это использовать ManagementEventWatcher. Но тут, похоже, есть свои проблемы. - person Felix; 18.01.2017

В проектах Windows CE/Windows Mobile/SmartDevice стандартный Form не обеспечивает переопределения метода WndProc, но это можно сделать, создав класс на основе Microsoft.WindowsCE.Forms.MessageWindow, создав конструктор, который принимает форму, хранит эту форму в локальном переменная, чтобы метод в этой форме мог вызываться при каждом обнаружении сообщения. Вот уменьшенный образец для иллюстрации. Надеюсь, это будет полезно кому-то в мире CE/Windows Mobile.

  public class MsgWindow : Microsoft.WindowsCE.Forms.MessageWindow {

    public const int WM_SER = 0x500;
    public const int WM_SER_SCANDONE = WM_SER + 0;

    frmMain msgform { get; set; }

    public MsgWindow(frmMain msgform) {
      this.msgform = msgform;
    }

    protected override void WndProc(ref Microsoft.WindowsCE.Forms.Message m) {
      switch (m.Msg) {
        case WM_SER_SCANDONE:
          this.msgform.RespondToMessage(WM_SER_SCANDONE);
          break;
        default:
          break;
      }
      base.WndProc(ref m);
    }

  }

  public partial class frmMain : Form {

    public frmMain() {
      InitializeComponent();
    }

    public void RespondToMessage(int nMsg) {
      try {
        switch (nMsg) {
          case MsgWindow.WM_SER_SCANDONE:
            // do something here based on the message
            break;
          default:
            break;
        }
      } catch (Exception ex) {
        MessageBox.Show(string.Format("{0} - {1}", ex.Message, ex.ToString()), "RespondToMessage() Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1);
        // throw;
      }
    }

  }
person Michael Murphy    schedule 18.06.2015