2

不透明なハンドルと内部参照カウントを使用するネイティブ Dll を使用して C# で作業すると、次の 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. ハンドル A の参照カウントを内部的に増やすように dll に指示します。これにより、関数 1 で取得したハンドルを dll が内部的に解放しないことが保証されます。

  4. ハンドルの参照カウントを減らすように dll に指示します。ハンドルの参照カウントを増やした場合、または関数 2 を介して取得した場合に呼び出す必要があります。

  5. ハンドルで何らかの操作を行う

IntPtr を SafeHandle の独自のサブクラスに置き換えたいと思います。新しいハンドルを作成してハンドルを取得するときの手順は明らかです。ハンドルの参照カウントは 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); 失敗する可能性があります。安全にする方法はありますか?

4

1 に答える 1

2

を呼び出すとmakeNewHandleOfA、返されたインスタンスを所有しているため、解放する必要があります。を呼び出すとgetHandleOfA、返されたインスタンスを所有していませんが、そのライフサイクルを管理したい (つまり、基になるネイティブ ライブラリがインスタンスを解放しないようにする) 必要があります。

つまり、これら 2 つのユース ケースには基本的に異なるリリース戦略が必要です。

オプション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);
}
于 2013-01-29T17:22:36.947 に答える