2006 年 11 月 14 日 23 時 58 分

サーバから見た IUnknown(前編)


このアーカイブは同期化されません。 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 の実装は明日にしよう。
最も重要な部分であるため長くなりそうだ。



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