Hooking Direct3D11
Direct3D は、デバッグビルドの場合、終了時に解放されていない ID3D* 系オブジェクトがあるとその旨を警告してくれます。しかし、その時出してくれる情報は、どの class にいくつ開放漏れがあります、というだけでデバッグの助けにはあまりなりません。
これがものすごく不満だったので、もっと親切なリークチェッカを作ってみました。
https://github.com/i-saint/D3DHookInterface/tree/master/LeakChecker (一括ダウンロード)
使い方は、DirectX の Device & SwapChain を作成した後 D3D11LeakCheckerInitialize() を呼び、リークチェックしたい箇所で D3D11LeakCheckerPrintLeakInfo() を呼ぶだけです。その時点で解放されていないリソースの、作成時のコールスタック、AddRef() / Release() した時のコールスタックとその回数を表示します。
(現状 D3D11 しか対応してません)
実装には、D3D の interface の vtable をすり替えて hook を仕込む、という方法を用いています。
他に、VirtualProtect() で OS の write protect を無効化して vtable を直接書き換える、import table を書き換える、などのアプローチもあることを知りましたが、1 番穏便?な方法を取りました。
実装中、DirectX の内部実装が垣間見える現象に色々遭遇して興味深かったです。以下その時のメモ。
- DirectX の interface のメンバ関数は stdcall
- DirectX は Release ビルドでは自ら vtable 書き換えを行なっている
- Release ビルドの場合、IDXGISwapChain::Present() の中で ID3D11DeviceContext の描画系メンバ関数の書き換えが行われているようです。最適化のためと推測されます。 (該当箇所 http://codepad.org/1eqo7XQZ )
- vtable を直接書き換えるアプローチで hook を実装してたら、IDXGISwapChain::Present() するたびに vtable が元に戻っていてナニゴトかと思いました。
- ID3D11Device::CreateSamplerState() は実際に作成を行うとは限らない
- 過去に同じパラメータで作成されたものがあると、参照カウンタだけ上げてそれを返すようです。ありそうな最適化ではありますが、意表をつかれました。(Create を名乗ってるのに Create してない!!)
- 未確認ですがたぶん CreateSamplerState() の他にもいくつかあると思います。
- IUnknown::Release() が 0 を返す時、その時点では実際の開放は行われていない
- どうも排他制御して削除してねフラグを立てるだけのようで、実際の開放は全く別のタイミングで行われているようです。描画処理が非同期に行われるのを考えるとこれも自明な気はしますが、ちょっと驚きました。
- 既に参照カウンタが 0 になってるオブジェクトに Release() した時、即座にクラッシュするケースとそうはならないケースを経験した覚えがありますが、これが原因なのかもしれません。