共用方式為


TN038:MFC/OLE IUnknown 實現

備註

自第一次包含在在線文件中以來,尚未更新下列技術附注。 因此,某些程式和主題可能已過期或不正確。 如需最新信息,建議您搜尋在線檔索引中感興趣的主題。

OLE 2 的核心是“OLE 元件物件模型”或 COM。 COM 定義合作物件如何彼此通訊的標準。 這包括「物件」外觀的詳細數據,包括如何在物件上分派方法。 COM 也會定義基類,從中衍生所有 COM 兼容類別。 這個基類是 IUnknown。 雖然 IUnknown 介面稱為「C++類別」,但 COM 並非任何一種語言所特有,它可以在 C、PASCAL 或任何可支援 COM 物件二進位配置的語言中實作。

OLE 將衍生自 IUnknown 的所有類別稱為「介面」。這是一個重要的區別,因為 IUnknown 之類的「介面」沒有實作。 它只會定義物件通訊的通訊協定,而不是這些實作所執行之用途的詳細數據。 對於允許最大彈性的系統而言,這是合理的。 MFC 的工作是實作 MFC/C++程式的預設行為。

若要瞭解 MFC 的 IUnknown 實作,您必須先了解這個介面是什麼。 IUnknown 的簡化版本定義如下:

class IUnknown
{
public:
    virtual HRESULT QueryInterface(REFIID iid, void** ppvObj) = 0;
    virtual ULONG AddRef() = 0;
    virtual ULONG Release() = 0;
};

備註

某些必要的呼叫約定詳細資訊,例如 __stdcall,在此示例中被排除。

AddRefRelease 成員函式會控制物件的記憶體管理。 COM 會使用參考計數配置來追蹤物件。 在 C++ 中,物件從來不會被直接引用。 相反地,COM 物件一律會透過指標參考。 若要在擁有者完成使用物件時釋放物件,則會呼叫物件的 Release 成員(而不是使用運算符 delete,如同傳統C++物件所做的那樣)。 參考計數機制允許管理單一物件的多個參考。 AddRefRelease 的實作會維護對象的參考計數, 對象在參考計數達到零之前不會刪除。

從實作的觀點來看,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 很有用的地方。 它可讓您在同一個物件上取得不同的「介面」。 這些介面通常衍生自 IUnknown ,並藉由新增成員函式來新增其他功能。 COM 介面永遠不會在 介面中宣告成員變數,而且所有成員函式都會宣告為純虛擬。 例如,

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

如果您只有IUnknown,若要取得 IPrintInterface,請使用IID呼叫IPrintInterfaceIID是可唯一識別介面的128位數位。 每個由您或 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 和 IUnknown 介面的物件呢?在此情況下,由於 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;
}

如您所見,如果辨識介面標識碼 (IID),則會將指標傳回至您的物件;否則會發生錯誤。 另請注意,成功的 QueryInterface 會導致隱含的 AddRef。 當然,您也必須實作 CEditObj::Print。 這很簡單,因為 IPrintInterface 是直接衍生自 IUnknown 介面。 不過,如果您想要支援兩個不同的介面,這兩個介面都是衍生自 IUnknown,請考慮下列事項:

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

雖然實作支援 IEditInterface 和 IPrintInterface 的類別有許多不同方式,包括使用 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 類別中,而不是在 CEditPrintObj::CEditObj 和 CEditPrintObj::CPrintObj 中複製程式代碼。 這樣可減少程式代碼數量,並避免 Bug。 這裡的重點是,從 IUnknown 介面,您可以呼叫 QueryInterface 來擷取物件可能支援的任何介面,並從這些介面中,執行相同的動作。 這表示每個介面中所有可用的 QueryInterface 函式都必須以完全相同的方式運作。 為了讓這些內嵌物件在「外部物件」中呼叫實作,會使用反向指標(m_pParent)。 m_pParent指標會在 CEditPrintObj 建構函式期間初始化。 然後,您將實作 CEditPrintObj::CPrintObj::PrintObject 和 CEditPrintObj::CEditObj::EditObject。 新增了相當多的程式代碼來新增一項功能, 這是編輯物件的能力。 幸運的是,介面只有單一成員函式的情況相當罕見(雖然確實會發生),而在這種情況下,EditObject 和 PrintObject 通常會合併成一個介面。

