英文:
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.)
英文:
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.)
答案1
得分: 1
以下是代码的中文翻译:
[DllImport("CfgMgr32.dll")]
static extern int CM_Register_Notification(
CM_NOTIFY_FILTER pFilter,
IntPtr pContext, // 用于回调的自定义信息
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;
// 在结构体之后添加更多数据
}
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,
}
static IntPtr _contextHandle;
static CM_NOTIFY_CALLBACK _callback = YourCallbackFunction;
// 必须保留对回调的引用,以避免被释放
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
// 或者使用 FilterType 和设备句柄或GUID
};
var result = CM_Register_Notification(in filter, IntPtr.Zero, _callback, out _contextHandle);
if (result != 0)
throw new Exception($"发生错误:{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>();
// 对事件执行某些操作
// 确保无论如何都要迅速执行操作,不要阻塞
return 0;
}
static void UnRegister()
{
if (_contextHandle != IntPtr.Zero)
{
CM_Unregister_Notification(_contextHandle);
_contextHandle = IntPtr.Zero;
}
}
请注意,CM_NOTIFY_EVENT_DATA
结构包含额外信息,这取决于传入的长度,因此我将其作为 IntPtr
传递。
请注意文档中的以下警告,看起来非常重要:
务必尽快处理即插即用设备事件。如果事件处理程序执行可能阻塞执行的任何操作(例如I/O),最好启动另一个线程以异步执行操作。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论