MFC对COM应用的支持(1)

来源:互联网 发布:navicat导出sql语句 编辑:程序博客网 时间:2024/06/04 18:42
MFC对COM的支持从CCmdTarget类开始,CCmdTarget使用了一种与消息映射表非常类似的机制来COM接口,我们把这种机制成为接口映射表。接口映射表的基本思路是C++嵌套类,只是MFC将这些细节隐藏起来了。

实现COM接口的关键是引用计数和QueryInterface函数的实现,MFC的引用计数实现方法很简单,在CCmdTarget类中使用m_dwRef数据成员作为计数器,然后按照COM规范维护计数器的增11操作,所有的接口都共享同一个引用计数器,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的定义可以看出,它包含两个嵌套类XDictionaryObjXspellCheckObj,并且它定义了两个嵌套类数据成员m_dictionaryObjm_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_ENTRYAFX_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_PARTINIT_INTERFACE_PARTEND_INTERFACE_PART封装了嵌套类的定义,嵌套类的类名为Xdictionary,嵌套类的实例成员名为m_xDictionary

²  BEGIN_INTERFACE_PART定义了接口的前三个成员函数:QueryInterfaceAddRefRelease,所以我们在上面的代码中看不到这三个函数的定义;

²  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_MAPBEGIN_INTERFACE_PARTINIT_INTERFACE_PARTEND_INTERFACE_PART,和END_INTERFACE_MAP宏定义接口映射表。

3、为每一个接口定义嵌套类成员。

4、实现嵌套类。

 

 

 

 

 

 

 

原创粉丝点击