在我写的本机dll包装器中,我刚刚用SafeHandles替换了IntPtr(用于编组句柄)的所有用法。我的印象是,正确编写的SafeHandle类型可以通过这种方式与IntPtr互换。
但是,我的Marshal.GetFunctionPointerForDelegate调用现在抛出异常:
Cannot marshal 'parameter #n': SafeHandles cannot be marshaled from unmanaged to managed.
回调在参数列表中包含一个句柄,因此委托包含一个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);
导致我麻烦的是回调。 C#端看起来像这样:
delegate void CallBackType(IntPtr instance, int argument);
然后:
var funcPtr = Marshal.GetFunctionPointerForDelegate(del = new CallbackType(somefunc)):
NativeFunction.setCallback(funcPtr)
这很好用,而且一直都做到了。但是,我想从IntPtr转移到将句柄管理到安全处理,并且读到它是一个替代品。但是,使用上述C#代码中的SafeHandle子类替换IntPtr会导致报告的异常:
delegate void CallBackType(MySafeHandle instance, int argument);
答案 0 :(得分:6)
错误消息具有误导性。 100%可能将安全句柄从非托管编组到托管,因为这就是应该创建的安全手柄。请参阅如何定义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);
编译器生成错误消息的原因实际上是您声明委托的方式。我犯了同样的错误并尝试使用MySafeHandle类型作为委托参数,当我声明我的回调委托时(这里,非托管代码将回调您的托管代码):
delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, MySafeHandle ptpTimer);
我得到了与您完全相同的错误消息。但是,一旦我将我的委托签名更改为IntPtr,错误消失了,所以我们可以看到我们的直觉直觉是错误的......
delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, IntPtr ptpTimer);
看,瞧,错误消失了!现在我们只需要弄清楚如何使用进入委托的IntPtr来查找正确的MySafeHandle对象......!
一旦我弄清楚修正错误的更改,我还可以提出一个关于为什么修复错误的理论。
理论:(未经证实)
您必须在委托签名中使用IntPtr的原因是SafeHandles是特殊的。每当你作为SafeHandle编组时,CLR编组程序会自动将不透明的IntPtr句柄转换为新的CLR SafeHandle对象,拥有有问题的HANDLE。 (请注意,SafeHandles是对象,而不是结构!)
如果每次调用委托时都为OS HANDLE创建了一个新的所有者对象,那么很快就会遇到很大问题,因为一旦从委托中返回,您的对象就会被垃圾收集!
所以我想也许编译器只是试图将我们从这个错误中解救出来 - 以其令人费解的措辞?
答案 1 :(得分:3)
SafeHandle
在基本编组期间使用P / invoke实现,但不是“手动编组”,就像你在这里做的那样......尝试这样的事情,也许?
internal delegate void InnerCallbackType(IntPtr instance, int argument);
public delegate void MyCallBackType(MySafeHandle instance, int argument);
public void SetCallback(Action<MySafeHandle, int> someFunc)
{
InnerCallbackType innerFunc = (rawHandle, rawArg) =>
{
someFunc(new MySafeHandle(rawHandle, true), rawArg);
};
var funcPtr = Marshal.GetFunctionPointerForDelegate(innerFunc);
NativeFunction.setCallback(funcPtr);
}
这样,你仍然可以保留SafeHandle
使用的“类型安全”,同时让你按照自己想要的方式处理编组......
答案 2 :(得分:0)
可能,其中还有一些额外的步骤,可以将IntPtr
转换为MySafeHandle
,而无需代理,使用ICustomMarshaler将委托转换为另一个 < / strong>。
委托void CallBackType(MySafeHandle实例,int参数);
所以让我们花一些点时间吧:
假装我们使用__cdecl调用约定将这样的委托传递给非托管环境(仅用于示例):
[DllImport("some.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
public static extern void setCallback([MarshalAs(UnmanagedType.FunctionPtr)] CallBackType callBackType);
然后我们要装饰我们的代表:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] // depends on unmanaged implementation, but for the example sake
delegate void CallBackType([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyCustomMarshaler))] MySafeHandle instance, int argument);
最后是 ICustomMarshaler 实现:
public sealed class MyCustomMarshaler : ICustomMarshaler
{
private static MyCustomMarshaler _instance = new MyCustomMarshaler();
public void CleanUpManagedData(object o)
{
}
public void CleanUpNativeData(IntPtr ptr)
{
}
public int GetNativeDataSize()
{
return IntPtr.Size;
}
public IntPtr MarshalManagedToNative(object o)
{
return IntPtr.Zero;
}
public object MarshalNativeToManaged(IntPtr ptr)
{
return new MySafeHandle()
{
handle = ptr
};
}
public static ICustomMarshaler GetInstance(string s)
{
return _instance;
}
}
我发布了答案,因为我在互联网上找不到任何答案(无论它是否安全,或者为什么不进行封送处理都会自动将IntPtr转换为SafeHandle)。