Apok
Apok

Reputation: 101

How to hook into WPF window handle to listen to USB events

I had a need at work to hook into Windows events for a WPF application. I needed to listen to USB events. I've found scattered and incomplete answers, so I wanted to document my approach in a consolidated location.

My original problem occurred when trying to replicate the code example here:
https://stackoverflow.com/a/19435744/1683999

I was able to hook into Windows events and receive device notifications, however they were very generic and didn't give me much information that I could use for my application.

Further reading on that page led me to a different answer on the same page that hooked directly into the window handle to monitor events:
https://stackoverflow.com/a/620179/1683999

This answer gave a link to:
https://www.codeproject.com/Articles/3946/Trapping-windows-messages

By following the codeproject tutorial with a bit of modification to hook into WPF's window handle I was able to get WM_DEVICECHANGE messages, but when decoding wParam, I was only receiving DBT_DEVNODES_CHANGED since I wasn't registered to listen to USB events. A quick Google search led me to an old MSDN forums post:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/983dc1ee-6208-4036-903f-3fd5674a1efb/registerdevicenotification-in-wpf?forum=wpf

In this thread, I found the answer I was looking for. I wasn't registering the Window to look specifically for USB events, so I was getting a general event code from Windows. Further research led me back to StackOverflow:
https://stackoverflow.com/a/16245901/1683999

This last answer completed the puzzle for me. I have provided code snippets outlining what was needed to listen for Windows events by hooking into the WPF window and then creating a listener that registers the window to listen for USB events.

Upvotes: 3

Views: 1951

Answers (2)

ProgramistaZaDyche
ProgramistaZaDyche

Reputation: 266

Note that "OnSourceInitialized" should look like that in Apok answer:

 base.OnSourceInitialized(e);
 HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
 source.AddHook(WndProc);

Otherwise, it will trigger each message twice

private const int WM_DEVICECHANGE = 0x219;
    private const int DBT_DEVICEARRIVAL = 0x8000;
    private const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
    private const int DBT_DEVTYP_VOLUME = 0x00000002;
    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        switch (msg)
        {
            case WM_DEVICECHANGE:
               int parameter = wParam.ToInt32();
                switch (parameter)
                {
                    case DBT_DEVICEARRIVAL:
                        MessageBox.Show("New device arrived!");
                        break;

                    case DBT_DEVICEREMOVECOMPLETE:
                        MessageBox.Show("Device removed!");
                        break;
                }
                break;
        }
        return IntPtr.Zero;
    }

Upvotes: 0

Apok
Apok

Reputation: 101

To access USB events, we need several things.

First we need to link to some methods in user32.dll.

namespace Example
{
    // https://www.pinvoke.net/default.aspx/Structures.DEV_BROADCAST_DEVICEINTERFACE
    [StructLayout(LayoutKind.Sequential)]
    public struct DEV_BROADCAST_DEVICE_INTERFACE
    {
        public int Size;
        public int DeviceType;
        public int Reserved;
        public Guid ClassGuid;
        public short Name;
    }

    public class Win32Native
    {
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr RegisterDeviceNotification(
            IntPtr hRecipient,
            IntPtr notificationFilter,
            uint flags);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        public static extern uint UnregisterDeviceNotification(IntPtr hHandle);
    }
}

We need this to be able to register our WPF window to listen to USB events.

namespace Example
{
    public class UsbEventRegistration : IDisposable
    {
        private const int DBT_DEVTYP_DEVICEINTERFACE = 5;

        private static readonly s_guidDevInterfaceUsbDevice =
            new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED");

        private readonly IntPtr _windowHandle;
        private IntPtr _notificationHandle = IntPtr.Zero;

        public bool IsRegistered => _notificationHandle != IntPtr.Zero;

        public UsbEventRegistration(IntPtr windowHandle)
        {
            _windowHandle = windowHandle;
        }

        public void Register()
        {
            var dbdi = new DEV_BROADCAST_DEVICE_INTERFACE
            {
                DeviceType = DBT_DEVTYP_DEVICEINTERFACE,
                Reserved = 0,
                ClassGuid = s_guidDevInterfaceUsbDevice,
                Name = 0,
            };
            dbdi.Size = Marshal.SizeOf(dbdi);

            IntPtr buffer = Marshal.AllocHGlobal(dbdi.Size);
            Marshal.StructureToPtr(dbdi, buffer, true);
            _notificationHandle = Win32Native.RegisterDeviceNotification(
                _windowHandle, 
                buffer, 
                0);
        }

        // Call on window unload.
        public void Dispose()
        {
            Win32Native.UnregisterDeviceNotification(_notificationHandle);
        }
    }
}

Finally, get your WPF window code-behind ready.

namespace Example
{
    public partial class WPFWindow : Window
    {

        private UsbEventRegistration _usbEventRegistration;

        public WPFWindow()
        {
            InitializeComponent();
        }

        protected override void OnSourceInitialized(EventArgs e)
        {
            base.OnSourceInitialized(e);

            // IMO this should be abstracted away from the code-behind.
            var windowSource = (HwndSource)PresentationSource.FromVisual(this);
            _usbEventRegistration = new UsbEventRegistration(windowSource.Handle);
           // This will allow your window to receive USB events.
           _usbEventRegistration.Register();
           // This hook is what we were aiming for. All Windows events are listened to here. We can inject our own listeners.
           windowSource.AddHook(WndProc);
        }

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            // Here's where the help ends. Do what you need here.
            // Get additional info from http://www.pinvoke.net/
            // USB event message is msg == 0x0219 (WM_DEVICECHANGE).
            // USB event plugin is wParam == 0x8000 (DBT_DEVICEARRIVAL).
            // USB event unplug is wParam == 0x8004 (DBT_DEVICEREMOVECOMPLETE).
            // Your device info is in lParam. Filter that.
            // You need to convert wParam/lParam to Int32 using Marshal.
            return IntPtr.Zero;
        }
    }
}

Upvotes: 5

Related Questions