-->

Handling Windows USB events

2019-03-01 13:52发布

问题:

I created a generic USB HID comms driver capable of monitoring USB events and sending/receiving data packets to and from devices. I've been using it successfully since my WinForms days as much of the code was culled from MSDN and implemented very similarly to the way suggested here. My question is: The examples I see continue to rely on what seem to be the old "Windows Messaging" architecture, i.e. System.Runtime.InteropServices, WM_xxxxxxx type messages, registering/handling these in a purely C# WPF app with HwndHandler, overriding WndProc, etc. I was wondering is there not a way to handle USB events using a more modern, event-driven approach, say with delegates, subscribing to EventHandlers, etc? Even this SO posts suggest refraining from using such archaic methods and I imagine there must be a solution for USB events as well. Thanx.

回答1:

After doing a bit of research, it seems that Windows messaging is a necessary part of handling USB Device Change notifications. I have a working usb comms driver class which includes a simple implementation. This solution consists of two parts, 1) the EventNotifier class, which generates the events and 2) the 'receiver', which subscribes to the events (i.e. a client that gets notified of USB events). The sample code is C++/CLI and although I don't subscribe to the practice of putting executable code in header files, for the sake of brevity, I do so here.

#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>
        void RequestNotifications(IntPtr handle) {
            GUID InterfaceClassGuid = GUID_DEVINTERFACE_USB_DEVICE;
            DEV_BROADCAST_DEVICEINTERFACE MyDeviceBroadcastHeader;

            MyDeviceBroadcastHeader.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
            MyDeviceBroadcastHeader.dbcc_size = sizeof(DEV_BROADCAST_DEVICEINTERFACE);
            MyDeviceBroadcastHeader.dbcc_reserved = 0;
            MyDeviceBroadcastHeader.dbcc_classguid = InterfaceClassGuid;
            RegisterDeviceNotificationUM((HANDLE)handle, &MyDeviceBroadcastHeader, DEVICE_NOTIFY_WINDOW_HANDLE);
        }

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

Then, in the 'receiver' (the object that subscribes to and consumes our NotificationEvent, all you have to do is:

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