次の方法で共有


テクニカル ノート 38: MFC/OLE IUnknown の実装

更新 : 2007 年 11 月

5hhehwba.alert_note(ja-jp,VS.90).gifメモ :

次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。

OLE 2.0 の中核は "OLE コンポーネント オブジェクト モデル (COM: Component Object Model)" です。COM は複数のオブジェクトが相互に協調して通信するための基準を定義しています。この定義にはオブジェクトのメソッドの呼び出し方など、オブジェクトを外部から見たときの振る舞いが含まれています。COM には基本クラスが定義されています。COM 対応のクラスはすべてこのクラスから派生します。基本クラスは、IUnknown です。IUnknown のインターフェイスは C++ クラスと呼ばれていますが、COM は特定の言語を限定しないで利用できます。C、Pascal など、COM オブジェクトのバイナリ形式をサポートするすべての言語が COM を使用できます。

OLE ではすべての IUnknown 派生クラスを "インターフェイス" と呼びます。IUnknown のようなクラスが "インターフェイス" と呼ばれるのは、この種のクラスが実装を持たないからです。このクラスはオブジェクト間の通信プロトコルだけを定義し、その具体的な実装は含みません。この機構によって柔軟性を持ったシステムを得ることができます。MFC/C++ プログラムでは、既定の具体的な実装が MFC (Microsoft Foundation Class) によって与えられます。

MFC の IUnknown に関する実装を理解する前に、インターフェイスについて理解しておく必要があります。簡単な IUnknown は次のような内容を持っています。

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};
5hhehwba.alert_note(ja-jp,VS.90).gifメモ :

この説明では、呼び出し規約の詳細の一部 ( __stdcall など) は省略しています。

メンバ関数 AddRef および Release はオブジェクトのメモリ管理を制御します。COM は参照カウント手法を使用してオブジェクトを維持します。C++ の場合と同じように、COM オブジェクトは直接参照されず、常にポインタを通じて参照されます。オブジェクトが不要になると、オーナーはオブジェクトのメンバ関数 Release を呼び出して不要なオブジェクトを破棄します。この方法は、C++ における delete 演算子による破棄とは異なります。参照カウント手法によって、単一オブジェクトに対する多重参照が可能になります。オブジェクトの参照カウントは AddRefRelease で管理されます。オブジェクトは参照カウントが 0 になるまで削除されません。

AddRefRelease の動作は単純です。実際の処理内容は次のとおりです。

ULONG CMyObj::AddRef() 
{ 
    return ++m_dwRef; 
}

ULONG CMyObj::Release() 
{ 
    if (--m_dwRef == 0) 
    {
        delete this; 
        return 0;
    }
    return m_dwRef;
}

メンバ関数 QueryInterface はより高度な手法を使用しています。メンバ関数として AddRefRelease しか持たないオブジェクトの動作は限られているので、IUnknown よりも多くの機能を持つオブジェクトを作ることをお勧めします。このような場合に QueryInterface が役に立ちます。QueryInterface を利用すると、1 つのオブジェクトが複数の "インターフェイス" を持ちます。通常、これらのインターフェイスは IUnknown 派生クラスを作成して用意します。新しい機能はこのクラスのメンバ関数として作成します。COM インターフェイス クラスではメンバ変数を宣言せず、メンバ関数はすべて純粋仮想関数として宣言します。次に例を示します。

class IPrintInterface : public IUnknown
{
public:
    virtual void PrintObject() = 0;
};

IUnknown しかない場合に、IPrintInterface を使用するには IUnknown::QueryInterface を呼び出して IPrintInterface のインターフェイス ID (IID: Interface Identifier) を渡します。IID はインターフェイスを一意に識別する 128 ビットの ID です。各インターフェイスはプログラマや OLE が定義する IID を持っています。pUnkIUnknown へのポインタである場合、IPrintInterface を取得するためのコードは次のようになります。

IPrintInterface* pPrint = NULL;
if (pUnk->QueryInterface(IID_IPrintInterface, 
    (void**)&pPrint) == NOERROR)
{
    pPrint->PrintObject();
    pPrint->Release();   
        // release pointer obtained via QueryInterface
}

