c# ネイティブリソースをラップする方法 (その2)

その2では、ネイティブリソースをミュータブルなクラスでラップする方法を示します。イミュータブルなクラスでラップ したほうが効率的だと思いますが、こちらの方法が必要な場合もあるかもしれません。ナイーブな実装は↓のようになります。

class A
{
  protected IntPtr handle;

  public A()
  {
    handle = Api.Alloc();
  }

  ~A()
  {
    Release();
  }

  public virutal Release()
  {
    if (handle == IntPtr.Zero)
      release;
    Api.Release(handle);
    handle = IntPtr.Zero;
  }
  
  public void Method()
  {
    Api.Method(handle);
  }
}

Api クラスでネイティブ API の呼びだしが実装されていることを想定した疑似コードです。実際には、Api.Alloc では、ILCreateFromPathW でアイテムIDリストを作成したり、Api.Release では、ILFree で開放したりします。

このコードでは、handle がスレッド間で共有されることが考慮されていないので、ネイティブ API によっては、クラッシュします。また、c# では、ファイナライザーの実行タイミング がかなりアグレッシブなため、稀に不正なメモリーアクセスなどの原因で、環境によっては、クラッシュします。 Api.Method(handle) 実行中に、~A() が呼ばれる場合があるためです。これを避けるには、↓のようにします。

class A
{
  protected IntPtr handle;
  protected Object lockObj = new Object();

  public A()
  {
    handle = Api.Alloc();
  }

  ~A()
  {
    Release();
  }

  public virutal Release()
  {
    lock (lockObj)
    {
      if (handle == IntPtr.Zero)
        release;
      Api.Release(handle);
      handle = IntPtr.Zero;
    }
  }
  
  public void Method()
  {
    lock (lockObj)
    {
      Api.Method(handle);
    }
  }
}

handle を使う場合には、lock により、複数スレッドから同時にアクセスすることないよう保護します。当然、 handle 使用中にデストラクターが実行されることはありません。もちろん、 handlelock 外へ公開してはいけません。やむを得ず公開する場合には、 handle 使用中は、同じように lockObject で保護する必要があります。

メソッド呼び出し毎に、同期オブジェクトが作成されるため、あまり高速な動作は期待できません。メソッドが頻繁に呼び出される場合には、イミュータブルクラスでラップする方法 をお勧めします。

となりのページ

このサイトについて

このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。

連絡先

ここをクリック すると表示されるページから作者へメールで連絡できます。

共有