この特集のトップページへ
Chapter 2:COMアーキテクチャの概要

見出し 2.2.3 CDictionaryクラスの定義
 IUnknownインタフェースを継承したIDictionaryインタフェースを定義したら,次にIDictionaryインタフェースを継承するCDictionaryクラスを定義する(List 2-7Fig.2-11)。このCDictionaryクラスは,これまで定義してきたインタフェースのメソッドとは異なり,純粋仮想関数としては宣言しないことに注意していただきたい。このことは,CDictionaryクラスで抽象基本クラスのメソッドを実装することを意味している。List 2-7において強調されている文字列は,抽象基本クラスで定義されたIUnknownインタフェースのメソッドを宣言している部分である(実装の実体であるクラスではvirtualを指定しない)。

 また,CDictionaryクラスのコンストラクタのように,CDictionaryクラスの内部にメソッドを実装しても問題はない。

Fig.2-11 CDictionaryクラス
fig.2-11

●QueryInterfaceメソッドの実装

 QueryInterfaceメソッドは,第1引数REFIID riidで渡されたインタフェースIDを基にして,インタフェースへのアドレス(実際には,インタフェースへのポインタを指すポインタ)を返すCOMアーキテクチャのなかでも中核となる重要な働きをするメソッドである。このサンプルの場合,IID_IUnknownまたはIID_IDictionaryというインタフェースIDを渡された場合に,そのインタフェースへのポインタを指すポインタを返す処理を担う。  では,IUnknownインタフェースのメソッドであるQueryInterfaceメソッドを実装してみよう。

   HRESULT __stdcall 
       QueryInterface(REFIID riid, void** ppvObj)

 このメソッドを実装する場合,引数に渡された128ビットのインタフェースIDと定義ずみのIID_IUnknownやIID_IDictionaryの値とを比較し,一致したときにインタフェースへのアドレスを*ppvObjという引数に設定すればよい。128ビットのインタフェースIDを比較するという点では,次のようなコードが想定される。

   if (!memcmp(&riid ,&IID_IUnknown,sizeof(GUID)))
       *ppvObj = (IUnknown*)this;

 このコードを簡略化するためにobjbase.hというヘッダファイルを参照すると,List 2-8のような,高度に最適化された高速なマクロが定義されていることがわかる。そのため,Cのmemcmp関数を使わずにIsEqualIID(riid ,IID_IUnknown)のように記述できるようになっている。さらに,強制的に型を変換するキャストについても,最新のC++のキャスト書式を利用すれば,static_cast(this)のように記述できる。このキャストによって,インタフェースへのアドレスに型変換されたポインタが強制的に呼び出し元へと返される一方,処理に成功した証としてHRESULT型の戻り値にS_OKが指定される。また,インタフェースが見つかった場合には,COMオブジェクトを管理するために使用される参照カウントをインクリメントするため,AddRefメソッドが呼び出される(これについては後述する)。

 ここまでの話は,引数で渡されたインタフェースIDが見つかった場合の処理であるが,見つからなかった場合にはエラーを返さなければならない。このようなエラー(#define E_NOINTERFACE 0x80000004L)はヘッダに記述されているため,そのマクロも実装しておく必要がある(winerror.hファイルをインクルードすれば追加する必要はない)。

●HRESULT型

 HRESULT型は,COMコンポーネントでインタフェースに関連したメソッドの実行結果を戻す際に利用される32ビット値である。HRESULT型は,winerror.hファイルに定義され,Fig.2-12のように,シビリティ(1ビット),ファシリティ(15ビット),リターンコード(16ビット)によって構成されている。シビリティにはメソッド呼び出しの成否が設定され,ファシリティにはリターンコードのタイプとそれにかかわる情報が設定され,リターンコードにはメソッドが返す戻り値が設定される。

Fig.2-12 HRESULT型
fig.2-12

 ユーザーがHRESULT型を指定する場合には,次の規約に従わなければならない。

  ○0x0000〜0x01FFのリターンコード値を割り当ててはならない
  ○FACILITY_ITFのコードを伝達してはならない
  ○COMで既定された成否コードを利用しなければならない
  ○できるだけメソッドのリターン値を渡す

Table 2-1 代表的なファシリティコード
ファシリティコード 内容
FACILITY_WINDOWS Windows
FACILITY_STORAGE Storage
FACILITY_SSPI SSPI
FACILITY_SETUPAPI SetupAPI
FACILITY_RPC RPC
FACILITY_WIN32 Win32
FACILITY_CONTROL Control
FACILITY_MSMQ MSMQ
FACILITY_MEDIASERVER MediaServer
FACILITY_INTERNET Internet
FACILITY_ITF ITF
FACILITY_DISPATCH Dispatch
FACILITY_CERT CERT
Table 2-2 代表的なリターンコード
リターンコード 内容
S_OK 処理に成功した
S_FALSE 処理に失敗した
E_NOINTERFACE 指定されたインタフェースが見つからない
E_OUTOFMEMORY メモリが不足している
E_FAIL 特定されない失敗である

 QueryInterfaceメソッドのソースコードをList 2-9に示す。

●AddRefメソッドの実装

 COMオブジェクトの生成と破棄は,参照カウント(Reference count)の値を判断して処理される。COMオブジェクトを生成するときには参照カウントに1を加え,COMオブジェクトが不要になると参照カウントから1を減ずる。参照カウントの値が0になると,そのCOMオブジェクトは破棄しても問題ないと判断される。

 このサンプル(CDictionaryオブジェクト)に参照カウントを実装するには,CDictionaryクラスの内部にm_dwRefを宣言する。AddRefメソッドの機能は,単にm_dwRefの値に1を加えて,その値を呼び出し元に返すだけである。AddRefメソッドの実装は,およそList 2-10のようになる。

●Releaseメソッドの実装

 AddRefメソッドで提供した機能とは逆に,参照カウントから値を減ずるのがReleaseメソッドの働きである。Releaseメソッドは,その時点の参照カウントから1を減らし,その値が0であれば,delete thisという処理を実行してCOMオブジェクトを破棄する。そして,処理の成否にかかわらず,呼び出し元にその時点の参照カウントの値を戻す。

 以上のように,AddRefメソッドとReleaseメソッドによって参照カウントを操作することにより,COMオブジェクトの寿命を管理することができるようになる(List 2-11)。

●CDictionaryクラスのLookupWordメソッドの実装

 QueryIntefaceメソッド,AddRefメソッド,Releaseメソッドを実装したことで,IUnknownインタフェースへのメソッドの実装は完了した。次に,ユーザーが定義したインタフェースに対して,必要なメソッドを実装する。では,宣言した英語辞書を検索するLookupWordメソッドをIDictionaryインタフェースに実装してみよう。

 LookupWordメソッドは,第1引数char* lpszEwordで渡された英単語を受け取り,メソッド内で英語辞書からその単語を検索し,英単語が見つかったら第2引数char* lpszJwordに和単語をコピーし,*pbResultにFOUNDと設定する。続いて,メソッドの実行時にエラーが発生しなかったことを示すため,戻り値にS_OKを設定する。

 英単語が見つからない場合には,第2引数char* lpszJwordに[Not Found]をコピーし,*pbResultにNotFOUNDを設定する。そして,メソッドの実行時にエラーが発生しなかったことを示すため,単語が見つかった場合と同様に戻り値にS_OKを設定する。ここまでの実装は,CDictionaryCOM1のプロジェクトに保管する(List 2-12)。

●IClassFactoryインタフェースを定義する

 IClassFactoryインタフェースは,IUnknownインタフェースと同様にCOMの中核を担うインタフェースである。COM+では,IClassFactoryインタフェースを実装していないCOMコンポーネントやCOM+コンポーネントをCOM+エクスプローラ(従来のMTS。COMコンポーネントやCOM+コンポーネントを利用するための標準サーバー)に登録できないこともあり,欠かすことのできない重要なインタフェースとなっている。したがって,IUnknownインタフェースとIClassFactoryインタフェースを理解できれば,後続するCOMの解説がより深く理解できるものと思われる。

Fig.2-13 IClassFactoryインタフェースとCClassFactoryクラスの関係
fig.2-13

 IClassFactoryインタフェースには,(1)COMオブジェクトを生成するためのCreateInstanceメソッド,(2)DLLで構築されたインプロセスサーバーでCOMコンポーネントをロックするためのLockServerメソッド,という2つのメソッドが宣言されている。C++のアプリケーションでは,クラスの定義が行われていれば,そのクラスを元にnewキーワードによって簡単にオブジェクトを生成できるが,COMコンポーネントでCOMオブジェクトを生成する場合は,IClassFactoryインタフェースに宣言されたCreateInstanceメソッドをインタフェースを通じて呼び出す仕組みになっている。

 このサンプルでも,COMアーキテクチャをアプリケーションとして実装しているとはいえ,COMオブジェクトの生成のためにCreateInstanceメソッドを実装して利用することにする(とはいえ,どちらのメソッドも機能的には何ら違いはない)。なお,LockServerメソッドは,COMコンポーネントをDLL形式で構築するときに必要となるため,このサンプルでの実装は見送ることにする。

 List 2-13に,IClassFactoryインタフェースの定義とインタフェースIDを示す。

prev Chapter 2 7/9 next