问题
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)
The meanings of these calls are are follows:
Get a pointer/handle to an opaque type A from an existing handle B. The internal reference count of the returned handle is unaffected.
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.
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.
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.
Perform some operation with the handle
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)
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.
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;
}
Is this safe?
EDIT: Well, no it is clearly not safe, as addRefHandleToA(safeHandle); could fail. Is there a way I can make it safe?
回答1:
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.
Option 1
With:
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;
}
}
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);
Option 2
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;
}
}
And use it like so:
[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);
}
来源:https://stackoverflow.com/questions/14524720/correct-use-of-safehandles-in-this-p-invoke-use-case