dotnet / pinvoke

A library containing all P/Invoke code so you don't have to import it every time. Maintained and updated to support the latest Windows OS.
MIT License
2.12k stars 222 forks source link

Request RegisterDeviceNotification and related definitions. #392

Closed ghost closed 1 year ago

ghost commented 6 years ago

RegisterDeviceNotification.

Is there a way to automatically generate signatures and definitions? I explored the code a little bit but could not figure out for sure. I'm not sure of the best way to wrap calls in managed classes. My experience has been that it is almost impossible to use the managed classes.

AArnott commented 6 years ago

Please review our contributing document for how to add APIs.

We welcome code contributions to add the APIs you're missing.

ghost commented 6 years ago

I've done a simple translation of the entirety of Dbt.h. It may be missing some constants. What should be changed? Off of the top of my head I can think of two things:

  1. Switch out GUID for the standard Guid if it will marshal correctly. (A quick test shows that it does, but there are definitely events which are registered by default besides COM ports.)
  2. Move the symbolic constants into enumerations.

I'm not sure how best to proceed on number 2. The GUID is for HID devices if anyone has time to test.

class Dbt
{
    public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x0000;

    public const int WM_DEVICECHANGE = 0x0219;
    public const int BSM_ALLCOMPONENTS = 0x00000000;
    public const int BSM_VXDS = 0x00000001;
    public const int BSM_NETDRIVER = 0x00000002;
    public const int BSM_INSTALLABLEDRIVERS = 0x00000004;
    public const int BSM_APPLICATIONS = 0x00000008;
    public const int BSF_QUERY = 0x00000001;
    public const int BSF_IGNORECURRENTTASK = 0x00000002;
    public const int BSF_FLUSHDISK = 0x00000004;
    public const int BSF_NOHANG = 0x00000008;
    public const int BSF_POSTMESSAGE = 0x00000010;
    public const int BSF_FORCEIFHUNG = 0x00000020;
    public const int BSF_NOTIMEOUTIFNOTHUNG = 0x00000040;

    /// DBTFAR -> far
    public const string DBTFAR = far;

    public const int BSF_MSGSRV32ISOK = unchecked((int)0x80000000);
    public const int BSF_MSGSRV32ISOK_BIT = 31;

    public const int DBT_APPYBEGIN = 0x0000;
    public const int DBT_APPYEND = 0x0001;
    public const int DBT_DEVNODES_CHANGED = 0x0007;
    public const int DBT_QUERYCHANGECONFIG = 0x0017;
    public const int DBT_CONFIGCHANGED = 0x0018;
    public const int DBT_CONFIGCHANGECANCELED = 0x0019;
    public const int DBT_MONITORCHANGE = 0x001B;
    public const int DBT_SHELLLOGGEDON = 0x0020;
    public const int DBT_CONFIGMGAPI32 = 0x0022;
    public const int DBT_VXDINITCOMPLETE = 0x0023;

    public const int DBT_VOLLOCKQUERYLOCK = 0x8041;
    public const int DBT_VOLLOCKLOCKTAKEN = 0x8042;
    public const int DBT_VOLLOCKLOCKFAILED = 0x8043;
    public const int DBT_VOLLOCKQUERYUNLOCK = 0x8044;
    public const int DBT_VOLLOCKLOCKRELEASED = 0x8045;
    public const int DBT_VOLLOCKUNLOCKFAILED = 0x8046;

    public const int LOCKP_ALLOW_WRITES = 0x01;
    public const int LOCKP_FAIL_WRITES = 0x00;
    public const int LOCKP_FAIL_MEM_MAPPING = 0x02;
    public const int LOCKP_ALLOW_MEM_MAPPING = 0x00;
    public const int LOCKP_USER_MASK = 0x03;
    public const int LOCKP_LOCK_FOR_FORMAT = 0x04;
    public const int LOCKF_LOGICAL_LOCK = 0x00;
    public const int LOCKF_PHYSICAL_LOCK = 0x01;

    public const int DBT_NO_DISK_SPACE = 0x0047;
    public const int DBT_LOW_DISK_SPACE = 0x0048;
    public const int DBT_CONFIGMGPRIVATE = 0x7FFF;
    public const int DBT_DEVICEARRIVAL = 0x8000;
    public const int DBT_DEVICEQUERYREMOVE = 0x8001;
    public const int DBT_DEVICEQUERYREMOVEFAILED = 0x8002;
    public const int DBT_DEVICEREMOVEPENDING = 0x8003;
    public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
    public const int DBT_DEVICETYPESPECIFIC = 0x8005;
    public const int DBT_CUSTOMEVENT = 0x8006;

