この特集のトップページへ
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(拡大可能)
fig.2-15

 取得した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メソッドの内部メモリマップ(拡大可能)
fig.2-16

 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メソッドの内部メモリマップ(拡大可能)
fig.2-17

 このようにして取得できた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アーキテクチャを使った英和辞書の検索
fig.2-18

 最後に,本稿で作成したサンプルプログラムの全ソースリストを掲載する(List 2-21)。

 次章では,COMアーキテクチャで開発した辞書検索アプリケーションをDLL形式でサーバーとクライアントに分割し,COMコンポーネントの仕組みについて解説する。

▼ Chapter 2のソースファイル

prev Chapter 2 9/9