例のように IPrintInterface は単純なコードで取得できます。しかし、IPrintInterfaceIUnknown インターフェイスの両方をサポートするオブジェクトを実装する方法が不明です。上の例では IPrintInterface は IUnknown からの直接派生クラスであるため、この実装は簡単です。IPrintInterface を実装すると IUnknown の機能は自動的にサポートされます。次に例を示します。

class CPrintObj : public CPrintInterface
{
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
    virtual ULONG AddRef();
    virtual ULONG Release();
    virtual void PrintObject();
};

AddRefRelease の実装は前の例と同じです。CPrintObj::QueryInterface は次のようになります。

HRESULT CPrintObj::QueryInterface(REFIID iid, void FAR* FAR* ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = this;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

上の例のように、インターフェイス ID (IID) が取得できると、ポインタはオブジェクト自身を指します。インターフェイス ID が取得できない場合はエラーになります。QueryInterface の処理が成功した場合は、AddRef を呼び出します。CEditObj::Print も実装する必要があります。上の例の場合は、IPrintInterfaceIUnknown インターフェイスから直接派生しているので、CEditObj::Print は簡単です。ただし 2 つの異なる IUnknown 派生インターフェイスを持たせるときは、次のような作業が必要です。

class IEditInterface : public IUnkown
{
public:
    virtual void EditObject() = 0;
};

IEditInterfaceIPrintInterface の両方をサポートするクラスは、C++ の多重継承を利用する方法も含めて、さまざまな方法で作成できますが、ここではクラスの入れ子を利用する方法を説明します。

class CEditPrintObj
{
public:
    CEditPrintObj();

    HRESULT QueryInterface(REFIID iid, void**);
    ULONG AddRef();
    ULONG Release();
    DWORD m_dwRef;

    class CPrintObj : public IPrintInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual HRESULT QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_printObj;

    class CEditObj : public IEditInterface
    {
    public:
        CEditPrintObj* m_pParent;
        virtual ULONG QueryInterface(REFIID iid, void** ppvObj);
        virtual ULONG AddRef();
        virtual ULONG Release();
    } m_editObj;
};

以下のコードを含めると完全な実装を取得できます。

CEditPrintObj::CEditPrintObj()
{
    m_editObj.m_pParent = this;
    m_printObj.m_pParent = this;
}

ULONG CEditPrintObj::AddRef() 
{ 
    return ++m_dwRef;
}

CEditPrintObj::Release()
{
    if (--m_dwRef == 0)
    {
        delete this;
        return 0;
    }
    return m_dwRef;
}

HRESULT CEditPrintObj::QueryInterface(REFIID iid, void** ppvObj)
{
    if (iid == IID_IUnknown || iid == IID_IPrintInterface)
    {
        *ppvObj = &m_printObj;
        AddRef();
        return NOERROR;
    }
    else if (iid == IID_IEditInterface)
    {
        *ppvObj = &m_editObj;
        AddRef();
        return NOERROR;
    }
    return E_NOINTERFACE;
}

ULONG CEditPrintObj::CEditObj::AddRef() 
{ 
    return m_pParent->AddRef(); 
}

ULONG CEditPrintObj::CEditObj::Release() 
{ 
    return m_pParent->Release(); 
}

HRESULT CEditPrintObj::CEditObj::QueryInterface(
    REFIID iid, void** ppvObj) 
{ 
    return m_pParent->QueryInterface(iid, ppvObj); 
}

ULONG CEditPrintObj::CPrintObj::AddRef() 
{ 
    return m_pParent->AddRef(); 
}

ULONG CEditPrintObj::CPrintObj::Release() 
{ 
    return m_pParent->Release(); 
}

HRESULT CEditPrintObj::CPrintObj::QueryInterface(
    REFIID iid, void** ppvObj) 
{ 
    return m_pParent->QueryInterface(iid, ppvObj); 
}

IUnknown の実装の大部分は、CEditPrintObj::CEditObj および CEditPrintObj::CPrintObj にコードを 2 回書くのではなく、CEditPrintObj クラスに記述します。これによって、プログラム サイズを縮小でき、またバグの回避につながります。ここで重要なのは、IUnknown インターフェイスから QueryInterface を呼び出すことによって、オブジェクトがサポートするすべてのインターフェイスを取得できる点と、このように取得した各インターフェイスからも同じようにインターフェイスを取得できる点です。つまり、どのインターフェイスから呼び出された場合でも、関数 QueryInterface は同じ動作を行う必要があります。埋め込みオブジェクトからその "外側のオブジェクト" の実装を呼び出すために、バック ポインタ (m_pParent) が使用されています。ポインタ m_pParent は CEditPrintObj コンストラクタの中で初期化されます。CEditPrintObj::CPrintObj::PrintObject と CEditPrintObj::CEditObj::EditObject も同じように作成します。少量のコードを追加するだけで、オブジェクトの編集機能という新しい機能を追加できます。インターフェイスが 1 つのメンバ関数だけを持つことはほとんどなく、このような場合は通常 EditObject と PrintObject を 1 つのインターフェイスにまとめます。

ここでは詳細な解説および数多くのコード例を引用しましたが、MFC/OLE クラスには簡単な代替手法が用意されています。MFC では、Windows メッセージ処理に利用されるメッセージ マップに似た方法を使用してインターフェイスを処理しています。この機能は "インターフェイス マップ" と呼ばれています。次にこの機能について説明します。

MFC インターフェイス マップ

MFC/OLE は "インターフェイス マップ" 機能を持っています。この機能は概念上および機構上、MFC の "メッセージ マップ" や "ディスパッチ マップ" に似ています。MFC インターフェイス マップは次のような基本的機能を持っています。

  • CCmdTarget クラスに組み込まれた、IUnknown の標準実装

  • 参照カウントの管理と、AddRefRelease によるサポート

  • QueryInterface のデータ駆動機構

インターフェイス マップには次のような拡張機能もあります。

  • 集約可能な COM オブジェクトの作成機能

  • COM オブジェクトの実装にほかの集約オブジェクトを使用する機能

  • これらの実装のフックや拡張

オブジェクトの集約については、『OLE Programmer's Reference』を参照してください。

MFC インターフェイス マップの基点は CCmdTarget クラスです。CCmdTarget クラスには、参照カウントと IUnknown の実装に関連するすべてのメンバ関数があります。参照カウントの例は CCmdTarget にあります。) OLE COM をサポートするクラスを作成するには CCmdTarget の派生クラスを作成し、マクロや CCmdTarget メンバ関数を利用して必要なインターフェイスを加えます。MFC のインターフェイス マップでは、上の例で示したように各インターフェイスの実装で入れ子になったクラスが使用され、IUnknown の標準実装が簡略化されています。また、コード簡略化のために多くのマクロも用意されています。

