というわけで、ちょっと内部的に使っている技術の解説っぽいことをやってみましょう。
日記の方はそれぞれの記事ごとでリンクがあるようなのでこれで書いても問題はないと思います。
ゲームを作ろうとしたときに出てくるCOM(DirectXなどに使用されている技術)とIUnknownです。
COMでは、プログラムとして使用するときにはすべてのオブジェクトがIUnknownに対応する、とあります。
これは、つまりすべてのCOMのインターフェイスはIUnknownを継承しているような振る舞いをする、ということになります。
正しくは、以下のような定義がある、ということですが・・・。
class CCOMObject : public IUnknown { //class CCOMObject { virtual HRESULT QueryInterface(IID& iid, void** ppvObj); virtual ULONG AddRef(void); virtual ULONG Release(void); }
後ろの2つ(AddRef,Release)は、このオブジェクトの解体のタイミングを操作している関数です。
簡単に言うと、
COMオブジェクトを複数のオブジェクトで共有して使用するときに共有先でAddRefを呼び出してオブジェクトの共有を開始します。
必要がなくなるとReleaseを呼び出して対象のオブジェクトで不必要になったことを通知します。
つまり、内部的な参照カウント(いくつのオブジェクトに所有されているか)を制御しているわけですね。
使用者がもっとも困るのがQueryInterfaceです。この関数は一応以下のような解説がされています。
アプリケーションがコンポーネント オブジェクトに含まれる関数を利用したい場合は、そのオブジェクトのQueryInterfaceを呼び出し、目的の関数を実装したインターフェイスのポインタをリクエストします。コンポーネント オブジェクトがこのインターフェイスに対応している場合は、対象となるインターフェイス ポインタおよび成功コードを返します。コンポーネント オブジェクトが目的のインターフェイスに対応していない場合は、エラー値が返されます。(from MSDN)
対象のCOMオブジェクトがなんのインターフェイスを所持しているか、なんてヘルプを見てもわかりません。
かろうじてサンプルコードやチュートリアルにある程度です。なのでほとんどはサンプルコードの通り書くだけです。
ただ、このQueryInterfaceにはその定義上おもしろい仕掛けがあります。たとえば以下のようなコードです。
//lpCOMObjectはCOMの有効なポインタ変数 HRESULT hr; IUnknown *lpUnknown; hr = lpCOMObject->QueryInterface(IID_IUnknown,(void **)&lpUnknown); if(reinterpret_cast<void *>(lpCOMObject) != reinterpret_cast<void *>(lpUnknown)) return false;
このとき、戻り値hrは必ずS_OKになります。そして、ほとんどの場合、if文でfalseは戻りません。つまり、この作業は事実上以下のコードを実行したのと同じになります。
lpUnknown = static_cast<IUnknown *>(lpCOMObject); lpCOMObject->AddRef(); hr = S_OK;
もちろん、COMオブジェクトはstatic_cast的な書き方をサポートしていません。が、QueryInterfaceでの要求を行うとアップキャストを行ったような状態になります。
ダウンキャスト方向も全く同じ記述方法で安全に元に戻せます。実際にプログラムを書いて実行してみるとわかります。
あまり役に立たないようですが、すべてのオブジェクトはIUnknownを継承している「ような振る舞い」なのでこの記述は有効です。
もちろん、IUnknownを実際に継承していなくても3関数が実装されていて、QueryInterfaceでIUnknownが取得できるならそれはCOMのインターフェイスとして有効です。
(これはDirectShowのフィルタを実装するときのベースクラスとなるCUnknownがこの考え方に基づいています。)
QueryInterfaceを「機能の取得」以外にも「安全なダウンキャスト/アップキャスト(dynamic_cast)の代わり」としても使うことが出来るんですね~。
IUnknown(インターフェイスなんてわかんないよ~<訳違)といっても馬鹿には出来ません。さすがにCOMのベースインターフェイスとなる仕様ですね~。