このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
サーバ側はクラスの実装を提供する必要がある。
C++ クラスに IUnknown インタフェースを実装すると、
COM によって操作可能なクラスとなる。
では、最も基本的な COM クラスの枠組みを考えてみよう。
まずは、クラスの定義である。
通常、これはヘッダファイルに記述する。
==================================================
class Object : public IUnknown {
// インタフェース
public:
// IUnknown のメソッド
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [out] */ void** ppvObject);
virtual ULONG STDMETHODCALLTYPE AddRef();
virtual ULONG STDMETHODCALLTYPE Release();
// コンストラクタ
public:
Object();
// デストラクタ
protected:
virtual ~Object();
// 複製の禁止
private:
Object(const Object&);
Object& operator=(const Object&);
// フィールド
protected:
// 参照カウンタ
ULONG referenceCount;
};
==================================================
C++ には厳密なインタフェースが存在しないため、
クラスを継承する構文を使って実装することを示す。
この例では、Object というクラスが、
IUnknown を public 継承するという形になる。
インタフェースを実装するということは、
インタフェースに定義されたメソッド全てを、
クラスに実装しなければならないということになるため、
QueryInterface、AddRef、Release の 3 つ全てを、
クラス定義の中に明記する必要がある。
大半の場合、インタフェースの定義をコピーすることになる。
COM では初期化はコンストラクタで行わず、
インスタンスの初期化は、インスタンス生成後に、
任意のインタフェースのメソッドを呼び出して行う。
そのため、初期化なしにインスタンスを作れるように、
引数なしのコンストラクタのみを提供するのが一般的である。
インスタンスは参照カウンタによって管理されるので、
破棄するのはインスタンス自身である。
そのため、デストラクタは private にするか、
クラスを継承することを考慮して protected にする。
ここでも virtual をつけるのを忘れないこと。
コンストラクタ以外のインスタンスメソッドには、
ほぼ確実に virtual が必要と考えて間違いない。
referenceCount フィールドは、
参照カウンタの値を保持する整数値である。
そして、「複製の禁止」と書いた部分はおまじないだ。
C++ クラスは、既定でクローンの生成が可能となっており、
フィールドの(シャロー)コピーによって複製する。
これは、値型のクラスを作成する便宜を図ったものだ。
しかし、参照カウンタはインスタンス固有のものであり、
COM クラスは参照型であるため、複製は好ましくない。
そのため、複製に関わる 2 メソッドを private で定義し、
その実装を行わない事で自動的な複製を抑制する。
(誤って複製するコードを書いてもコンパイルエラーとなる)
この記述方法は、C++ のイディオムとなっている。
さて、では次にクラスの実装を考えて見る。
この部分が COM クラス実装の核となる。
==================================================
Object::Object() {
// 初期状態ではカウンタは 0
referenceCount = 0;
}
Object::~Object() {
// 処理不要
}
==================================================
まずは、コンストラクタとデストラクタだ。
このクラスでは何も扱わないので、
初期化するのは参照カウンタのみ。
破棄するものは何もなく、空のデストラクタとなる。
==================================================
ULONG Object::AddRef() {
// 参照カウンタを 1 加算
return ++referenceCount;
}
ULONG Object::Release() {
// 参照カウンタを 1 減算
if (--referenceCount == 0) {
// カウンタが 0 なら自身を破棄
delete this;
return 0;
}
return referenceCount;
}
==================================================
次に、AddRef/Release を考えてみよう。
AddRef は何も考えず、参照を 1 増やすのみである。
増やした後の値を返すので、前置インクリメントだ。
Release はその逆であり、参照を 1 減らす。
減らした結果 0 になったら、もはや参照は存在しないため、
delete this で、自分自身を破棄する。
delete this は自身のデストラクタを呼び出し、
インスタンスの占有しているメモリ領域を解放する。
その後の、return 0 には注意すること。
既にインスタンスが破棄されているため、
ここで return referenceCount とはできないのだ。
C++ 言語では、原則としてクラスも値型であるため、
new でインスタンスを生成する以外に、
基本型と同様、スタック上にインスタンスを置く事ができる。
前者はヒープ(フリーストア)ベースのオブジェクト、
後者はスタックベースのオブジェクトと呼ばれている。
上記の実装は後者を考慮していないことに注意すること。
前者は、インスタンス用のメモリを
new 等で外部に割り当てる必要があり、
クラス型のポインタ変数を介して間接操作する。
インスタンスの寿命は、delete するまでである。
後者は、インスタンス用のメモリをスタックに直接割り当て、
クラス型の変数を使って直接操作するため、
インスタンスの寿命はスコープを抜けるまでである。
一般的には後者の方が性能が良いが、
COM の寿命管理手法には適合しないため、
今後、スタックベースのクラスは考えないことにする。
QueryInterface の実装は明日にしよう。
最も重要な部分であるため長くなりそうだ。