對於如此簡單的情境,卻有這麼多的說明和程式碼。 MFC/OLE 類別提供更簡單的替代方案。 MFC 實作會使用一種類似於 Windows 訊息封裝訊息對應表的方法技術。 這項功能稱為 「介面對應」,並在下一節中有討論。

MFC 介面映射

MFC/OLE 包含類似 MFC 之「訊息對應」和「分派對應」的概念和執行中的「介面對應」實作。 MFC 的介面映射核心功能如下:

  • 內建於 類別的標準 CCmdTarget 實作。

  • 維護由 AddRefRelease 所修改的參考計數

  • QueryInterface 的數據驅動實作

此外,介面映射支援下列進階功能:

  • 建立可匯總 COM 物件的支援

  • 支援在 COM 物件的實作中使用匯總物件

  • 實作可連結且可延伸

如需匯總的詳細資訊,請參閱 匯總 主題。

MFC 的介面映射支援是以 CCmdTarget 類別為基礎。 CCmdTargethas-a” 參考計數以及與 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_MAP 和 END_INTERFACE_MAP 來定義類別的介面對應。

  5. 針對每個支援的 IID,請使用 BEGIN_INTERFACE_MAP 與 END_INTERFACE_MAP 巨集之間的 INTERFACE_PART 巨集,將該 IID 對應至類別的特定「部分」。

  6. 實作每個巢狀類別,這些類別代表您支持的介面。

  7. 使用 METHOD_PROLOGUE 巨集來存取父 CCmdTarget系衍生物件。

  8. AddRefReleaseQueryInterface 可以委派給 CCmdTarget 這些函式的實作(ExternalAddRefExternalReleaseExternalQueryInterface)。

上述 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_PART和END_INTERFACE_PART巨集會定義巢狀類別,在此案例中,名稱為 CEditObj 和 CPrintObj(X 僅用來區分巢狀類別與開頭為 “C” 的全域類別,以及開頭為 “I” 的介面類別)。 會分別建立這些類別的兩個巢狀成員:m_CEditObj和m_CPrintObj。 巨集會自動宣告 AddRefReleaseQueryInterface 函式;因此,您只會宣告這個介面特有的函式:EditObject 和 PrintObject(使用 OLE 巨集 STDMETHOD,以便針對目標平臺提供 _stdcall 和虛擬關鍵詞)。

若要為此類別建立介面映射:

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 連接m_CEditObj。 CCmdTarget QueryInterfaceCCmdTarget::ExternalQueryInterface)的實作會使用此對應,在請求時傳回 m_CPrintObj 和 m_CEditObj 的指標。 不需要為 IID_IUnknown 包含一個項目;在要求 IID_IUnknown 時,架構會使用映射中的第一個介面(在此案例中為 m_CPrintObj)。

儘管BEGIN_INTERFACE_PART巨集會自動為您宣告 AddRefReleaseQueryInterface 函式,您仍然需要實作它們:

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將基類名稱作為其第二個參數的原因。

備註

通常不可能僅透過從 MFC 版本繼承該介面的內嵌特化來重複使用 MFC 內建的 OLE 介面實作。 這是不可能的,因為使用 METHOD_PROLOGUE 巨集來訪問所包含的 CCmdTarget 衍生物件意味著內嵌物件與 衍生物件之間有一個CCmdTarget。 例如,這表示您無法從 MFC 的COleClientItem::XAdviseSink實作衍生內嵌 XMyAdviseSink,因為 XAdviseSink 依賴於在COleClientItem物件頂端的特定位移。

備註

不過,您可以針對您想要 MFC 預設行為的所有函式,委派給 MFC 實作。 在 IOleInPlaceFrame 類別的 COleFrameHook(XOleInPlaceFrame)的 MFC 實作中完成(它將許多函式委託給 m_xOleInPlaceUIWindow)。 此設計被選擇用來減少實作許多介面之物件的執行時記憶體大小,使其不需要回指標(例如上一節中使用 m_pParent 的方式)。

匯總和介面映射

除了支持獨立 COM 物件之外,MFC 也支持匯總。 匯總本身太複雜,無法在這裡討論;如需匯總的詳細資訊,請參閱 匯總 主題。 此備註將簡單描述框架和介面映射中內建的匯總支持。

