2013-01-24 218 views
2

在我写的本地dll包装中,我刚刚用SafeHandles替换了IntPtr的所有用法(用于编组句柄)。我的印象是,正确书写的SafeHandle类型可以通过这种方式与IntPtr互换。将非SafeGuard从非托管状态管理到托管状态

但是,我Marshal.GetFunctionPointerForDelegate呼叫现在抛出一个异常:

Cannot marshal 'parameter #n': SafeHandles cannot be marshaled from unmanaged to managed. 

回调包含在参数列表中的手柄,所以委托含有(a 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

不是SafeHandle的.NET方面(而不是本地)的概念?你的interop声明是什么样的? –

+0

SafeHandle确实是一个.NET Side概念(与IntPtr一样)。将更多的细节添加到我原来的问题。 –

+0

SafeHandle是一个抽象类。 pinvoke编组没有任何方法可以从声明中选择正确的派生类类型,它只有一个IntPtr。 –

回答

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); 
} 

这样的话,你还是会保留你的“类型安全”的WRT使用SafeHandle,同时让您处理编组你想要的方式...

+0

我想这样做的困难就变成了我的库的回调是通过IntPtr而不是MySafeHandle实现的 - 所以我需要维护某种MySafeHandles的缓存字典,以便在给定IntPtr的情况下检索正确的一个。 –

+1

@TomDavies呃,没有想到...我的意思是,这将是一件微不足道的事情,但烦人......假设你可以将'缓存'烧成你的'SafeHandle'类,甚至可以一个隐式运算符来转换为IntPtr ... – JerKimball

+0

您可能还需要使用弱字典来实现它 - 这样它就不会阻止您的安全句柄对象被垃圾收集并在完成后最终完成使用它们。 –

5

的错误消息是误导。这是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,错误消失,所以我们可以看到我们的naiive的直觉是不正确......

delegate void TimerCallback(IntPtr pCallbackInstance, IntPtr context, IntPtr ptpTimer); 

见,瞧,该错误消失!现在我们只需要研究如何使用进入委托的IntPtr来查找正确的MySafeHandle对象......!

有一次我想出了什么样的修改修正了错误,我也可以想出一个理论,为什么修复了错误。

理论:(未经证实)

你在委托签名使用的IntPtr的原因是,SafeHandles是特殊。无论何时您编组为SafeHandle,CLR编组会自动将不透明的IntPtr句柄转换为新的CLR安全句柄对象,其中拥有所涉及的句柄。 (请注意,SafeHandles是对象,而不是结构体!)

如果您在每次调用委托时都为操作系统句柄创建了一个新的所有者对象,那么很快就会遇到很大的麻烦,因为您的对象会收集一次垃圾你从代表回来!

因此,我想也许编译器只是试图从自己的错误中拯救我们 - 以自己的混乱措辞?