MFC インターフェイス マップを利用したクラスを作成するには

  1. CCmdTarget. クラスの直接派生クラスまたは間接派生クラスを作成します。

  2. 派生クラスで関数 DECLARE_INTERFACE_MAP を定義します。

  3. サポートする各インターフェイスに対して、クラス定義でマクロ BEGIN_INTERFACE_PART とマクロ END_INTERFACE_PART を使用します。

  4. 実装ファイルの中で、マクロ BEGIN_INTERFACE_MAPEND_INTERFACE_MAP を使用してクラスのインターフェイス マップを定義します。

  5. BEGIN_INTERFACE_MAPEND_INTERFACE_MAP の間に、サポートする各 IID に対するマクロ INTERFACE_PART を記述します。これによって、IID とそのクラスの中の特定の "部分" が対応付けられます。

  6. 入れ子になったクラスとして、サポートするインターフェイスを実装します。

  7. 親 (CCmdTarget 派生オブジェクト) にアクセスするにはマクロ METHOD_PROLOGUE を使用します。

  8. AddRefReleaseQueryInterface の代わりに CCmdTargetExternalAddRefExternalReleaseExternalQueryInterface を使用できます。

上の例の CPrintEditObj の場合は次のように実装できます。

class CPrintEditObj : public CCmdTarget
{
public:
    // member data and member functions for CPrintEditObj go here

// Interface Maps
protected:
    DECLARE_INTERFACE_MAP()

