その1では、ネイティブリソースをイミュータブルなクラスでラップする方法を示します。イミュータブルとは、インスタンスの内部状態が、変化しないクラスのことです。ナイーブな実装は↓のようなものです。
class A
{
protected IntPtr handle;
public A()
{
handle = Api.Alloc();
}
~A()
{
Api.Release(handle);
}
public void Method()
{
Api.Method(handle);
}
}
Api
クラスでネイティブ API の呼びだしが実装されていることを想定した疑似コードです。実際には、Api.Alloc
では、ILCreateFromPathW
でアイテムIDリストを作成したり、Api.Release
では、ILFree
で開放したりします。
しかし、c# では、ファイナライザーの実行タイミング
がかなりアグレッシブなため、(環境によっては)、稀に不正なメモリーアクセスなどの原因で、クラッシュします。
Api.Method(handle)
実行中に、~A()
が呼ばれる場合があるためです。これを避けるには、↓のようにします。
class A
{
protected IntPtr handle;
protected int pinCount = 0;
public A()
{
handle = Api.Alloc();
}
~A()
{
Debug.Assert(pinCount == 0);
Api.Release(handle);
}
public void Method()
{
Pin();
try
{
Api.Method(handle);
}
finally
{
Unpin();
}
}
protected void Pin()
{
Debug.Assert(pinCount >= 0);
++ pinCount;
}
protected void Unpin()
{
Debug.Assert(pinCount >= 1);
-- pinCount;
}
}
handle
を使う場合には、Pin()、Unpin()
を呼びだしてファイナライザーが呼び出されないようにしています。
handle
はスレッド間で共有されるので一般的には、ロックが必要ですが、handle
をコンストラクターとファイナライザー以外で変更しなければロックは不要だと思います。(イミュータブルなクラスを採用したのはこのためです)
handle
を外部で使用しない方が安全ですが、公開する場合には、Pin()、Unpin()
も
public
で公開して、handle
を保護する必要があります。
本質的には、pinCount
や、
Pin()
は不要ですが、コンパイラの最適化によって、Unpin()
が消えてしまうの懸念があるのと、デバッグの助けになるので入れた方がより安全だと思います。
pinCount
があるので、厳密にはイミュータブルでないような気もしますが、外から見たらイミュータブルに見えるので、イミュータブルと呼んでよいのではないでしょうか?自分は残念ながらイミュータブルの名付け親じゃないのでわかりません。
Pin()
を使用する方法の他、GC.KeepAlive
を使用する方法
もあるようです。記事を書いた後でネイティブリソースで検索したら、CA2115: ネイティブ リソースを使用しているときには GC.KeepAlive を呼び出します
がでてきたので発覚しました。こちらの方法の方が良いかもしれませんね。
また、ミュータブルなクラスでは、lock
の必要があるので、ネイティブリソースをラップする方法 (その2)
のように保護すると良いでしょう。
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。