Search code examples
c#.netwindowswinapipinvoke

How do I pInvoke CM_Register_Notification from C#?


I've found CM_Register_Notification which seems to be the simplest way to get notified of device addition and removal as mentioned in the first note here.

I've even found this which explains how to do it.

Unfortunately it's all for C++ developers.

So how do I call it (pInvoke) from C#? I don't even need the information that that function supplies. It's enough if I'm notified that something has changed. I'll then check using another way, what happened. (Any help would be appreciated. It doesn't have to be a complete answer.)


Solution

  • The documentation is all here. You just need the correct PInvoke declarations.

    The declarations you need are as follows, note that there are two union types and you need to test and make sure they work correctly

    [DllImport("CfgMgr32.dll")]
    static extern int CM_Register_Notification(
      CM_NOTIFY_FILTER pFilter,
      IntPtr pContext,  // your custom info for callback
      CM_NOTIFY_CALLBACK pCallback,
      [Out] out IntPtr pNotifyContext
    );
    
    [DllImport("CfgMgr32.dll")]
    static extern int CM_Unregister_Notification(IntPtr pContext);
    
    struct CM_NOTIFY_FILTER
    {
        const int MAX_DEVICE_ID_LEN = 200;
    
        public int cbSize = Marshal.SizeOf<CM_NOTIFY_FILTER>();
        public FilterFlags Flags;
        public CM_NOTIFY_FILTER_TYPE FilterType;
        int Reserved;
        public IdUnion union;
    
        [StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
        public struct IdUnion
        {
            [FieldOffset(0)]
            Guid ClassGuid;
    
            [FieldOffset(0)]
            IntPtr hTarget;
    
            [FieldOffset(0)]
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_DEVICE_ID_LEN)]
            string InstanceId;
        };
    
        public CM_NOTIFY_FILTER()
        {
        }
    }
    
    [Flags]
    enum FilterFlags
    {
        None = 0,
        CM_NOTIFY_FILTER_FLAG_ALL_INTERFACE_CLASSES = 0x00000001,
        CM_NOTIFY_FILTER_FLAG_ALL_DEVICE_INSTANCES  = 0x00000002
    }
    
    enum CM_NOTIFY_FILTER_TYPE
    {
        CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE = 0,
        CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE,
        CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE,
    }
    
    delegate int CM_NOTIFY_CALLBACK(
        IntPtr hNotify,
        IntPtr Context,
        CM_NOTIFY_ACTION Action,
        IntPtr EventData,
        int EventDataSize
        );
    
    struct CM_NOTIFY_EVENT_DATA
    {
        public CM_NOTIFY_FILTER_TYPE FilterType;
        int Reserved;
        // union
        Guid ClassOrEventGuid;
        int NameOffset;
        int DataSize;
        // more data added after struct
    }
    
    enum CM_NOTIFY_ACTION
    {
        /* Filter type: CM_NOTIFY_FILTER_TYPE_DEVICEINTERFACE */
    
        CM_NOTIFY_ACTION_DEVICEINTERFACEARRIVAL = 0,
        CM_NOTIFY_ACTION_DEVICEINTERFACEREMOVAL,
    
        /* Filter type: CM_NOTIFY_FILTER_TYPE_DEVICEHANDLE */
    
        CM_NOTIFY_ACTION_DEVICEQUERYREMOVE,
        CM_NOTIFY_ACTION_DEVICEQUERYREMOVEFAILED,
        CM_NOTIFY_ACTION_DEVICEREMOVEPENDING,
        CM_NOTIFY_ACTION_DEVICEREMOVECOMPLETE,
        CM_NOTIFY_ACTION_DEVICECUSTOMEVENT,
    
        /* Filter type: CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE */
    
        CM_NOTIFY_ACTION_DEVICEINSTANCEENUMERATED,
        CM_NOTIFY_ACTION_DEVICEINSTANCESTARTED,
        CM_NOTIFY_ACTION_DEVICEINSTANCEREMOVED,
    }
    

    You would use it something like this

    static IntPtr _contextHandle;
    
    static CM_NOTIFY_CALLBACK _callback = YourCallbackFunction;
    // must keep a reference to the callback to avoid being disposed
    
    static void RegisterCallback()
    {
        var filter = new CM_NOTIFY_FILTER
        {
            Flags = FilterFlags.CM_NOTIFY_FILTER_FLAG_ALL_INTERFACE_CLASSES,
            FilterType = CM_NOTIFY_FILTER_TYPE.CM_NOTIFY_FILTER_TYPE_DEVICEINSTANCE
            // Or use a FilterType and a Device handle or GUID
        };
        var result = CM_Register_Notification(in filter, IntPtr.Zero, _callback, out _contextHandle);
        if (result != 0)
            throw new Exception($"Error occurred {result:x}");
    }
    
    static int YourCallback(IntPtr hNotify, IntPtr Context, CM_NOTIFY_ACTION Action, IntPtr EventDataPtr, int EventDataSize)
    {
        var EventData = Marshal.PtrToStructure<CM_NOTIFY_EVENT_DATA>(EventDataPtr);
        var offsetOfMoreInfo = EventDataPtr + Marshal.SizeOf<CM_NOTIFY_EVENT_DATA>();
        // Do something with the event
        // Make sure whatever it is happens quickly, do not block
        return 0;
    }
    
    static void UnRegister()
    {
        if (_contextHandle != IntPtr.Zero)
        {
            CM_Unregister_Notification(_contextHandle);
            _contextHandle = IntPtr.Zero;
        }
    }
    

    The CM_NOTIFY_EVENT_DATA struct contains extra information, that is dependent on the length passed in, which is why I have passed it as an IntPtr.

    Note the following warning in the documentation, which looks pretty serious:

    Be sure to handle Plug and Play device events as quickly as possible. If your event handler performs any operation that may block execution (such as I/O), it is best to start another thread to perform the operation asynchronously.