    BEGIN_INTERFACE_PART(EditObj, IEditInterface)
        STDMETHOD_(void, EditObject)();
    END_INTERFACE_PART(EditObj)

    BEGIN_INTERFACE_PART(PrintObj, IPrintInterface)
        STDMETHOD_(void, PrintObject)();
    END_INTERFACE_PART(PrintObj)
};

この宣言によって CCmdTarget 派生クラスが作成されます。マクロ DECLARE_INTERFACE_MAP はこのクラスがカスタム インターフェイス マップを持つことをフレームワークに対して宣言します。また、マクロ BEGIN_INTERFACE_PARTEND_INTERFACE_PART は入れ子になったクラスを定義します。上の例では、入れ子になったクラスは CEditObj と CPrintObj です。入れ子になったクラス名の先頭には X が付きます。これに対してグローバル クラス名の先頭には "C"、インターフェイス クラス名の先頭には "I" が付きます。入れ子になった 2 つのクラスはそれぞれメンバ変数 m_CEditObj と m_CPrintObj を持ちます。AddRefReleaseQueryInterface はこれらのマクロによって自動的に宣言されるため、ここではそのインターフェイスに特有のメンバ関数 EditObject と PrintObject だけを宣言します。ターゲット プラットフォームに対する適切な _stdcall や仮想キーワードが与えられるように、OLE マクロ STDMETHOD が使用されています。

このクラスに対するインターフェイス マップは次のように作成します。

BEGIN_INTERFACE_MAP(CPrintEditObj, CCmdTarget)
    INTERFACE_PART(CPrintEditObj, IID_IPrintInterface, PrintObj)
    INTERFACE_PART(CPrintEditObj, IID_IEditInterface, EditObj)
END_INTERFACE_MAP()

これにより IID_IPrintInterface IID と m_CPrintObj、IID_IEditInterface IID と m_CEditObj が関連付けられます。CCmdTargetQueryInterface (CCmdTarget::ExternalQueryInterface) はこのマップを参照し、要求があったときに m_CPrintObj や m_CEditObj へのポインタを返します。IID_IUnknown に対するエントリをマップに含める必要はありません。IID_IUnknown が要求されると、マップの先頭のインターフェイス (この場合は m_CPrintObj) が使用されます。

関数 AddRefReleaseQueryInterface はマクロ BEGIN_INTERFACE_PART によって自動的に宣言されますが、次のような記述が必要です。

ULONG FAR EXPORT CEditPrintObj::XEditObj::AddRef()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalAddRef();
}

ULONG FAR EXPORT CEditPrintObj::XEditObj::Release()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return pThis->ExternalRelease();
}

HRESULT FAR EXPORT CEditPrintObj::XEditObj::QueryInterface(
    REFIID iid, void FAR* FAR* ppvObj)
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

void FAR EXPORT CEditPrintObj::XEditObj::EditObject()
{
    METHOD_PROLOGUE(CEditPrintObj, EditObj)
    // code to "Edit" the object, whatever that means...
}

CEditPrintObj::CPrintObj の実装も上の CEditPrintObj::CEditObj と同様です。ここに示した関数を自動的に生成するマクロを提供することも可能ですが (開発初期バージョンの MFC/OLE では実際に提供されていました)、マクロが複数行に展開される場合はブレークポイントの設定が難しくなります。このため、コードは手動で展開されます。

フレームワークのメッセージ マップを利用すると、次のような作業が不要になります。

  • QueryInterface の実装

  • AddRef と Release の実装

  • 各インターフェイスに対するこれらの組み込みメソッドの宣言

フレームワーク内部でもメッセージ マップが使用されています。このため、フレームワーク クラス (COleServerDoc など) の派生クラスを作成すると、そのクラスは自動的に数種類のインターフェイスを持つことになります。また、このインターフェイスを変更したり、まったく新しいものに置き換えることも可能です。これは、フレームワークが基本クラスからのインターフェイス マップの "継承" をサポートしているからです。このため、BEGIN_INTERFACE_MAP は第 2 パラメータとして基本クラス名を必要とします。

