Tom*_*ies 2 c# pinvoke interop marshalling
在我写的本机dll包装器中,我刚刚用SafeHandles替换了IntPtr(用于编组句柄)的所有用法.我的印象是,正确编写的SafeHandle类型可以通过这种方式与IntPtr互换.
但是,我的Marshal.GetFunctionPointerForDelegate调用现在抛出一个异常:
Cannot marshal 'parameter #n': SafeHandles cannot be marshaled from unmanaged to managed.
Run Code Online (Sandbox Code Playgroud)
回调在参数列表中包含一个句柄,因此委托包含一个SafeHandle(而不是像以前一样的IntPtr).那我可以不这样做吗?如果是这样,我有什么选择使用SafeHandles,因为我需要编组回调?
这是一个原生dll头的编辑示例:
struct aType aType;
typedef void (*CallBackType)(aType*, int);
aType* create(); // Must be released
void release(aType* instance);
int doSomething(aType* instance, int argumnet);
void setCallback(CallbackType func);
Run Code Online (Sandbox Code Playgroud)
导致我麻烦的是回调.C#端看起来像这样:
delegate void CallBackType(IntPtr instance, int argument);
Run Code Online (Sandbox Code Playgroud)
然后:
var funcPtr = Marshal.GetFunctionPointerForDelegate(del = new CallbackType(somefunc)):
NativeFunction.setCallback(funcPtr)
Run Code Online (Sandbox Code Playgroud)
这工作正常,而且一直都做到了.但是,我想从IntPtr转移到将句柄管理到安全处理,并且读到它是一个替代品.但是,在上面的C#代码中用SafeHandle子类替换IntPtr会导致报告的异常:
delegate void CallBackType(MySafeHandle instance, int argument);
Run Code Online (Sandbox Code Playgroud)
错误消息具有误导性.这是100%的可能元帅从非托管safehandles来管理,因为这是SafeHandles如何应该是创建.了解如何定义CreateFile:
[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
private static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess,
FileShare dwShareMode, SECURITY_ATTRIBUTES securityAttrs,
FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
Run Code Online (Sandbox Code Playgroud)
编译器生成错误消息的原因实际上是您声明委托的方式.我犯了同样的错误,并尝试使用MySafeHandle类型作为委托参数,当我声明我的回调委托时(这里,非托管代码将回调您的托管代码):
delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, MySafeHandle ptpTimer);
Run Code Online (Sandbox Code Playgroud)
为此,我得到了与您完全相同的错误消息.但是,一旦我将我的委托签名更改为IntPtr,错误消失了,所以我们可以看到我们的直觉直觉是错误的......
delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, IntPtr ptpTimer);
Run Code Online (Sandbox Code Playgroud)
瞧,瞧,错误消失了!现在我们只需要弄清楚如何使用进入委托的IntPtr来查找正确的MySafeHandle对象......!
一旦我想通了什么变化固定的错误,我也可以拿出一个理论来为什么它修复错误.
理论:(未经证实)
您必须在委托签名中使用IntPtr的原因是SafeHandles是特殊的.每当你作为SafeHandle编组时,CLR marshaller会自动将不透明的IntPtr句柄转换为一个新的CLR SafeHandle对象,该对象拥有相关的HANDLE.(注意SafeHandles是对象,而不是结构!)
如果每次调用委托时都为OS HANDLE创建了一个新的所有者对象,那么很快就会遇到很大问题,因为一旦从委托中返回,您的对象就会被垃圾收集!
所以我想也许编译器只是试图将我们从这个错误中拯救出来 - 以其令人费解的措辞?