在此P/Invoke用例中正确使用SafeHandles [英] Correct use of SafeHandles in this P/Invoke use case
问题描述
在C#中使用不透明的句柄和内部引用计数的本机Dll,我具有以下P/Invoke签名(全部用DllImport属性装饰)
Working in C# with a native Dll, that uses opaque handles and internal reference counting, I have the following P/Invoke signatures (all decorated with DllImport attribute)
[DllImport("somedll.dll"]
public extern IntPtr getHandleOfA(IntPtr handleToB, int index); //(1)
public extern IntPtr makeNewHandleOfA(); //(2)
public extern void addRefHandleToA(IntPtr handleToA); //(3)
public extern void releaseHandleToA(IntPtr handleToA); //(4)
public extern void doSomethingWithHandle(IntPtr handleToA) //(5)
这些调用的含义如下:
-
从现有的句柄B获取指向不透明类型A的指针/句柄.返回的句柄的内部引用计数不受影响.
Get a pointer/handle to an opaque type A from an existing handle B. The internal reference count of the returned handle is unaffected.
创建一个新的A句柄.内部引用计数已预先增加,该句柄应由具有功能4的客户端释放.否则将发生泄漏.
Create a new handle of A. The internal reference count is pre-incremented, and the handle should be released by the client with function 4, otherwise a leak will occur.
告诉dll在内部增加句柄A的引用计数.这使我们可以确保dll不会在内部释放我们通过功能1获取的句柄.
Tell the dll to internally increase the reference count of a handle A. This allows us to be sure that the dll will not internally release a handle that we have acquired through function 1.
告诉dll以减少句柄的引用计数.如果我们增加了句柄的引用计数,或者是通过函数2获取的,则应调用该函数.
Tell the dll to decrease the ref count of a handle. Should be called if we have increased the ref count of a handle, or acquired it through function 2.
使用手柄执行一些操作
我想用我自己的SafeHandle子类替换IntPtr.当我通过创建新的手柄来获取手柄时,过程很明显.该句柄的ref计数在dll中是预先增加的,因此我只重写SafeHandle的Release函数,然后调用releaseHandleToA(handle).使用这个新类'MySafeHandle',我可以像上面这样更改P/Invvoke签名:
I would like to replace the IntPtr with my own subclass of SafeHandle. When I acquire handles by creating new ones, the procedure is obvious; the handle's ref count is pre-incremented inside the dll, so I just override the Release function of SafeHandle, and call releaseHandleToA(handle). Using this new class 'MySafeHandle', I can change the P/Incvoke signatures above like so:
public extern MySafeHandleA getHandleOfA(MySafeHandleB handleToB, int index); //(1)
public extern MySafeHandleA makeNewHandleOfA(); //(2)
public extern void addRefHandleToA(MySafeHandleA handleToA); //(3)
public extern void releaseHandleToA(MySafeHandleA handleToA); //(4)
public extern void doSomethingWithHandle(MySafeHandleA handleToA) //(5)
但是这里有一个错误:在函数1中,获取的句柄没有增加其引用计数,因此尝试释放该句柄将是一个错误.
There is an error here though: in function 1, the handle acquired has not had its refcount increased, so trying to release the handle would be an error.
因此,也许我应该始终确保getHandleOfA调用与立即的addRefHandleToA配对,如下所示:
So, perhaps I should always ensure getHandleOfA calls are paired with an immediate addRefHandleToA, like this:
[DllImport("somedll.dll"]
private extern MySafeHandleA getHandleOfA(MySafeHandleB handleToB, int index); //(1)
[DllImport("somedll.dll"]
private extern void addRefHandleToA(MySafeHandleA handleToA); //(3)
public MySafeHandleA _getHandleOfA(MySafeHandleB handleToB, int index)
{
var safehandle = getHandleOfA(handleToB, index);
addRefHandleToA(safeHandle);
return safeHandle;
}
这样安全吗?
嗯,不,这显然不安全,因为addRefHandleToA(safeHandle);可能会失败.有什么办法可以使它安全吗?
Well, no it is clearly not safe, as addRefHandleToA(safeHandle); could fail. Is there a way I can make it safe?
推荐答案
调用makeNewHandleOfA
时,您拥有返回的实例,因此必须释放它.
调用getHandleOfA
时,您不拥有返回的实例,但是您仍要管理其生命周期(即:防止基础本机库释放它).
When you call makeNewHandleOfA
, you own the returned instance, so you must release it.
When you call getHandleOfA
, you do not own the returned instance, but you still want to manage its lifecyle (ie: prevent the underlying native library from releasing it).
这意味着您基本上希望针对这两种用例使用不同的发布策略.
It means you basically want different Release strategies for those two use cases.
选项1
使用:
internal class MyOwnedSafeHandleA : MySafeHandleA
{
protected override bool ReleaseHandle()
{
releaseHandleToA(handle);
return true;
}
}
internal class MySafeHandleA : SafeHandle
{
private int refCountIncremented;
internal void IncrementRefCount(Action<MySafeHandleA> nativeIncrement)
{
nativeIncrement(this);
refCountIncremented++;
}
protected override bool ReleaseHandle()
{
while (refCountIncremented > 0)
{
releaseHandleToA(handle);
refCountIncremented--;
}
return true;
}
}
您可以这样声明您的DllImports:
You can declare your DllImports like so:
[DllImport("somedll.dll")]
public extern MyOwnedSafeHandleA makeNewHandleOfA();
[DllImport("somedll.dll")]
private extern MySafeHandleA getHandleOfA(MySafeHandleB handleToB, int index);
[DllImport("somedll.dll")]
private extern void addRefHandleToA(MySafeHandleA handleToA);
选项2
您可以这样声明您的SafeHandle:
You could declare your SafeHandle like this:
internal class MySafeHandleA : SafeHandle
{
MySafeHandleA(IntPtr handle) : base(IntPtr.Zero, true)
{
SetHandle(handle);
}
protected override bool ReleaseHandle()
{
releaseHandleToA(handle);
return true;
}
}
并像这样使用它:
[DllImport("somedll.dll"]
private extern IntPtr getHandleOfA(MySafeHandleB handleToB, int index);
[DllImport("somedll.dll"]
private extern void addRefHandleToA(IntPtr ptr);
public MySafeHandleA _getHandleOfA(MySafeHandleB handleToB, int index)
{
IntPtr ptr = getHandleOfA(handleToB, index);
addRefHandleToA(ptr);
return new MySafeHandleA(ptr);
}
这篇关于在此P/Invoke用例中正确使用SafeHandles的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!