5hhehwba.alert_note(ja-jp,VS.90).gifメモ :

通常は、MFC から埋め込みに特殊化したインターフェイスを継承しても、MFC の組み込み OLE インターフェイスを再利用できません。これは、包含先の CCmdTarget 派生オブジェクトにアクセスするために、マクロ METHOD_PROLOGUE を使用しているからです。このマクロは CCmdTarget の派生オブジェクトからそこに埋め込まれているオブジェクトにアクセスするときに固定長のオフセット値を使用します。たとえば COleClientItem::XAdviseSink では、MFC 版から埋め込み版の XMyAdviseSink を派生できません。これは XAdviseSink では、COleClientItem オブジェクトの先頭からのオフセット値が異なる値になるためです。

5hhehwba.alert_note(ja-jp,VS.90).gifメモ :

しかし、これらの関数に MFC の既定の動作を求めるときは、MFC 版の関数に処理を任せることができます。これは COleFrameHook クラスで MFC 版の IOleInPlaceFrame (XOleInPlaceFrame) によってなされます。このクラスは多くの関数に対して m_xOleInPlaceUIWindow に処理を任せます。この方法は、多数のインターフェイスを含むオブジェクトの実行時サイズを小さくする目的のために使用されます。この処理ではバック ポインタ (前の m_pParent など) を必要としません。

集約とインターフェイス マップ

MFC はスタンドアロンの COM オブジェクトのほかに、集約もサポートしています。ここでは集約の詳細については説明しません。詳細については、『OLE Programmer's Reference』を参照してください。このテクニカル ノートではフレームワークとインターフェイス マップに組み込まれている集約に限定して説明します。

集約の利用方法には次の 2 種類があります。(1) 集約をサポートする COM オブジェクトの利用。(2) 集約の一部となることができるオブジェクトの作成。これらの機能はそれぞれ "集約オブジェクトの利用" と "集約可能オブジェクトの作成" と呼ばれます。MFC ではこの 2 つがサポートされています。

集約オブジェクトの利用

集約オブジェクトを使用するには、集約を QueryInterface 機構に結び付ける必要があります。つまり、本体オブジェクトに最初から含まれているかのように集約オブジェクトを動作させる必要があります。MFC のインターフェイス マップ機構にオブジェクトを結びつけるには、マクロ INTERFACE_PART で入れ子になったオブジェクトを IID にリンクするほか、集約オブジェクトを CCmdTarget 派生クラスの一部として宣言します。宣言するには、INTERFACE_AGGREGATE マクロを使用します。このマクロはメンバ変数 (IUnknown オブジェクトまたはその派生クラスのオブジェクトへのポインタ) を指定してインターフェイス マップ機構に組み込むことができます。CCmdTarget::ExternalQueryInterface を呼び出したときのポインタが NULL でなく、指定された IIDCCmdTarget オブジェクト自身がサポートする IID でない場合は、集約オブジェクトのメンバ関数 QueryInterface が自動的に呼び出されます。

マクロ INTERFACE_AGGREGATE を使用するには

  1. 集約オブジェクトへのポインタ (IUnknown*) となるメンバ変数を宣言します。

  2. インターフェイス マップにマクロ INTERFACE_AGGREGATE を記述します。このマクロでこのメンバ変数を名前で指定します。

  3. 適切な時点で (普通は CCmdTarget::OnCreateAggregates の中で) このメンバ変数を NULL 値以外の値に初期化します。

次に例を示します。

class CAggrExample : public CCmdTarget
{
public:
    CAggrExample();

protected:
    LPUNKNOWN m_lpAggrInner;
    virtual BOOL OnCreateAggregates();

    DECLARE_INTERFACE_MAP()
    // "native" interface part macros may be used here
};

CAggrExample::CAggrExample()
{
    m_lpAggrInner = NULL;
}

