COM コンポーネントを集成する

SPECIAL

COM コンポーネントをバージョンアップしよう、ということで集成に目をつけてみたのですけど…。

なんだか見当違いのようで、いまいち利点が見出せませんでした。


集成と包含

COM コンポーネントは約束事として、リリース後にインターフェイスを変更してはならないという条件があります。

なので、インターフェイスにメソッドを追加する場合には新しくインターフェイスを作る必要があるのですけど、そのときに旧バージョンのインターフェイスを継承できれば、効率よく開発を行うことができます。

 

その再利用を実現する方法として一般に、集成 (Aggrigation) と包含 (Containment) という2つの方法があります。

包含というのは、新しい COM コンポーネント内で古い COM コンポーネントを構築して、新しいインターフェイスに、旧インターフェイス内のメソッドを呼ぶためのメソッド (ラッパー) を用意します。

これによって旧バージョンのメソッドを呼び出せるわけですが、もちろんこの場合、ラッパーを用意していないメソッドに関しては呼び出すことができません。

 

集成という方法は、旧 COM コンポーネントを新 COM コンポーネントの内部に持たせるという点では包含と同じなのですけど、包含と大きく異なる点は、旧 COM コンポーネントのインターフェイスをそのまま新 COM コンポーネントに持たせることができるという点です。

なので今回は集成を行ってみることにします。

 

COM コンポーネントを集成する

IVersion1 インターフェイスを集成した IVersion2 インターフェイスを作るという想定で話を進めていくことにします。

 

外側のインターフェイスの作成

インターフェイスの構築

では、外側のインターフェイス (IVersion2) を作成します。

まずは通常のインターフェイスを作成する手順で、ATL のシンプルオブジェクトを作成します。今回はショートネームとして IVersion2 を指定します。それ以外の値はディフォルトにしました。

 

ヘッダファイル "Version2.h" の編集

外側のインターフェイス (IVersion2) のヘッダファイルに、内側のインターフェイス (IVersion1) のインスタンスを保存するための IUnknown ポインタ変数を登録します。

今回はスマートポインタを使用して IUnknown を登録することにしました。アクセスコントロールは Private でいいでしょう。

そして、内側のインスタンスを生成する都合で、FinalConstruct() 関数をオーバーライドします。

また、GetControllingUnknown() 関数を利用するに当たって、DECLARE_GET_CONTROLLING_UNKNOWN() マクロを追加します。

さらに BEGIN_COM_MAP と END_COM_MAP の間に、COM_INTERFACE_ENTRY_AGGREGATE マクロを追加します。

DECLARE_GET_CONTROLLING_UNKNOWN()

 

BEGIN_COM_MAP(CVersion2)

COM_INTERFACE_ENTRY(IVersion2)

COM_INTERFACE_ENTRY(IDispatch)

COM_INTERFACE_ENTRY_AGGREGATE(IID_IVersion1, m_pAgg.p)

END_COM_MAP()

 

private:

CComPtr<IUnknown> m_pAgg;

 

public:

HRESULT FinalConstruct();

 

また、同一のプロジェクト内では必要ないのですけど、別プロジェクトの場合には、内側のオブジェクトのヘッダをインクルードします。

#include "Version1.h"

 

ソースファイル "Version2.cpp" の編集

同一プロジェクト内の場合は必要ないのですけど、もしほかのプロジェクト内で集成を行いたい場合には、内側のオブジェクトの "プロジェクト名_i.c" というファイルをインクルードします。

#include "****_i.c"

 

内側のオブジェクトインスタンスを生成します。

内側のインスタンスは、外側の生成が完了した直後に行う必要があります。なので、FinalConstruct() 関数をオーバーライドして、その中でインスタンスを作ります。

HRESULT CVersion2::FinalConstruct()

{

    return CoCreateInstance(CLSID_Version1, GetControllingUnknown(), CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pAgg;

}

これで m_pAgg メンバ変数に、内側のインターフェイスである IVersion1 が、IUnknown として登録されます。

 

IDL "*****.idl" の編集

集成をおこなった coclass のセクションに、インターフェイスを追加します。

coclass Version2

{

[default] interface IVersion2;

interface IVersion1;

};

 

あと、これも同一プロジェクト内の場合は必要ないのですけど、ほかのプロジェクト内で集成を行いたい場合には、内側のオブジェクトの "プロジェクト名.tlb" というファイルを library セクションにインクルードします。

library COMOUTERLib

{

importlib("stdole32.tlb");

importlib("stdole2.tlb");

importlib("****.tlb");

 

[

これで集成され、IVersion1 と IVersion2 の両方のインターフェイスをもった COM コンポーネントが生成できます。

 

集成の利点というのは…?

とりあえず集成をやってみたのですが、僕自身はいまいち利点が見出せていません。

透過的にインターフェイスを介してそれぞれのメソッドにアクセスできるならいいのですけど、結局のところ2つのインターフェイスが独立してしまっているため、しっかりと使い分けなくてはいけないような感じです。

これでは、CreateObject を 2 回やるのとそう変わらないような…。

 

しかしながら、集成は再利用するうえでの非常に優れた仕組みのような話もあるようなので、単純に自分が利点を見出せていないだけなのでしょう…。