如何在没有Windows表单的情况下接收即插即用设备通知

35

我正在尝试编写一个类库,可以捕获Windows消息来通知我设备是否已连接或已移除。通常,在Windows窗体应用程序中,我会重写WndProc方法,但在这种情况下没有WndProc方法。是否有其他方法可以获取这些消息?

3个回答

43

你需要一个窗口,这是不可避免的。以下是一个示例实现。为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);
  }
}

@jordan - 注意消息的wparam值。 - Hans Passant
我是个傻瓜,调用了两次 EventHandler... 很抱歉浪费了你的时间。 - PICyourBrain
谢谢你!你再次帮了我一个大忙! - Brian Reinhold
请注意,如果已经存在一个窗口,这也适用。这在可以从窗口、控制台或任何应用程序调用的库中非常方便。 - tm1
补充一下,如果您不想处理HWND_BROADCAST,那么从System.Windows.Forms.Control继承就足够了。但是,如果您需要HWND_BROADCAST,则必须使用System.Windows.Forms.Form来确保它成为应用程序中的顶级窗口(否则它将无法接收任何广播消息)。 - Sverrir Sigmundarson
显示剩余2条评论

7
我有一个工作中的USB通讯类,如果有人感兴趣,可以实现设备更改通知。它非常紧凑(没有注释),不依赖于Threading或客户端中的OnSourceInitializedHwndHandler等内容。此外,您不需要像提到的那样使用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.
}

表单(Form)是从控件(Control)继承而来的,而控件则包装了一个Win32窗口实例。目前我所发现的唯一非基于窗口的方法是使用ManagementEventWatcher。但这似乎也存在其自身的问题。 - Felix

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

  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接