BOOL CAggrExample::OnCreateAggregates()
{
    // wire up aggregate with correct controlling unknown
    m_lpAggrInner = CoCreateInstance(CLSID_Example,
        GetControllingUnknown(), CLSCTX_INPROC_SERVER,
        IID_IUnknown, (LPVOID*)&m_lpAggrInner);
    if (m_lpAggrInner == NULL)
        return FALSE;
    // optionally, create other aggregate objects here
    return TRUE;
}

BEGIN_INTERFACE_MAP(CAggrExample, CCmdTarget)
    // native "INTERFACE_PART" entries go here
    INTERFACE_AGGREGATE(CAggrExample, m_lpAggrInner)
END_INTERFACE_MAP()

m_lpAggrInner はコンストラクタによって NULL に初期化されます。既定の QueryInterface では NULL 値のメンバ変数は無視されます。集約オブジェクトは OnCreateAggregates で作成するのが適切です。MFC の COleObjectFactory 以外でオブジェクトを作成するときは、この関数を明示的に呼び出します。CCmdTarget::OnCreateAggregates で集約オブジェクトを作成し、CCmdTarget::GetControllingUnknown を使用する理由は、集約可能オブジェクトの作成方法を解説するときに説明します。

上の方法を使用すると、オブジェクト本来のインターフェイスに加えて集約オブジェクトが持つすべてのインターフェイスを持つことができます。集約オブジェクトがサポートするインターフェイスの一部だけを利用するには、CCmdTarget::GetInterfaceHook をオーバーライドします。この方法は、QueryInterface と同じように、下位のフックを可能にします。通常は、集約オブジェクトがサポートするすべてのインターフェイスを使用します。

集約可能オブジェクトの作成

オブジェクトを集約可能にするには、AddRefReleaseQueryInterface の処理を "controlling unknown" に任せる必要があります。集約オブジェクトを相手のオブジェクトの一部にするには、AddRefReleaseQueryInterface の処理をほかの IUnknown 派生オブジェクトに任せる必要があります。この "controlling unknown" はオブジェクトが作成されるときにオブジェクトに与えられます。つまり、COleObjectFactory のメンバに "controlling unknown" が与えられます。COleObjectFactory をこのように実装すると多少のオーバーヘッドが発生するため、不要な場合もあります。このため、この処理はオプションとして選択できるようになっています。オブジェクトを集約可能にするときは、そのオブジェクトのコンストラクタから CCmdTarget::EnableAggregation を呼び出します。

オブジェクトで集約を使用するときは、適切な "controlling unknown" が集約オブジェクトに渡されるようにする必要があります。通常は、集約が作成されるときに IUnknown ポインタがオブジェクトに渡されます。たとえば、CoCreateInstance を使用してオブジェクトを生成すると、パラメータ pUnkOuter が "controlling unknown" になります。正確な "controlling unknown" ポインタは CCmdTarget::GetControllingUnknown を呼び出して調べることができます。しかし、コンストラクタ中ではこの関数の戻り値は保証されません。このため、集約オブジェクトは CCmdTarget::OnCreateAggregates をオーバーライドした関数の中でのみ作成するようにします。この中では (COleObjectFactory から作成されたものであっても) GetControllingUnknown の戻り値は保証されます。

架空の参照カウントの作成、破棄を行うときに、オブジェクトが正しい参照カウントを操作できるようにすることも重要です。これを保証するために InternalReleaseInternalAddRef ではなく、必ず ExternalAddRefExternalRelease を呼び出すようにしてください。集約をサポートするクラスでは InternalReleaseInternalAddRef を呼び出すことはほとんどありません。

参考

独自のインターフェイスを定義したり、フレームワークの OLE インターフェイスをオーバーライドするなど、OLE の高度な機能を利用するときは、インターフェイス マップ機構の使い方を理解しておく必要があります。

ここでは、このような高度な機能を実現しているマクロや API について説明します。

CCmdTarget::EnableAggregation - 関数の説明

voidEnableAggregation();

解説

OLE 集約をサポートするときに、この関数を派生クラスのコンストラクタで呼び出します。この関数を呼び出すことによって、集約可能オブジェクトに必要な IUnknown 機能が用意されます。

CCmdTarget::ExternalQueryInterface - 関数の説明

