Marshaling SafeHandles从非托管到托管

时间:2013-01-24 23:50:45

标签: c# interop pinvoke marshalling

在我写的本机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);

3 个答案:

答案 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)。