IUnknown の動作

IUnknown のメソッドを使うと、アプリケーションはコンポーネントのインターフェイスを問い合わせたり、コンポーネントの参照カウントを管理できる。

参照カウント

参照カウントは、AddRef メソッドでインクリメントされ Release メソッドでデクリメントされる内部変数である。基底クラスは、参照カウントを管理し、複数のスレッド間で参照カウントへのアクセスを同期する。

インターフェイスの問い合わせ

インターフェイスの問い合わせはわかりやすい。呼び出し元は、インターフェイス識別子 (IID) とポインタのアドレスの 2 つのパラメータを渡す。要求されたインターフェイスをコンポーネントがサポートしている場合、コンポーネントはそのインターフェイスへのポインタを設定し、専用の参照カウントをインクリメントし、S_OK を返す。コンポーネントがそのインターフェイスをサポートしていない場合、コンポーネントは NULL へのポインタを設定し、E_NOINTERFACE を返す。次の擬似コードは、QueryInterface メソッドの概要を示す。次のセクションで説明するコンポーネントの集成化では、処理が複雑になっている。

if (IID == IID_IUnknown)
    (IUnknown *)this へのポインタを設定する。
    AddRef
    return S_OK

else if (IID == IID_ISomeInterface) 
    (ISomeInterface *)this へのポインタを設定する。
    AddRef
    return S_OK

else if ... 

else
    NULL へのポインタを設定する。
    return E_NOINTERFACE

1 つのコンポーネントの QueryInterface メソッドと別のコンポーネントの QueryInterface メソッドの唯一の違いは、それぞれのコンポーネントがテストする IID のリストにある。コンポーネントがサポートするすべてのインターフェイスに対して、コンポーネントはそのインターフェイスの IID のテストを行わなければならない。

集成化と委任

コンポーネントの集成は呼び出し元に対して透過的でなければならない。したがって、集成は、外部コンポーネントの実装に従う集成されたコンポーネントを使って、1 つの IUnknown インターフェイスを公開しなければならない。そうでないと、呼び出し元は同じ集成内で 2 つの異なる IUnknown インターフェイスを認識することになる。コンポーネントが集成されていない場合、コンポーネントは独自の実装を使う。

この動作をサポートするために、コンポーネントは間接的なレベルを追加しなければならない。"委任 IUnknown" は、作業を適切な場所に委任する。すなわち、インターフェイスが 1 つであれば外部コンポーネントか、またはコンポーネントの内部バージョンに委任する。"非委任 IUnknown" は、前のセクションで説明した操作を行う。

委任バージョンはパブリックで、IUnknown の名前をそのまま保持する。非委任バージョンは INonDelegatingUnknown に名前が変更される。これはパブリック インターフェイスではないので、この名前は COM 仕様の一部ではない。

クライアントがコンポーネントのインスタンスを作成するとき、クライアントは IClassFactory::CreateInstance メソッドを呼び出す。パラメータは、集成コンポーネントの IUnknown インターフェイスへのポインタか、または新しいインスタンスが集成されていない場合は NULL である。次の例に示すように、コンポーネントは使用する IUnknown インターフェイスを示すメンバ変数を格納するためにこのパラメータを使う。

CMyComponent::CMyComponent(IUnknown *pOuterUnkown)
{
    if (pOuterUnknown == NULL)
        m_pUnknown = (IUnknown *)(INonDelegatingUnknown *)this;
    else
        m_pUnknown = pOuterUnknown;

    [ ... コンストラクタのコードが続く ... ]
}

委任 IUnknown の各メソッドは、次の例に示すように対応する非委任メソッドを呼び出す。

HRESULT QueryInterface(REFIID iid, void **ppv) 
{
    return m_pUnknown->QueryInterface(iid, ppv);
}

委任動作の本質上、委任メソッドはすべてのコンポーネントにおいて同じに見える。非委任バージョンだけが変わる。