在此P / Invoke用例中正确使用SafeHandles

时间:2013-01-25 15:14:58

标签: c# pinvoke marshalling dllimport handles

在C#中使用本机Dll,使用不透明句柄和内部引用计数,我有以下P / Invoke签名(全部用DllImport属性修饰)

[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)

这些电话的含义如下:

  1. 从现有句柄B获取指向透明类型A的指针/句柄。返回句柄的内部引用计数不受影响。

  2. 创建一个新的A句柄。内部引用计数是预先递增的,并且句柄应由具有功能4的客户端释放,否则将发生泄漏。

  3. 告诉dll在内部增加句柄A的引用计数。这样我们就可以确保dll不会在内部释放我们通过函数1获取的句柄。

  4. 告诉dll减少句柄的引用计数。如果我们增加了句柄的引用计数,或者通过函数2获取它,则应该调用。

  5. 使用句柄执行某些操作

  6. 我想用我自己的SafeHandle子类替换IntPtr。当我通过创建新的句柄获得句柄时,程序是显而易见的;句柄的引用计数在dll中预先递增,所以我只是覆盖SafeHandle的Release函数,并调用releaseHandleToA(句柄)。使用这个新类'MySafeHandle',我可以改变上面的P / Incvoke签名:

    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中,获取的句柄没有增加其引用计数,因此尝试释放句柄将是一个错误。

    所以,也许我应该总是确保getHandleOfA调用与一个立即的addRefHandleToA配对,如下所示:

    [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);可能会失败。有没有办法让它安全?

1 个答案:

答案 0 :(得分:2)

当您致电makeNewHandleOfA时,您拥有返回的实例,因此您必须将其释放。 当您致电getHandleOfA时,不拥有返回的实例,但您仍希望管理其生命周期(即:阻止底层本机库发布它)。

这意味着您基本上需要针对这两个用例的不同发布策略。

选项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:

    [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:

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