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

2023-12-19

在 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. 使用手柄执行一些操作

我想用我自己的 SafeHandle 子类替换 IntPtr。当我通过创建新句柄来获取句柄时,过程很明显;句柄的引用计数在 dll 内预先递增,因此我只需重写 SafeHandle 的 Release 函数,并调用releaseHandleToA(handle)。使用这个新类“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);可能会失败。有什么办法可以保证安全吗?


你打电话时makeNewHandleOfA, you own返回的实例,因此您必须释放它。 你打电话时getHandleOfA, you 不拥有返回的实例,但您仍然希望管理其生命周期(即:防止底层本机库释放它)。

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

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

您可以像这样声明您的 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);

Option 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);
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在此 P/Invoke 用例中正确使用 SafeHandles 的相关文章

随机推荐