web-dev-qa-db-fra.com

Vérifier les événements de changement de périphérique (ajout / suppression)

Je voudrais savoir s'il existe un moyen de déclencher un événement lorsqu'un périphérique est ajouté ou supprimé du système. Je veux pouvoir détecter si, disons, un lecteur flash USB a été ajouté, ou une souris, ou autre chose. J'ai essayé de chercher autour, mais je ne trouve rien qui dise comment faire ça.

Des idées?

34
Andi Jay

Si vous avez une fenêtre dans votre application, vous pouvez utiliser quelque chose comme ceci:

using System;
using System.Runtime.InteropServices;

internal static class UsbNotification
{
    public const int DbtDevicearrival = 0x8000; // system detected a new device        
    public const int DbtDeviceremovecomplete = 0x8004; // device is gone      
    public const int WmDevicechange = 0x0219; // device change event      
    private const int DbtDevtypDeviceinterface = 5;
    private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
    private static IntPtr notificationHandle;

    /// <summary>
    /// Registers a window to receive notifications when USB devices are plugged or unplugged.
    /// </summary>
    /// <param name="windowHandle">Handle to the window receiving notifications.</param>
    public static void RegisterUsbDeviceNotification(IntPtr windowHandle)
    {
        DevBroadcastDeviceinterface dbi = new DevBroadcastDeviceinterface
        {
            DeviceType = DbtDevtypDeviceinterface,
            Reserved = 0,
            ClassGuid = GuidDevinterfaceUSBDevice,
            Name = 0
        };

        dbi.Size = Marshal.SizeOf(dbi);
        IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
        Marshal.StructureToPtr(dbi, buffer, true);

        notificationHandle = RegisterDeviceNotification(windowHandle, buffer, 0);
    }

    /// <summary>
    /// Unregisters the window for USB device notifications
    /// </summary>
    public static void UnregisterUsbDeviceNotification()
    {
        UnregisterDeviceNotification(notificationHandle);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

    [DllImport("user32.dll")]
    private static extern bool UnregisterDeviceNotification(IntPtr handle);

    [StructLayout(LayoutKind.Sequential)]
    private struct DevBroadcastDeviceinterface
    {
        internal int Size;
        internal int DeviceType;
        internal int Reserved;
        internal Guid ClassGuid;
        internal short Name;
    }
}

Voici comment vous l'utilisez à partir d'une fenêtre WPF (Windows Forms est similaire):

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

        // Adds the windows message processing hook and registers USB device add/removal notification.
        HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        if (source != null)
        {
            windowHandle = source.Handle;
            source.AddHook(HwndHandler);
            UsbNotification.RegisterUsbDeviceNotification(windowHandle);
        }
    }

    /// <summary>
    /// Method that receives window messages.
    /// </summary>
    private IntPtr HwndHandler(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
    {
        if (msg == UsbNotification.WmDevicechange)
        {
            switch ((int)wparam)
            {
                case UsbNotification.DbtDeviceremovecomplete:
                    Usb_DeviceRemoved(); // this is where you do your magic
                    break;
                case UsbNotification.DbtDevicearrival:
                    Usb_DeviceAdded(); // this is where you do your magic
                    break;
            }
        }

        handled = false;
        return IntPtr.Zero;
    }

Voici l'exemple d'utilisation des formulaires Windows (encore plus simple):

public Form1()
{
    InitializeComponent();
    UsbNotification.RegisterUsbDeviceNotification(this.Handle);
}

protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);
        if (m.Msg == UsbNotification.WmDevicechange)
    {
        switch ((int)m.WParam)
        {
            case UsbNotification.DbtDeviceremovecomplete:
                Usb_DeviceRemoved(); // this is where you do your magic
                break;
            case UsbNotification.DbtDevicearrival:
                Usb_DeviceAdded(); // this is where you do your magic
                break;
        }
    }
}   
66
Darko Kenda

La réponse acceptée est excellente, mais elle ne fonctionne qu'avec des périphériques USB.

Pour le faire fonctionner avec tous les appareils (et éventuellement filtrer USB), utilisez la classe légèrement modifiée suivante:

static class DeviceNotification {
    //https://msdn.Microsoft.com/en-us/library/aa363480(v=vs.85).aspx
    public const int DbtDeviceArrival = 0x8000; // system detected a new device        
    public const int DbtDeviceRemoveComplete = 0x8004; // device is gone     
    public const int DbtDevNodesChanged = 0x0007; //A device has been added to or removed from the system.

