实现COM接口的关键是引用计数和QueryInterface函数的实现,MFC的引用计数实现方法很简单,在CCmdTarget类中使用m_dwRef数据成员作为计数器,然后按照COM规范维护计数器的增1减1操作,所有的接口都共享同一个引用计数器,QueryInterface函数的实现取决于COM接口的实现机制——多重继承或是嵌套类。接口映射表记录了CCmdTarget类中每一个嵌套类的接口ID以及几口vtable与父类this指针之间的的偏移量,使嵌套类可以计算出父类的this指针,从而访问父类成员。此偏移量实际上是个常数,用offset宏可以给出成员与父类之间的偏移值,编译器在编译时刻计算此值。
为了透彻了解接口映射表技术,我们还是从C++嵌套类开始探索。
首先我们按嵌套类的方式写出字典对象类CDictionary的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class CDictionary { public: CDictionary(); ~CDictionary();
public: HRESULT _stdcall QueryInterface(REFIID iid, void** ppvobj); ULONG _stdcall AddRef(); ULONG _stdcall Release();
class XDictionaryObj : public Idictionary { public: CDictionary* m_pParent; virtual HRESULT _stdcall QueryInterface(REFIID iid, void** ppvObj); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); virtual BOOL _stdcall Initialize(); virtual BOOL _stdcall LoadLibrary(String); virtual BOOL _stdcall InsertWord(String, String); virtual BOOL _stdcall DeleteWord(String, String); virtual BOOL _stdcall LookupWord(String, *String); virtual BOOL _stdcall RestoreLibrary(String); virtual void _stdcall FreeLibrary(); } m_dictionaryObj;
class XSpellCheckObj : public IspellCheck { public: CDictionary* m_pParent; virtual HRESULT _stdcall QueryInterface(REFIID iid, void** ppvobj); virtual ULONG _stdcall AddRef(); virtual ULONG _stdcall Release(); virtual ULONG _stdcall CheckWord(String word, String*); }m_spellCheckObj;
private: struct Dictword* m_pData; char* m_DictFilename[128]; int m_Ref; int m_nWordNumber, m_nStructNumber; }; |
从CDictionary的定义可以看出,它包含两个嵌套类XDictionaryObj和XspellCheckObj,并且它定义了两个嵌套类数据成员m_dictionaryObj和m_spellCheckObj,两个嵌套类内部都包含一个指向父类的数据成员,下面的代码片段反映了父类和嵌套类中IUknown成员函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | CDictionary::CDictionary() { // ...... Initializtion m_dictionaryObj.m_pParent = this; m_dictionaryObj.m_pParent = this; }
CDictionary::~CDictionary() { // ...... }
HRESULT CDictionary::QueryInterface(const IID& iid, void** ppvobj) { if(iid == IID_IUnknown || iid == IID_Dictionary) { *ppvobj = &m_dictionaryObj; AddRef(); return S_OK; }
else if(iid == IID_SpellCheck) { *ppvobj = &m_spellCheckObj; AddRef(); return S_OK; }
*ppv = NULL; return E_NOINTERFACE; }
ULONG CDictionary::AddRef() { ++m_Ref; return (ULONG) m_Ref; }
ULONG CDictionary::Release() { if(--m_Ref == 0){ delete this; return 0; }
return (ULONG) m_Ref; }
/***********************************************************************************************/
ULONG CDictionary::XDictionaryObj::QueryInterface(const IID& iid, void** ppvObj) { return m_pParent->QueryInterface(iid, ppvObj); }
ULONG CDictionary::XDictionaryObj::AddRef() { return m_pParent->AddRef(); }
ULONG CDictionary::XDictionaryObj::Release() { return m_pParent->Release(); }
// the implemention of member function in CDictionary::XSpellCheckObj // behaves the same way as CDictionary::XDictionaryObj |
注意,在CDictionary类的实现过程中,IUnknown成员函数的实现被放在了父类的CDictionary中,两个嵌套类中的对象成员函数只是简单调用父类的成员函数,这样既可以减少代码的数量,还可以减少出错的可能,保持对象处理引用计数的一致性。这样处理的两一个好处是QueryInterface成员函数处理的一致性,不过从哪个接口,它总是能得到对象所支持的任一个COM接口,而且是唯一的,这完全符合COM规范的要求。
现在我们回头看看CCmdTarget类中给出的接口映射表的宏定义。在CCmdTarget类的定义中,使用了DECLARE_INTERFACE_MAP()宏,其定义如下:
1 2 3 4 5 6 7 | #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; / |
代码很容易看懂。CCmdTarget定义了静态成员_interfaceEntries和静态的interfaceMap成员,以及静态成员函数_GetBaseInterfaceMap()和虚函数GetInterfaceMap。其中AFX_INTERFACEMAP_ENTRY和AFX_INTERFACEMAP的数据结构的定义为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | struct AFX_INTERFACEMAP_ENTRY { const void* piid; // the interface id(IID) (NULL for aggregate) size_t nOffest; // offset of the interface vtable from m_Unknown };
struct AFX_INTERFACEMAP { #ifdef _AFXDLL const AFX_INTERFACE* (PASCAL *pfnGetBaseMap) (); #else const AFX_INTERFACE* pBaseMap; #endif
const AFX_INTERFACE_ENTRY* pEntry; // map for this class }; |
通过以上这些定义,我们可以看出,CCmdTarget利用DECLARE_INTERFACE_MAP()宏定义了一张接口表:
u 表中第一项指向其基类的映射表(CCmdTarget类中,此成员为NULL);
u 第二项指向接口表的入口。
接口表中每一项都包括了IID和接口vtable与父类this指针之间的偏移,这样的结构既允许一个对象实现多个接口,接口数目没有限制,而且用户编写自己的COM对象类时,可以从CCmdTarget派生,甚至形成多层派生结构,被派生的类可以继承其父类实现的接口,通过AFX_INTERFACEMAP结构的第一项获取其基类的接口表,从而形成接口链。
CCmdTarget本身只提供了COM接口支持的结构,并没有实现特定的接口。继续前面的CDictionary例子,我们看看利用接口映射表如何实现:
1 2 3 4 | BEGIN_INTERFACE_MAP(CDictionary, CCmdTarget) INTERFACE_PART(CDictionary, IID_IDictionary, Dictionary) INTERFACE_PART(CDictionary, IID_ISpellCheck, SpellCheck) END_INTERFACE_MAP() |
其中,各个宏的具体定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #define BEGIN_INTERFACE_MAP(theClass, theBase) / /* 以下给出了静态函数_GetBaseInterfaceMap()和GetInterfaceMap()的实现过程*/ const AFX_INTERFACEMAP* PASCAL theClass::_GetBaseInterfaceMap() { / return &theBase::interfaceMap; } / const AFX_INTERFACEMAP* theClass::GetInterfaceMap() const { / return &theClass::interfaceMap; } / /*定义静态成员interfaceMap和_interfaceEntries*/ 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, offset(theClass, m_x##localClass) }, / #define END_INTERFACE_MAP() { / NULL, (size_t) -1} / }; / |
不得不说的一句话是,MFC通过这样一系列设计巧妙的宏使得编码工作得以简化,但无疑也提高了上手的难度。
定义了接口映射表之后,我们再看一下嵌套类的定义和实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class CDictionary : public CCmdTarget { DECLARE_DYNCREAT(CDictionary) CDictionary(); DECLARE_INTERFACE_MAP()
......
//Interface Maps
public: //IDictionary BEGIN_INTERFACE_PART(Dictionary,IDictionary) INIT_INTERFACE_PART(CDictionary, Dictionary) STDMETHOD_(BOOL, Initialize) (); STDMETHOD_(BOOL, LoadLibrary)(LPOLESTR); STDMETHOD_(BOOL, InsertWord)(LPOLESTR); STDMETHOD_(void, DeleteWord)(LPOLESTR); STDMETHOD_(BOOL, LookupWord)(LPOLESTR, LPOLESTR*); STDMETHOD_(BOOL, RestoreLibrary)LPOLESTR); STDMETHOD_(void, FreeLibrary)(); END_INTERFACE_PART_STATIC(Dictionary)
//ISpellCheck
BEGIN_INTERFACE_PART(SpellCheck, ISpellCheck) INIT_INTERFACE_PART(CDictionary, SpellCheck) STDMETHOD_(BOOL, CheckWord)(LPOLESTR, LPOLESTR*); END_INTERFACE_PART_STATIC(SpellCheck) }; |
宏BEGIN_INTERFACE_PART、INIT_INTERFACE_PART和END_INTERFACE_PART封装了嵌套类的定义,嵌套类的类名为Xdictionary,嵌套类的实例成员名为m_xDictionary。
² 宏BEGIN_INTERFACE_PART定义了接口的前三个成员函数:QueryInterface、AddRef、Release,所以我们在上面的代码中看不到这三个函数的定义;
² 宏INIT_INTERFACE_PART定义了记录偏移量的数据成员m_nOffset,并在嵌套类的构造函数中对m_nOffset进行了初始赋值。
下面是这三个宏的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #define BEGIN_INTERFACE_MAP(theClass, theBase) / const AFX_INTERFACEMAP* PASCAL theClass::GetThisInterfaceMap() / { return &theClass::interfaceMap; } / const AFX_INTERFACEMAP* theClass::GetInterfaceMap() const / { return &theClass::interfaceMap; } / AFX_COMDAT const AFX_INTERFACEMAP theClass::interfaceMap = / { &theBase::GetThisInterfaceMap, &theClass::_interfaceEntries[0], }; / AFX_COMDAT const AFX_INTERFACEMAP_ENTRY theClass::_interfaceEntries[] = / { /
#define INIT_INTERFACE_PART(theClass, localClass) / size_t m_nOffset; / INIT_INTERFACE_PART_DERIVE(theClass, localClass) /
#define INIT_INTERFACE_PART_DERIVE(theClass, localClass) / X##localClass() / { m_nOffset = offsetof(theClass, m_x##localClass); } /
#define END_INTERFACE_PART(localClass) / } m_x##localClass; / friend class X##localClass; / |
至此,我们看到了接口映射表有关宏和实现的支持,最后我们总结一下接口映射表实现的步骤:
1、在CCmdTarget类及其派生类定义中使用宏DECLARE_INTERFACE_MAP()声明接口映射表使用的一些静态成员以及两个成员函数。 |
2、在类的实现部分使用BEGIN_INTERFACE_MAP,BEGIN_INTERFACE_PART、INIT_INTERFACE_PART、END_INTERFACE_PART,和END_INTERFACE_MAP宏定义接口映射表。 |
3、为每一个接口定义嵌套类成员。 |
4、实现嵌套类。 |