このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
IUnknown の定義を再度引用しておこう。
class IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [out] */ void** ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef() = 0;
virtual ULONG STDMETHODCALLTYPE Release() = 0;
};
念のため書いておくが、これはインタフェースである。
C++ の構文上の制約で、上のような書き方になっている。
IUnknown は、ルートインタフェースであり、
あらゆる COM インタフェースがこれを継承し、
あらゆる COM クラスがこれを実装することが要求される。
そのため、あらゆるインタフェースポインタから
上記メソッドを呼び出し可能なことが保証される。
IUnknown は、インスタンスの管理に必要な、
最小限のメソッドを定義している。
クライアントは、インタフェースポインタを通じて、
サーバにあるインスタンスを操作する。
クライアントは、インタフェースポインタをコピーしたり、
他のメソッドに渡したりすることができる。
サーバは、参照されているポインタの数を管理しており、
インスタンスを指すポインタがなくなれば、
一般的にはインスタンスを破棄する処理を取る。
この規則を満たすために存在するのが、
IUnknown というインタフェースである。
AddRef や Release は、インスタンスに対して、
インタフェースポインタのコピーを行ったこと、
インタフェースポインタの利用を終了したことを通知する。
これは、サーバの管理している参照カウンタを増減する。
このメソッドは絶対に成功するように実装されており、
戻り値は参照カウンタの現在値である。
値が 0 ならば、インスタンスは破棄されたことになる。
基本的にこの値はデバッグ目的以外に使わない。
QueryInterface は、インスタンスに対して、
別のインタフェースを実装しているかどうかを問い合わせる。
riid は、インタフェースの ID(IID)であり、
ppvObject には、ポインタを受け取る変数を渡す。
インスタンスが指定したインタフェースを実装していた場合、
サーバは指定したインタフェース型のポインタを割り当て、
ppvObject 引数を経由してクライアントに返す。
これは、高水準言語の実行時キャストに相当する機能だ。
クライアント側には実装の詳細が隠されているため、
言語機能で型キャストすることはできず、
必ず QueryInterface を呼び出す必要がある。
(クライアントには、キャストの妥当性を調べる方法がない)
言葉で説明しても分かりにくいと思うので、
クライアントから見た IUnknown の使い方の例を示す。
SetObject 関数は引数で任意のオブジェクトを受け取り、
それをグローバル変数 _object に格納する。
もしオブジェクトが IFoo インタフェースを実装していれば、
その Bar というメソッドを呼び出すこととする。
IUnknown* _object = NULL;
HRESULT SetObject(/* [in] */ IUnknown* object) {
// NULL なら例外とする
if (object == NULL) return E_POINTER;
IFoo* foo;
HRESULT ret;
// IFoo にキャストできるか試す
ret = object->QueryInterface(IID_IFoo, (void**)&foo);
if (SUCCEEDED(ret)) {
// キャストに成功した場合……
// IFoo#Bar の呼び出し
ret = foo->Bar();
// 使い終わったポインタは必ず解放
foo->Release();
// Bar が失敗したら関数も失敗
if (FAILED(ret)) return ret;
}
// 値を _object に代入
// 既存の _object が有効なポインタを持っていれば解放
if (_object != NULL) {
_object->Release();
_object = NULL; // 省略してもいい
}
// ポインタを複製する場合、必ずサーバに通知
_object = object;
object->AddRef();
// 成功を意味する値を返す
return S_OK;
}
QueryInterface は別のインタフェースへのキャストを行う。
QueryInterface が成功した場合、
新しいインタフェースポインタを作成して返すので、
使い終わったら Release で解放する事が肝要である。
_object に代入する操作においても、
既存の _object 値が有効な値を指している場合、
その値を上書きする前に元のポインタを解放している。
object 引数は、呼び出し元から受け取ったものなので、
これは勝手に解放してはいけない。
このように、クライアントはインスタンスの寿命を気にせず、
自分で取得したポインタを全て Release で解放すれば良い。
COM のポインタを扱うには手間が多いことが分かるが、
VBA や VBScript、.NET では、IUnknown の操作や、
HRESULT の処理は言語機能に隠蔽されており、
より直感的にコードを書けるようになっている。
VC++ でも、_com_ptr_t というスマートポインタを用いると、
AddRef/Release/QueryInterface を裏に隠蔽し、
HRESULT を C++ 例外処理に置き換えてくれるので
コードを見やすく書くことは可能である。
まあ、これは今回の本筋から離れるので扱わない。
明日は、サーバ側の IUnknown 実装を考えてみよう。