Correct use of SafeHandles in this P/Invoke use case

橙三吉。 提交于 2019-12-24 06:03:58

问题


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:

  1. Get a pointer/handle to an opaque type A from an existing handle B. The internal reference count of the returned handle is unaffected.

  2. 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.

  3. 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.

  4. 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.

  5. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!