Chapter 4.
インターフェース



今回からしばらくはCOMのメカニズムについて解説していきます。

前回、インターフェースの重要性をちょっぴり書きました。一般社会においても、インターフェースは重要です。
例えば、PC/AT互換機のインターフェースは、PCIやCPUのソケット(スロットか?)です。 最近では、USBやその他インターフェースが、いろいろ増えてきました。 最近、マザーボードを新しくして、CPUをCeleronかPentiumⅢにしようかと迷いましたが、 結局、今使っている、CPUをAMD K6-2 550MHzに交換しただけでした・・・。 代わりに、サウンドボードを買い替えましたが、スピーカーが壊れていて、今では、ただの板切れです。(涙

ここで重要なのは、スピーカーが壊れていたことことではありませんし、CPUを買い換えたことでもありません。 重要なのは、インターフェースを使用する利点が、見えてくることです。 インターフェースを使用すると、各部分の変更からシステム全体を保護できるということが分かります。 つまり、CPUを高速化するのに、マザーボード自身を交換する必要はなく、 そのマザーボードに付いているメモリ(256MB)やグラフィックボード(VooDoo3)を交換する必要もありません。 また、カプセル化を行っていることも分かります。サウンドボードが変更されても、サウンドボード自体が、 スピーカーに音を送るという機能は変わることはなく、スピーカーは、サウンドボードが変更されたことも意識しません。 つまり、スピーカーは送られた音を鳴らすという機能を実装したコンポーネントで、 そのインターフェースは接続端子の規格なのです。

話をCOMに戻します。
COMも、コンポーネントとクライアントを結ぶものは、インターフェースだけです。 スピーカと同様、クライアントは、コンポーネントの実装には全く関与せず、ただインターフェースだけを要求します。
PC/AT互換機とその部品のように、クライアントは、コンポーネントを同一のインターフェースを持った別のコンポーネントに交換することも出来ます。 その逆に、同一コンポーネントを異なったクライアントで使用できます。(ActiveXコントロールは、まさにこれの典型です。)

以上の話より、 コンポーネント = 部品 と言って差し支えないということが分かります。



では、とりあえず、C++でインターフェースを定義して実装してみます。 基本的に実践で使用されるCOMコンポーネントは、ほとんど、多重継承で作られているので、 これ以降、基礎のサンプルは多重継承で作っていきます。 説明を簡略化するため、今回は、C++のみで説明を行います。 また、コンポーネントの実装部分をクライアントから見えなくするために、ファイルを分割します。

interface.h
インターフェースを定義しています。
// IChap4_1インターフェース
interface IChap4_1
{
	virtual void FuncA(void) = 0;
};

// IChap4_2インターフェース
interface IChap4_2
{
	virtual void FuncB(void) = 0;
};

// インターフェース作成関数
extern void CreateInstance(IChap4_1 **);
extern void CreateInstance(IChap4_2 **);

// インターフェース開放関数
extern void Release(IChap4_1 *);
extern void Release(IChap4_2 *);

component.cpp
上記インターフェースを実装するコンポーネントを定義しています。
#include <objbase.h>
#include <iostream.h>
#include "interface.h"

///////////////////////////////////////
// Chap4クラス
class Chap4 : public IChap4_1, public IChap4_2
{
	// Chap4_1インターフェース
	virtual void FuncA(void);

	// Chap4_2インターフェース
	virtual void FuncB(void);
};

void Chap4::FuncA(void)
{
	cout << "Chap4::FuncAが呼ばれたよ~" << endl;
}

void Chap4::FuncB(void)
{
	cout << "Chap4::FuncBが呼ばれたよ~" << endl;
}

///////////////////////////////////////
// インターフェース作成関数
extern void CreateInstance(IChap4_1 **pIFace)
{
	Chap4	*pChap4 = new Chap4;
	*pIFace = (IChap4_1 *)pChap4;

	cout << "IChap4_1がCreateInstanceされたよ~" << endl;
}

extern void CreateInstance(IChap4_2 **pIFace)
{
	Chap4	*pChap4 = new Chap4;
	*pIFace = (IChap4_2 *)pChap4;

	cout << "IChap4_2がCreateInstanceされたよ~" << endl;
}

///////////////////////////////////////
// インターフェース開放関数
extern void Release(IChap4_1 *pIFace)
{
	delete (Chap4 *)pIFace;
	cout << "IChap4_1がReleaseされたました~。" << endl;
}

extern void Release(IChap4_2 *pIFace)
{
	delete (Chap4 *)pIFace;
	cout << "IChap4_2がReleaseされたました~。" << endl;
}

client.cpp
上記コンポーネントにアクセスするクライアントです。
#include <objbase.h>
#include <iostream.h>
#include "interface.h"

int main(void)
{
	// IChap4_1インターフェースを作成して、使用して、開放する。
	IChap4_1	*pChap4_1;
	CreateInstance(&pChap4_1);
	pChap4_1->FuncA();
	Release(pChap4_1);

	// IChap4_2インターフェースを・・・。
	IChap4_2	*pChap4_2;
	CreateInstance(&pChap4_2);
	pChap4_2->FuncB();
	Release(pChap4_2);

	return 0;
}

まず、C++においてCOMインターフェースは純粋抽象基本クラスとして定義する必要があります。 サンプルでは『interface』という単語でインターフェースを定義していますが、 これは<objbase.h>に次のように定義されているのを見つけることが出来ます。
#define interface struct
VC++のエディタ上で青色で表示されていることから、この単語はVC++の予約語であることが分かります。現在(2000年頃)のバージョンではstructのエイリアスに過ぎませんが、将来のVC++で変更される可能性があるということです。結果的に純粋仮想関数のみを持ったクラス(≒純粋抽象基本クラス)を定義しているのと同じ意味になります。

このサンプルの意味するところは、コンポーネントChap4が、2つのインターフェースを持ち 作成と削除以外のアクセスでは、インターフェース経由でしかコンポーネントにアクセスできないことを示しています。 これはコンポーネントにカプセル化の概念を実装していることになります。

上記以外で見るべきところは、C++でのstructとclassの違いは、メンバがデフォルトで、publicになるか、privateになるかという違いがあります。 COMのインターフェースでは、メンバ関数(変数は無い)は全てpublicである必要があるため、interfaceと書いた方が、 コードが見やすくなると私は考えています。そのため、このホームページでは、COMインターフェースの宣言は、 すべて、interfaceを使うものとします。



このサンプルの問題点として、まず、CreateInstanceとReleaseというインターフェースを使用しないアクセスが、2つ存在することが挙げられます。 コンポーネントの作成時には、new演算子を使用する必要があるため、全てをインターフェース経由のアクセスにすることは、不可能ですが、 外部から隠蔽する方法があります。これは、また後の章で、解説します。
また、今回の方法では、C++のオーバーロードを使用しているため、ヘッダファイルやコンパイラに依存してしまうという難点もあります。 個々のインターフェースを別々の関数で取得していては、オブジェクトの実行時要求もサポートできません。 さらに、インターフェース毎にコンポーネントを作成するという、問題もあります。
が、現時点では、仕方の無いことです。完全なCOMへの道は、まだまだ、遠いのです。

とりあえず、今回のサンプルでのポイントは、次の2点に絞られます。
1. クライアントから、コンポーネントの実装を隠す。
2. 1つのコンポーネントで、複数のインターフェースをサポートできる。

次の章では、インターフェースの実行時要求の手法について解説します。



上記3ファイルを固めたサンプルです。コンソールアプリケーションとして、ビルド&実行してください。
C++用ソース:chap4.lzh(1KB未満)


前へ 次へ
COM研究室へ
総合トップへ