2006 年 11 月 13 日 0 時 21 分

クラスとインタフェース


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


COM はクライアントサーバシステムであるため、
クラスの設計について、特殊なルールを定めている。

それは、実装側と利用側を明確に区別し、
利用側には、クラスの継承階層を持ち込まず、
インタフェースのみによる制約を課していることである。

一般的なオブジェクト指向の設計では、
クラスとインタフェースを駆使して開発を行うが、
その結果完成したクラスの継承階層には、
「クラスを単に利用する」ための情報と、
「クラスを拡張する」ための情報が混在している。
前者は利用側のための、後者は実装側のための情報である。

利用側にとって必要なのは、
どのような具象(コンクリート)クラスが利用可能で、
それらクラスでどのようなメソッドを呼べるかである。
クラス階層とか、実装がどのクラスか等には興味がない。
protected メンバや抽象クラスには興味がないのだ。

しかし、あるクラスの役割を理解するためには、
その基底(親)クラスに遡って調べる必要がある。
派生クラスが、基底クラスの実装に依存しているのだ。
結果的に、利用者には実装者により近い理解が求められる。

クラスの継承は、拡張性・再利用性で優れているため、
実装側にとっては非常に便利な機能であるが、
この複雑なクラス階層をそのまま公開してしまうと、
利用側にとっては理解が難しくなりやすい傾向にある。

インタフェースは、クラスより依存性を低くしたものだ。
インタフェースは、実装すべきメソッド名を定めているが、
メソッドの実装(中身)を提供することはできない。
インタフェースは、クラスに実装を要求するだけだ。

COM は、クライアントに対してクラスを公開せず、
インタフェースのみを公開することを定めている。
クラスの実装や、インスタンスの実体はサーバ側にあり、
クライアントには一切公開されない。

クライアントは、具象クラスのインスタンスの生成方法と、
具象クラスが実装しているインタフェースの
定義だけを知っていれば良いことになる。

この決まりにより、クライアントは、
サーバの実装を考える必要がない。
また、サーバには実装の自由が与えられる。
具象クラスが実装しているインタフェースを変えない限り、
サーバ内部でクラス継承階層を変えようが、
メソッドの追加や削除をしようが互換性を保つことができる。

さて、サーバで COM クラスを公開するためには、
IUnknown というインタフェースを実装する必要がある。

IUnknown は、名前が示すとおり未知のオブジェクトを指す。
Java や C# には、Object というルートクラスがあるが、
COM においては、IUnknown がルートインタフェースであり、
あらゆる COM インタフェースが必ず実装している。

IUnknown の定義を以下に示す。

class IUnknown {

public:

    virtual HRESULT STDMETHODCALLTYPE QueryInterface(
            /* [in] */ REFIID riid,
            /* [out] */ void** ppvObject) = 0;

    virtual ULONG STDMETHODCALLTYPE AddRef() = 0;

    virtual ULONG STDMETHODCALLTYPE Release() = 0;

};

実は、C++ にはインタフェースという概念がないため、
public 関数メンバ(メソッド)しか持たないクラスに、
純粋仮想関数(抽象メソッド)のみを定義したものを指す。

メソッド修飾子が色々と付いているので分かりにくいが、
virtual は仮想関数であることを示す。
これはオブジェクト指向言語では必須の機能であり、
ポリモルフィズムを実現するために必要な機能だ。
クラス設計では必ずつけると考えて差し支えない。

HRESULT や ULONG は戻り値である。
HRESULT は COM のエラーコードであり、
原則としてあらゆるメソッドは HRESULT を返す。
数少ない例外が、AddRef と Release である。

HRESULT は、高級言語における例外に相当する整数値だ。
Java の NullPointerException は、
COM では E_POINTER という HRESULT 値に相当する。
C++ 言語にも例外機構はあるのだが、
この例外は言語依存性が高いため COM では使われない。

次に、STDMETHODCALLTYPE だが、これはマクロであり、
通常、「__stdcall」に展開される。

__stdcall は呼び出し規約である。
呼び出し規約とは、関数を呼び出す際の、
引数の受け渡し方法を定めたものだ。
これを決めておくことで、言語を超えて、
COM オブジェクトの利用が可能になるのである。

32 ビットの Windows API や COM メソッド全般では、
STDCALL 呼び出し規約を使うことになっている。
__stdcall をメソッド名の直前に書くことで、
STDCALL 規約を使うことを指定する。

COM の呼び出し規約は OS によって異なるので、
STDMETHODCALLTYPE というマクロを使う慣習があるのだ。

C 言語では、既定で CDECL という呼び出し規約が使われる。
これは、STDCALL とは異なるため、
STDMETHODCALLTYPE を忘れると何が起きても不思議ではない。
これも、COM の世界では必ず指定すると覚えておくといい。

つまり、メソッドの定義は、ほとんどの場合において、
「virtual HRESULT 、STDMETHODCALLTYPE メソッド名」となる。

メソッド定義の後にある「= 0」は妙な構文だが、
0 を代入しているのではなく、抽象メソッドを示す。
インタフェースが持つのは全て抽象メソッドだが、
C 言語にはインタフェースがないので、
仕方なく抽象メソッドの構文を使っているのである。

長くなったので、メソッドの解説は明日にしよう。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.