DWORDExternalQueryInterface( 
   constvoidFAR*lpIID, 
   LPVOIDFAR*ppvObj 
);

解説

パラメータ

  • lpIID
    IID への far ポインタ (QueryInterface の第 1 引数)。

  • ppvObj
    IUnknown* へのポインタ (QueryInterface の第 2 引数)。

解説

IUnknown を実装するとき、この関数をクラスの各インターフェイスに対して呼び出します。この関数はオブジェクトのインターフェイス マップに基づき、標準的なデータ駆動型の QueryInterface を実行します。この関数の戻り値は HRESULT 型にキャストする必要があります。集約オブジェクトの場合、この関数はローカルなインターフェイス マップを使用する代わりに、"controlling IUnknown" を呼び出します。

CCmdTarget::ExternalAddRef - 関数の説明

DWORDExternalAddRef();

解説

IUnknown::AddRef を実装するときに、この関数をクラスの各インターフェイスに対して呼び出します。戻り値は CCmdTarget オブジェクトの新しい参照カウント値です。集約オブジェクトの場合、この関数はローカルな参照カウントを操作せずに、"controlling IUnknown" を呼び出します。

CCmdTarget::ExternalRelease - 関数の説明

DWORD ExternalRelease();

解説

IUnknown::Release を実装するときに、この関数をクラスの各インターフェイスに対して呼び出します。戻り値はオブジェクトの新しい参照カウント値を示します。集約オブジェクトの場合、この関数はローカルな参照カウントを操作せずに、"controlling IUnknown" を呼び出します。

DECLARE_INTERFACE_MAP - マクロの説明

DECLARE_INTERFACE_MAP

解説

このマクロは、CCmdTarget からの派生クラスのうち、インターフェイス マップを持つすべてのクラスで使用します。使用方法は DECLARE_MESSAGE_MAP と同じです。このマクロはクラス定義 (通常はヘッダー ファイル内) で使用します。DECLARE_INTERFACE_MAP を指定したクラスでは、実装ファイル (.CPP) でマクロ BEGIN_INTERFACE_MAPEND_INTERFACE_MAP を使用してインターフェイス マップを定義する必要があります。

BEGIN_INTERFACE_PART、END_INTERFACE_PART - マクロの説明

BEGIN_INTERFACE_PART( 
   localClass,
   iface 
);
END_INTERFACE_PART( 
   localClass 
)

解説

パラメータ

  • localClass
    このインターフェイスを実装するクラスの名前。

  • iface
    インターフェイス名。

解説

クラスに与えるインターフェイスごとに、BEGIN_INTERFACE_PARTEND_INTERFACE_PART のペアを記述する必要があります。これらのマクロは、OLE インターフェイスから派生させるローカル クラスと、そのクラスの埋め込みメンバ変数を定義します。メンバ関数 AddRefReleaseQueryInterface は自動的に宣言されます。これ以外のメンバ関数をインターフェイスで使用する場合は、直接宣言する必要があります。これらの宣言はマクロ BEGIN_INTERFACE_PARTEND_INTERFACE_PART の間に記述します。

引数 iface は作成する OLE インターフェイス (IAdviseSinkIPersistStorage など、またはカスタム インターフェイス) を示します。

引数 localClass に定義するローカル クラス名を指定します。名前の先頭には 'X' が自動的に付けられます。これはグローバル クラス名との衝突を回避するためです。また埋め込みメンバの名前は localClass 名の前に 'm_x' を付けたものになります。

次に例を示します。

BEGIN_INTERFACE_PART(MyAdviseSink, IAdviseSink)
   STDMETHOD_(void,OnDataChange)(LPFORMATETC, LPSTGMEDIUM);
   STDMETHOD_(void,OnViewChange)(DWORD, LONG);
   STDMETHOD_(void,OnRename)(LPMONIKER);
   STDMETHOD_(void,OnSave)();
   STDMETHOD_(void,OnClose)();
END_INTERFACE_PART(MyAdviseSink)

これによって IAdviseSink から派生されるローカル クラス XMyAdviseSink が定義されます。またこのクラスのメンバ名は m_xMyAdviseSink になります。

