その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)
のように保護すると良いでしょう。
このサイトのページへのリンクは自由に行っていただいてかまいません。
このサイトで公開している全ての画像、プログラム、文書の無断転載を禁止します。
ここをクリック
すると表示されるページから作者へメールで連絡できます。