1

在我正在编写的本机 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# 代码中将 IntPtr 替换为 SafeHandle 子类会导致报告的异常:

 delegate void CallBackType(MySafeHandle instance, int argument);
4

3 回答 3

8

错误消息具有误导性。将安全句柄从非托管编组到托管是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 句柄转换为拥有相关 HANDLE 的新CLR SafeHandle对象。(请注意,SafeHandles 是对象,而不是结构!)

如果您每次调用委托时都为 OS HANDLE 创建一个新的所有者对象,那么您很快就会遇到很大的麻烦,因为一旦您从委托返回,您的对象就会被垃圾回收!

所以我想也许编译器只是想把我们从这个错误中拯救出来——以它自己令人困惑的措辞方式?

于 2014-01-28T17:07:37.923 回答
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使用时保留“类型安全”,同时让您以您想要的方式处理编组......

于 2013-01-25T18:39:38.697 回答
1

可以通过一些额外的步骤来转向IntPtrMySafeHandle 而无需使用 ICustomMarshaler代理将委托转换为另一个。

委托 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)。

于 2019-11-17T10:26:39.603 回答