有兩種方式可以使用匯總:(1) 使用支持匯總的 COM 物件,以及實作另一個可匯總的物件。 這些功能可以稱為「使用匯總物件」和「使物件可匯總」。 MFC 支援兩者。

使用匯總物件

若要使用匯總對象,必須有某種方式將匯總系結至 QueryInterface 機制。 換句話說,匯總對象的行為必須如同是物件的原生部分一樣。 因此,除了使用 INTERFACE_PART 巨集來將巢狀對象對應到 IID 之外,您還可以將匯總物件宣告為您衍生類別中的一部分,這樣就可以更好地結合 MFC 的介面對應機制。 若要這樣做,會使用「INTERFACE_AGGREGATE」巨集。 這可讓您指定一個成員變數(必須是指向 IUnknown 或衍生類別的指標),該成員變數將被整合到介面對應機制中。 如果呼叫 時CCmdTarget::ExternalQueryInterface指標不是 NULL,則架構會自動呼叫匯總物件的 QueryInterface 成員函式,如果IID所要求的 不是物件本身所IID支援的原生CCmdTarget函式之一。

使用INTERFACE_AGGREGATE巨集

  1. 宣告成員變數 (a 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 的實作必須委派給「控制未知」。換句話說,若要成為物件的一部分,它必須將 AddRefReleaseQueryInterface 委派給不同的物件,而且衍生自 IUnknown。 建立物件時,這個「控制未知」會提供給物件,也就是提供給 COleObjectFactory 的實作。 執行此操作會產生少量的運行開銷,而且在某些情況下並不理想,因此MFC將其設為可選項。 若要讓物件可匯總,請從物件的建構函式呼叫 CCmdTarget::EnableAggregation

如果物件也使用聚合,您還必須確保將正確的「控制指標」傳遞給聚合物件。 這個 IUnknown 指標通常會在建立匯總時傳遞至物件。 例如,pUnkOuter 參數是使用 CoCreateInstance 所建立物件的「控制未知數」。 透過呼叫 CCmdTarget::GetControllingUnknown 可取得正確的「未知控制項」指標。 不過,從該函式傳回的值在建構函式期間無效。 基於此原因,建議您僅在CCmdTarget::OnCreateAggregates覆寫中建立匯總,這樣當從GetControllingUnknown實作中建立時,其COleObjectFactory返回值仍然是可靠的。

在新增或釋放人工參考計數時,非常重要的是物件必須正確地處理參考計數。 若要確保是這種情況,請一律呼叫 ExternalAddRefExternalRelease,而不是 InternalReleaseInternalAddRef。 在支持聚合的類別中,調用 InternalReleaseInternalAddRef 的情況很少見。

參考資料

使用 OLE 的進階功能,例如定義自己的介面或覆寫現有系統中 OLE 介面的實作,需要運用基礎的介面對應機制。

本節討論每個巨集和用來實作這些進階功能的 API。

CCmdTarget::EnableAggregation - 函式描述

void EnableAggregation();

備註

如果您想要支援此類型物件的 OLE 匯總,請在衍生類別的建構函式中呼叫此函式。 這是為可聚合物件所需的特殊 IUnknown 實作做準備。

CCmdTarget::ExternalQueryInterface - 函式描述

DWORD ExternalQueryInterface(
    const void FAR* lpIID,
    LPVOIDFAR* ppvObj
);

參數

lpIID
IID 的長指標(QueryInterface 的第一個參數)

ppvObj
IUnknown* 的指標(QueryInterface 的第二個自變數)

備註

針對類別實作的每個介面,在您的 IUnknown 實作中呼叫此函式。 此函式會根據物件的介面對應,提供 QueryInterface 的標準數據驅動實作。 必須將傳回值轉換成 HRESULT。 如果物件是聚合的,這個函式會呼叫控制的IUnknown,而不是使用本地介面映射。

CCmdTarget::ExternalAddRef - 函式描述

DWORD ExternalAddRef();

備註

在您的 IUnknown::AddRef 實作中,針對類別實作的每個介面呼叫此函式。 傳回值是 CCmdTarget 物件的新參考計數值。 如果匯總物件,此函式會呼叫 「控制 IUnknown」,而不是作本機參考計數。

CCmdTarget::ExternalRelease - 函式描述

DWORD ExternalRelease();

備註

在您的 IUnknown::Release 實作中,針對類別實作的每個介面呼叫此函式。 傳回值表示 物件上的新參考計數。 如果匯總物件,此函式會呼叫 「控制 IUnknown」,而不是作本機參考計數。

DECLARE_INTERFACE_MAP — 宏指令描述

DECLARE_INTERFACE_MAP

備註

在具有介面對應的任何衍生 CCmdTarget 類別中使用這個巨集。 使用的方式與DECLARE_MESSAGE_MAP大致相同。 這個巨集呼叫應該放在類別定義中,通常是在標頭檔案(.H)中。 具有 DECLARE_INTERFACE_MAP 的類別必須在實作檔 (.CPP) 中使用 BEGIN_INTERFACE_MAP 和 END_INTERFACE_MAP 巨集來定義介面對應。

BEGIN_INTERFACE_PART和END_INTERFACE_PART — 巨集描述

BEGIN_INTERFACE_PART(localClass, iface);
END_INTERFACE_PART(localClass)

參數

localClass
實作 介面的類別名稱

iface
這個類別實作的介面名稱

備註

針對您的類別要實作的每個介面,您必須有一個 BEGIN_INTERFACE_PART 和 END_INTERFACE_PART 組。 這些巨集會定義一個衍生自您所定義的 OLE 介面的本地類別,同時也定義此類的一個內嵌成員變數。 AddRefReleaseQueryInterface 成員會自動宣告。 您必須包含屬於所實作介面一部分之其他成員函式的宣告(這些宣告位於BEGIN_INTERFACE_PART和END_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)

會定義名為 XMyAdviseSink 的本機類別,該類別衍生自 IAdviseSink,並且在其宣告類別中是一個名為 m_xMyAdviseSink 的成員。注意:

備註

開頭是 STDMETHOD_ 的行基本上是從 OLE2.H 複製過來,並稍作修改。 從 OLE2 複製它們。H 可減少難以解決的錯誤。

BEGIN_INTERFACE_MAP和END_INTERFACE_MAP - 宏描述

BEGIN_INTERFACE_MAP(theClass, baseClass)
END_INTERFACE_MAP

參數

theClass
將定義介面映射的類別

baseClass
theClass 的基礎類別。

備註

在實作檔案中會使用 BEGIN_INTERFACE_MAP 和 END_INTERFACE_MAP 巨集來實際定義介面對應。 每個已實作的介面都會有一或多個 INTERFACE_PART 巨集被調用。 針對類別所使用的每個匯總,有一個INTERFACE_AGGREGATE巨集調用。

INTERFACE_PART — 巨集描述

INTERFACE_PART(theClass, iid, localClass)

參數

theClass
包含介面對應之類別的名稱。

iid
IID映射到嵌入的類別。

localClass
本地類別的名稱(不包含 'X')。

備註

此巨集用於BEGIN_INTERFACE_MAP巨集與END_INTERFACE_MAP巨集之間,以支持您對象的每個介面。 它可讓您將 IID 對應至 Class 和 localClass 所指示類別的成員。 'm_x' 會自動新增至 localClass 。 注意,可能有多個 IID 與單一成員相關聯。 當您只實作「最衍生」介面,而且想要提供所有中間介面時,這非常有用。 其中一個很好的範例是 IOleInPlaceFrameWindow 介面。 其階層看起來像這樣:

IUnknown
    IOleWindow
        IOleUIWindow
            IOleInPlaceFrameWindow

如果對象實作 IOleInPlaceFrameWindow,則用戶端可以在 QueryInterface 下列任何介面上: IOleUIWindowIOleWindowIUnknown,除了「最衍生的」介面(您實際實作的介面 IOleInPlaceFrameWindow )。 若要處理此作業,您可以使用多個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巨集,您可以讓匯總支援的所有介面似乎都由 物件直接支援。 theAggr 參數只是您類別中的一個成員變數名稱,而該類別是由 IUnknown 衍生出來的(不論直接或間接)。 在介面映射中放置時,所有INTERFACE_AGGREGATE巨集都必須遵循INTERFACE_PART巨集。

另請參閱

依編號的技術注意事項
依類別排序的技術注意事項