    public const int WmDevicechange = 0x0219; // device change event      
    private const int DbtDevtypDeviceinterface = 5;
    //https://msdn.Microsoft.com/en-us/library/aa363431(v=vs.85).aspx
    private const int DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
    private static readonly Guid GuidDevinterfaceUSBDevice = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); // USB devices
    private static IntPtr notificationHandle;

    /// <summary>
    /// Registers a window to receive notifications when devices are plugged or unplugged.
    /// </summary>
    /// <param name="windowHandle">Handle to the window receiving notifications.</param>
    /// <param name="usbOnly">true to filter to USB devices only, false to be notified for all devices.</param>
    public static void RegisterDeviceNotification(IntPtr windowHandle, bool usbOnly = false) {
        var dbi = new DevBroadcastDeviceinterface {
            DeviceType = DbtDevtypDeviceinterface,
            Reserved = 0,
            ClassGuid = GuidDevinterfaceUSBDevice,
            Name = 0
        };

        dbi.Size = Marshal.SizeOf(dbi);
        IntPtr buffer = Marshal.AllocHGlobal(dbi.Size);
        Marshal.StructureToPtr(dbi, buffer, true);

        notificationHandle = RegisterDeviceNotification(windowHandle, buffer, usbOnly ? 0 : DEVICE_NOTIFY_ALL_INTERFACE_CLASSES);
    }

    /// <summary>
    /// Unregisters the window for device notifications
    /// </summary>
    public static void UnregisterDeviceNotification() {
        UnregisterDeviceNotification(notificationHandle);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern IntPtr RegisterDeviceNotification(IntPtr recipient, IntPtr notificationFilter, int flags);

    [DllImport("user32.dll")]
    private static extern bool UnregisterDeviceNotification(IntPtr handle);

    [StructLayout(LayoutKind.Sequential)]
    private struct DevBroadcastDeviceinterface {
        internal int Size;
        internal int DeviceType;
        internal int Reserved;
        internal Guid ClassGuid;
        internal short Name;
    }
}

Le changement de clé est le paramètre Flags lors de l'appel de RegisterDeviceNotification (voir https://msdn.Microsoft.com/en-us/library/aa363431 (v = vs.85) .aspx ), qui, s'il est défini sur 4 au lieu de 0 ignorera le paramètre ClassGuid et s'enregistrera pour tous les périphériques.

8
Erwin Mayer

Je suis venu à ce poste pour un cas plus spécifique que la question d'origine en ce sens que je veux être averti chaque fois qu'un port est ajouté ou supprimé. Pour cette situation, la réponse est beaucoup plus simple et ne nécessite pas d'appeler RegisterDeviceNotification:

Les événements DBT_DEVICEARRIVAL et DBT_DEVICEREMOVECOMPLETE sont automatiquement diffusés à toutes les fenêtres de niveau supérieur pour les périphériques de port. Par conséquent, il n'est pas nécessaire d'appeler RegisterDeviceNotification pour les ports ....

https://docs.Microsoft.com/en-us/windows/desktop/api/Winuser/nf-winuser-registerdevicenotificationa

La solution devient donc quelque chose comme:

using System.Runtime.InteropServices;

//Put all of the following code inside your Form's partial class:

private struct DEV_BROADCAST_HDR {
    internal UInt32 dbch_size;
    internal UInt32 dbch_devicetype;
    internal UInt32 dbch_reserved;
};

protected override void WndProc(ref Message m) {
    base.WndProc(ref m);        //This allows window default behavior of base class to be executed
    if (m.Msg == 0x0219) {      //WM_DEVICECHANGE = 0x0219
        DEV_BROADCAST_HDR dbh;
        switch ((int)m.WParam) {                    
            case 0x8000:        //DBT_DEVICEARRIVAL = 0x8000
                dbh = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
                if (dbh.dbch_devicetype == 0x00000003) {     //DBT_DEVTYP_PORT = 0x00000003
                    Console.WriteLine("Port added!");
                    //TODO
                }
                break;
            case 0x8004:        //DBT_DEVICEREMOVECOMPLETE = 0x8004                     
                dbh = (DEV_BROADCAST_HDR)Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HDR));
                if (dbh.dbch_devicetype == 0x00000003) {     //DBT_DEVTYP_PORT = 0x00000003
                    Console.WriteLine("Port removed!");
                    //TODO
                }
                break;
        }
    }
}
1
DatuPuti