    public const int DBT_DEVTYP_OEM = 0x00000000;
    public const int DBT_DEVTYP_DEVNODE = 0x00000001;
    public const int DBT_DEVTYP_VOLUME = 0x00000002;
    public const int DBT_DEVTYP_PORT = 0x00000003;
    public const int DBT_DEVTYP_NET = 0x00000004;
    public const int DBT_DEVTYP_DEVICEINTERFACE = 0x00000005;
    public const int DBT_DEVTYP_HANDLE = 0x00000006;

    public const int DBTF_MEDIA = 0x0001;
    public const int DBTF_NET = 0x0002;
    public const int DBTF_RESOURCE = 0x00000001;
    public const int DBTF_XPORT = 0x00000002;
    public const int DBTF_SLOWNET = 0x00000004;

    public const int DBT_VPOWERDAPI = 0x8100;
    public const int DBT_USERDEFINED = 0xFFFF;

    /// far -> 
    /// Error generating expression: Value cannot be null.
    ///Parameter name: node
    public const string far = "";

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_HDR
    {
        public uint dbch_size;
        public uint dbch_devicetype;
        public uint dbch_reserved;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct VolLockBroadcast
    {
        public DEV_BROADCAST_HDR vlb_dbh;
        public uint vlb_owner;
        public byte vlb_perms;
        public byte vlb_lockType;
        public byte vlb_drive;
        public byte vlb_flags;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_OEM
    {
        public uint dbco_size;
        public uint dbco_devicetype;
        public uint dbco_reserved;
        public uint dbco_identifier;
        public uint dbco_suppfunc;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_DEVNODE
    {
        public uint dbcd_size;
        public uint dbcd_devicetype;
        public uint dbcd_reserved;
        public uint dbcd_devnode;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_VOLUME
    {
        public uint dbcv_size;
        public uint dbcv_devicetype;
        public uint dbcv_reserved;
        public uint dbcv_unitmask;
        public ushort dbcv_flags;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct DEV_BROADCAST_PORT_A
    {
        public uint dbcp_size;
        public uint dbcp_devicetype;
        public uint dbcp_reserved;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)]
        public string dbcp_name;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    public struct DEV_BROADCAST_PORT
    {
        public uint dbcp_size;
        public uint dbcp_devicetype;
        public uint dbcp_reserved;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)]
        public string dbcp_name;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_NET
    {
        public uint dbcn_size;
        public uint dbcn_devicetype;
        public uint dbcn_reserved;
        public uint dbcn_resource;
        public uint dbcn_flags;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct DEV_BROADCAST_DEVICEINTERFACE_A
    {
        public uint dbcc_size;
        public uint dbcc_devicetype;
        public uint dbcc_reserved;
        public GUID dbcc_classguid;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)]
        public string dbcc_name;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    public struct DEV_BROADCAST_DEVICEINTERFACE
    {
        public uint dbcc_size;
        public uint dbcc_devicetype;
        public uint dbcc_reserved;
        public GUID dbcc_classguid;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)]
        public string dbcc_name;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_HANDLE
    {
        public uint dbch_size;
        public uint dbch_devicetype;
        public uint dbch_reserved;
        public System.IntPtr dbch_handle;
        public System.IntPtr dbch_hdevnotify;
        public GUID dbch_eventguid;
        public int dbch_nameoffset;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1)]
        public byte[] dbch_data;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_HANDLE32
    {
        public uint dbch_size;
        public uint dbch_devicetype;
        public uint dbch_reserved;
        public uint dbch_handle;
        public uint dbch_hdevnotify;
        public GUID dbch_eventguid;
        public int dbch_nameoffset;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1)]
        public byte[] dbch_data;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
    public struct DEV_BROADCAST_HANDLE64
    {
        public uint dbch_size;
        public uint dbch_devicetype;
        public uint dbch_reserved;
        public ulong dbch_handle;
        public ulong dbch_hdevnotify;
        public GUID dbch_eventguid;
        public int dbch_nameoffset;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 1, ArraySubType = System.Runtime.InteropServices.UnmanagedType.I1)]
        public byte[] dbch_data;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct _DEV_BROADCAST_USERDEFINED
    {
        public DEV_BROADCAST_HDR dbud_dbh;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 1)]
        public string dbud_szName;
    }

    [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential, CharSet = System.Runtime.InteropServices.CharSet.Ansi)]
    public struct GUID
    {
        public uint Data1;
        public ushort Data2;
        public ushort Data3;
        [System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.ByValTStr, SizeConst = 8)]
        public string Data4;
    }

    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient,
        IntPtr NotificationFilter, uint Flags);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern bool UnregisterDeviceNotification(IntPtr Handle);
}

In any case, it is possible to properly register for events:

protected void RegisterDeviceNotification(GUID guid)
{
    DEV_BROADCAST_DEVICEINTERFACE dbdi = new DEV_BROADCAST_DEVICEINTERFACE
    {
        dbcc_size = (uint)Marshal.SizeOf(new DEV_BROADCAST_DEVICEINTERFACE()),
        dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE,
        dbcc_classguid = guid
    };
    IntPtr dibuf = Marshal.AllocHGlobal((int)dbdi.dbcc_size);

    Marshal.StructureToPtr(dbdi, dibuf, true);
    hDevNotify = Dbt.RegisterDeviceNotification(Handle, dibuf, DEVICE_NOTIFY_WINDOW_HANDLE);
    Marshal.PtrToStructure(dibuf, typeof(DEV_BROADCAST_DEVICEINTERFACE));
    Marshal.FreeHGlobal(dibuf);
}

Elsewhere:

protected override void WndProc(ref Message m)
{
    switch ((WindowMessage)m.Msg)
    {
        case WindowMessage.WM_CREATE:
            Console.WriteLine("Created, registering for device change events.");
            RegisterDeviceNotification(new GUID()
            {
            });
            break;
        case WindowMessage.WM_DEVICECHANGE:
            Console.WriteLine("WM_DEVICECHANGE");
            break;
        default:
            base.WndProc(ref m);
            break;
    }

    base.WndProc(ref m);
}
AArnott commented 6 years ago

This is good progress.

A few quick points:

  1. Yes, convert GUID to System.Guid
  2. Avoid using string or any other managed types for struct fields as that makes them non-blittable. Check out other structs in the repo already and how they use [*Friendly*] attributes and pointers/arrays.
ghost commented 6 years ago

I've converted GUID to Guid in my working copy. What do you mean about friendly attributes? What should I replace strings with?

I saw some definitions for PInvoke code that used buffers of a large fixed size, but I don't think that is the best solution. At least with this API it is possible that some common sizes would experience overruns (255 and 1024).

AArnott commented 6 years ago

An example of the friendly attribute and how to represent strings is here:

https://github.com/AArnott/pinvoke/blob/bc4c6830f1918f5af28e76e075dd0750f27d3c61/src/BCrypt/BCrypt%2BBCRYPT_ALGORITHM_IDENTIFIER.cs#L16-L23

Basically, native code represents these structs as a pointer to an array of characters, so we should too. Memory management is more likely doable in this scenario as well since if the struct is passed in, the memory can be pre-allocated, and if the struct is passed out, you still have the original pointer by which you can call the library to release the memory when you're done with it.

No need for fixed size buffers unless the native contract is for that already.

ghost commented 6 years ago

Also, if I create an enumeration such as:

public enum DeviceChangeEvent
{
    DBT_CONFIGCHANGECANCELED = 0x0019,
    DBT_CONFIGCHANGED = 0x0018,
    DBT_CUSTOMEVENT = 0x8006,
    DBT_DEVICEARRIVAL = 0x8000,
    DBT_DEVICEQUERYREMOVE = 0x8001,
    DBT_DEVICEQUERYREMOVEFAILED = 0x8002,
    DBT_DEVICEREMOVECOMPLETE = 0x8003,
    DBT_DEVICEREMOVEPENDING = 0x8004,
    DBT_DEVICETYPESPECIFIC = 0x8005,
    DBT_DEVNODES_CHANGED = 0x0007,
    DBT_QUERYCHANGECONFIG = 0x0017,
    DBT_USERDEFINED = 0xFFFF
}

What should it be named? I see some names like XxxType or XxxNoun. Should the type/noun be removed? There are some lint suggestions about redundant names involving XxxAttribute and so on.

AArnott commented 6 years ago

So I gather these are not flags, but truly exclusive options, yes?

If the header files doesn't prescribe a name for the enum, we just try to come up with something descriptive. Presumably in this case a name for which DBT_ would be an acronym. Do you know what DBT stands for?

ghost commented 6 years ago

They are exclusive options. A WM_DEVICECHANGE message can be of only one type. There is no conclusive documentation I could find on what the acronym means, but after thinking about it my best guess is that DBT stands for "device broadcast type." Many of the structures in the include are named like DEV_BROADCAST_xxx and the enumeration represents a message type.

I suppose I will change the name and consider the other enumerations I encounter. If there is no obvious enumeration where should the constants go?

What other options to enumerations were considered? At times it is hard to find symbolic constants.

AArnott commented 6 years ago

Your best guess at the acronym's meaning sounds good enough for me. :)

When enums aren't appropriate, we simply define public const fields in the library's class.

qmfrederik commented 4 years ago

CM_Register_Notification (Windows 8 and later) was added as part of #473.