この特集のトップページへ
>
Chapter 2:COMアーキテクチャの概要
2.2.5 COMアーキテクチャの検証
ここまでに説明した内容で,COMコンポーネントを利用するときに必要となるほとんどの機能は実装できた。それでは,ここまでに実装した内容を利用し,COMアーキテクチャが正しく機能しているかどうかを検証してみよう。
“void main(int argc, char* argv[]) { ... }”にCDictionaryオブジェクトを生成し,LookupWordメソッドを呼び出すソースコードを追加してみる。
最初に,メソッドの実行結果を保持するHRESULT hr,英和辞書の検索結果を保持するBOOL bResult,さらに検索する英単語と検索結果を保管するchar lpszEword[]とlpszJword[10]を宣言する。
次に,IUnknownインタフェースへのポインタ,IClassFactoryインタフェースへのポインタ,IDictionaryインタフェースへのポインタを保管する変数を,それぞれ次のように宣言する。
// IUnknownインタフェースへのポインタ IUnknown* pIUnknown; // IClassFactoryインタフェースへのポインタ IClassFactory* pIClassFactory; // IDictionaryインタフェースへのポインタ IDictionary* pIDictionary;
これで,前準備が整ったので,次にIClassFactoryインタフェースへのポインタを取得するために,GetClassObject APIを呼び出す。第1引数には作成したいオブジェクトのCLSIDを,第2引数には取得したいインタフェースID(ここではIID_IClassFactory)を,それぞれ指定する。第3引数には,IClassFactoryインタフェースへのポインタを取得するため,冒頭で宣言したpIClassFactoryポインタに対して“(void**)&pIClassFactory”と指定して取得する。ソースに追加するGetClassObject APIは,次のような行となる。GetClassObject()を実行すると,指定されたCLSIDのオブジェクトを作成して、このオブジェクトのインタフェースポインタを取得することができる。
GetClassObject( CLSID_CDictionary, IID_IClassFactory, (void**)&pIClassFactory );
では,実際にGetClassObject APIの内部にブレイクポイントを置いて,IClassFactoryインタフェースがどのようなメモリ配置になっているのかを検証してみよう。
Fig.2-15を参照するとわかるように,IUnknownインタフェースの仮想関数テーブルであるCClassFactory::vftableを指す__vfptrポインタが確保され,仮想関数テーブルには3つのメソッド(QueryInterface,AddRef,Release)が格納される。
Fig.2-15 GetClassObject API(拡大可能)
取得したIClassFactoryインタフェースを指すpIClassFactoryポインタを用いることで,IClassFactoryインタフェースのCreateInstanceメソッドを呼び出すことができる。CreateInstanceメソッドの第2引数には,取得したいインタフェースのインタフェースIDとしてIID_IUnknownと,インタフェースのアドレスを格納するために(void**)&pIUnknownポインタを渡す。メソッドを実行したあと,これまでの手続きにすべて成功していれば,IUnknownインタフェースへのポインタを指すポインタを取得することができる。ソースに追加するGetClassObject APIは,次のようになる。
hr = pIClassFactory-> CreateInstance(NULL,IID_IUnknown,(void**)&pIUnknown);
Fig.2-16に,CreateInstanceメソッドの内部メモリマップを示す。仮想関数テーブルにはIDictionaryインタフェースの3つのメソッド(QueryInterface,AddRef,Release)へのポインタが格納されている。
Fig.2-16 CClassFactory::CreateInstanceメソッドの内部メモリマップ(拡大可能)
IUnknownインタフェースへのポインタをpIUnknownポインタに取得できれば,このポインタを元にして,IDictionaryインタフェースへのポインタを取得するため,次のようにしてQueryInterfaceメソッドを呼び出す。QueryInterfaceメソッドの第1引数には,インタフェースIDとしてIID_IDictionaryを渡すことにする。
hr = pIUnknown-> QueryInterface(IID_IDictionary,(void**)&pIDictionary);
QueryInterfaceメソッドを実行すると,IDictionaryインタフェースへのポインタがpIDictionaryポインタに格納される。その様子については,Fig.2-17を参照していだだきたい。*ppvObjポインタに格納されている値がIDictionaryインタフェースへのポインタである。もっと簡単にいうと,CDictionaryオブジェクトのアドレスということになる。
Fig.2-17 CDictionary::QueryInterfaceメソッドの内部メモリマップ(拡大可能)
このようにして取得できたpIDictionaryポインタを使用して,はじめてIDictionaryインタフェースのLookupWordメソッドを呼び出すことができる。
pIDictionary->LookupWord(lpszEword,lpszJword,&bResult);
以上のようにして,COMオブジェクトのカスタムインタフェースに実装されたメソッドを呼び出すことができる。この手法により,どのようなCOMオブジェクトのメソッドでも呼び出せるようになるのである。これが,COMアーキテクチャの基盤となるコアテクノロジといっても過言ではない。
このあと,参照カウントをデクリメントすることで,これまでの処理で生成されたオブジェクトを破棄してゆく。まず,pIDictionary->Release(),pIUnknown->Release()を呼び出して参照カウントを0に戻し,CDictionaryオブジェクトを破棄する。そのあと,CClassFactoryオブジェクトを破棄するためにpIClassFactory->Release()を呼び出す。
pIDictionary->Release(); pIUnknown->Release(); pIClassFactory->Release();
ここまでの説明で,CDictionaryオブジェクトとIClassFactoryインタフェース,IUnknownインタフェース,IDictionaryインタフェースの関係はもちろん,インタフェースを通じてさらにインタフェースを取得する方法についても理解していただけたと思う。ここでは,main関数で始まるC++アプリケーションとしてCOMアーキテクチャを紹介した。だが,これをCOMコンポーネント(COMサーバー)とそれを呼び出すアプリケーション(COMクライアント)とに分割し,それぞれを実装してゆけば,擬似コードではあるものの,COMコンポーネントをメモリにマップしてCOMオブジェクトに実装されたインタフェースから任意のメソッドを呼び出すことができるようになる。
List 2-20に,ここで説明したCOMオブジェクトを生成し,メソッドを呼び出すプログラムのソースコードとその実行結果を示す。
プログラムを実行してみると,英和辞書から「pencil」という英単語を引いて「鉛筆」という和単語を表示していることがわかる。これにより,COMアーキテクチャによるアプリケーションが正常に動作していることも検証された。ここまでの実装は,CDictionaryCOM2のプロジェクトに保管する。
Fig.2-18 COMアーキテクチャを使った英和辞書の検索
最後に,本稿で作成したサンプルプログラムの全ソースリストを掲載する(List 2-21)。
次章では,COMアーキテクチャで開発した辞書検索アプリケーションをDLL形式でサーバーとクライアントに分割し,COMコンポーネントの仕組みについて解説する。
Chapter 2 9/9 |