5hhehwba.alert_note(ja-jp,VS.90).gifメモ :

STDMETHOD_ で始まる行は基本的に OLE2.H からコピーしたものであり、変更が加えられます。OLE2.H からコピーすると、発見が困難なエラーを回避できます。

BEGIN_INTERFACE_MAP、END_INTERFACE_MAP - マクロの説明

BEGIN_INTERFACE_MAP( 
   theClass,
   baseClass 
)END_INTERFACE_MAP

解説

パラメータ

  • theClass
    このインターフェイス マップを持つクラスの名前。

  • baseClass
    theClass の基本クラス。

解説

実際にインターフェイス マップを定義するには、実装ファイルにマクロ BEGIN_INTERFACE_MAPEND_INTERFACE_MAP を記述します。インターフェイスごとに最低 1 個の INTERFACE_PART マクロを記述します。このクラスで使用する集約ごとに 1 個の INTERFACE_AGGREGATE マクロを記述します。

INTERFACE_PART - マクロの説明

INTERFACE_PART( 
   theClass,
   iid, 
   localClass 
)

解説

パラメータ

  • theClass
    インターフェイス マップを持つクラスの名前。

  • iid
    埋め込みクラスに割り当てられる IID

  • localClass
    ローカル クラスの名前。'X' は付けません。

解説

このマクロはオブジェクトでサポートする各インターフェイスを定義する BEGIN_INTERFACE_MAP マクロと END_INTERFACE_MAP マクロの間で使用します。このマクロは theClass と localClass で表されたクラスのメンバに IID を割り当てます。localClass の先頭には 'm_x' が自動的に付けられます。1 個のメンバに複数の IID を割り当てることもできます。これは、"最派生" インターフェイスを 1 個だけ作成し、これをすべての中間インターフェイスとして使用するときにも役に立ちます。この方法を使用した例が IOleInPlaceFrameWindow インターフェイスです。このインターフェイスの階層構造は次のようになっています。

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

IOleInPlaceFrameWindow を実装しているオブジェクトのクライアントは、実際に実装した "最派生" インターフェイス IOleInPlaceFrameWindow 以外に、IOleUIWindowIOleWindowIUnknown の各インターフェイスに対しても QueryInterface を適用できます。この適用を処理するには、複数の INTERFACE_PART マクロを使用して、すべての基本インターフェイスを IOleInPlaceFrameWindow インターフェイスに割り当てることができます。

クラス定義ファイルには次のように記述されます。

BEGIN_INTERFACE_PART(CMyFrameWindow, IOleInPlaceFrameWindow)

クラス実装ファイルには次のように記述されます。

BEGIN_INTERFACE_MAP(CMyWnd, CFrameWnd)
    INTERFACE_PART(CMyWnd, IID_IOleWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleUIWindow, MyFrameWindow)
    INTERFACE_PART(CMyWnd, IID_IOleInPlaceFrameWindow, MyFrameWindow)
END_INTERFACE_MAP

IUnknown は常に必要なので、フレームワークによって自動的にサポートされます。

INTERFACE_PART - マクロの説明

INTERFACE_AGGREGATE( 
   theClass,
   theAggr 
)

解説

パラメータ

  • theClass
    インターフェイス マップを持つクラスの名前。

  • theAggr
    集約するメンバ変数の名前。

解説

このマクロは、クラスが集約オブジェクトを使用することをフレームワークに知らせます。このマクロは BEGIN_INTERFACE_PART マクロと END_INTERFACE_PART マクロの間に記述します。集約オブジェクトは IUnknown から派生した別のオブジェクトです。集約オブジェクトと INTERFACE_AGGREGATE マクロを使用すると、集約オブジェクトが持つすべてのインターフェイスがサポートされます。引数 theAggrIUnknown の直接または間接派生クラスのメンバ変数名です。INTERFACE_AGGREGATE マクロをインターフェイス マップの中に記述するときは、必ず INTERFACE_PART マクロの後に記述します。

参照

その他の技術情報

番号順テクニカル ノート

カテゴリ別テクニカル ノート