向一個類中添加某個接口的實現,這是很常見的需求,特別是用在事件通知、連接點中更是多見。MFC類庫內的很多類也都有這樣的需求,比如類COleControl就實現了很多的接口。MFC自己實現的方法都用的是嵌套類,並且定義了幾個宏來簡化該過程。用同樣的方法,我們也可以很方便的在自己的類中添加一個接口的實現。CCmdTarget中實現了接口IDispatch,以及IUnknown 的三個函數的缺省實現。一般的MFC類都會從CCmdTarget繼承,所以這裡講的是典型的向CCmdTarget的派生類添加接口的方法。
比如,有一個類CSampleView從CVIEw中繼承。現在要給它添加一個新的接口IMyTest,該接口只有一個空的方法Test()。添加過程如下:
(1)CSampleVIEw類定義中加入以下代碼:
DECLARE_INTERFACE_MAP() //聲明接口映射
BEGIN_INTERFACE_PART(TestInterface, IMyTest) //聲明實現接口IMyTest的嵌套類
STDMETHOD(Test)();
END_INTERFACE_PART(FontNotify2)
(2)CSampleVIEw類實現中加入以下代碼:
BEGIN_INTERFACE_MAP(CSampleVIEw, CCmdTarget)
INTERFACE_PART(CSampleVIEw, IID_IMyTest, TestInterface)
END_INTERFACE_MAP()
STDMETHODIMP_(ULONG) CSampleVIEw::XTestInterface::AddRef( )
{
METHOD_PROLOGUE_EX(CSampleVIEw, TestInterface)
return (ULONG)pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CSampleVIEw::XTestInterface::Release( )
{
METHOD_PROLOGUE_EX(CSampleVIEw, TestInterface)
return (ULONG)pThis->ExternalRelease();
}
STDMETHODIMP CSampleVIEw::XTestInterface::QueryInterface( REFIID iid, LPVOID FAR* ppvObj )
{
METHOD_PROLOGUE_EX(CSampleVIEw, TestInterface)
return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj) ;
}
STDMETHODIMP CSampleVIEw::XTestInterface::Test()
{
METHOD_PROLOGUE_EX(CSampleVIEw, TestInterface)
// do something you like
return S_OK ;
}
揭開宏的神秘面紗,看看它到底是什麼東西。以下都是簡化的版本。
(1)DECLARE_INTERFACE_MAP
struct AFX_INTERFACEMAP_ENTRY
{
const void* piid;
// the interface id (IID) (NULL for aggregate)
size_t nOffset;
// offset of the interface vtable from m_unknown
};
struct AFX_INTERFACEMAP
{
const AFX_INTERFACEMAP* (PASCAL* pfnGetBaseMap)(); // NULL is root class
const AFX_INTERFACEMAP_ENTRY* pEntry; // map for this class
};
#define DECLARE_INTERFACE_MAP()
private:
static const AFX_INTERFACEMAP_ENTRY _interfaceEntrIEs[];
protected:
static AFX_DATA const AFX_INTERFACEMAP interfaceMap;
static const AFX_INTERFACEMAP* PASCAL _GetBaseInterfaceMap();
virtual const AFX_INTERFACEMAP* GetInterfaceMap() const;
(2)BEGIN_INTERFACE_PART/END_INTERFACE_PART
#define BEGIN_INTERFACE_PART(localClass, baseClass) // 定義了一個嵌套類
class X##localClass : public baseClass
{
public:
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
STDMETHOD(QueryInterface)(REFIID iid, LPVOID* ppvObj);
#define END_INTERFACE_PART(localClass)
} m_x##localClass;
frIEnd class X##localClass;
(3)BEGIN_INTERFACE_MAP/INTERFACE_PART/END_INTERFACE_MAP
#define offsetof(s,m) (size_t)&(((s *)0)->m)
#define BEGIN_INTERFACE_MAP(theClass, theBase)
const AFX_INTERFACEMAP* PASCAL theClass::_GetBaseInterfaceMap()
{ return &theBase::interfaceMap; }
const AFX_INTERFACEMAP* theClass::GetInterfaceMap() const
{ return &theClass::interfaceMap; }
AFX_COMDAT const AFX_DATADEF AFX_INTERFACEMAP theClass::interfaceMap =
{ &theClass::_GetBaseInterfaceMap, &theClass::_interfaceEntrIEs[0], };
AFX_COMDAT const AFX_DATADEF AFX_INTERFACEMAP_ENTRY theClass::_interfaceEntrIEs[] =
{
#define INTERFACE_PART(theClass, iid, localClass)
{ &iid, offsetof(theClass, m_x##localClass) },
#define END_INTERFACE_MAP()
{ NULL, (size_t)-1 }
};
(4)METHOD_PROLOGUE_EX
#define METHOD_PROLOGUE_EX(theClass, localClass)
METHOD_PROLOGUE(theClass, localClass)
#define METHOD_PROLOGUE(theClass, localClass)
theClass* pThis =
((theClass*)((BYTE*)this - offsetof(theClass, m_x##localClass)));
AFX_MANAGE_STATE(pThis->m_pModuleState)
pThis; // avoid warning from compiler
METHOD_PROLOGUE最大的作用就是得到pThis指針。該宏用在嵌套類的成員函數中,pThis是其父類的指針,這裡也即是CSampleVIEw的this指針。
這些宏與MFC中的消息映射宏非常的相似,在侯捷的《深入淺出MFC》中對消息映射宏有非常詳細的講述。我也無意畫蛇添足。它的基本思想就是把各個嵌套類的對象(即m_x開頭的變量)放到一個數組裡,這樣在QueryInterface時就可以得到這些接口的指針了,所謂的接口指針也就是這些嵌套類對象的地址。
CCmdTarget包含了三個函數:ExternalAddRef、ExternalRelease、ExternalQueryInterface。這樣我們就不用自己實現IUnknown接口了,只要簡單地調用父類的函數就可以了,這實在是很方便。ExternalQueryInterface的執行過程就是先在子類中找要查詢的接口,如果找到了就返回其接口指針。如果找不到就通過GetBaseInterfaceMap到父類中去找,以此類推。跟消息映射的處理方法是一樣的。