TomHornson's thoughts in MiddleWares!

致力于MiddleWare性能、稳定性、安全性的学习和研究!

深入探索MS COM开发框架 之 MFC和ATL/2

 

1.        事实上,组件类获得接口映射表是通过GetInterfaceMap()静态成员函数先

获得interfaceMap结构变量,此变量的第二个值便是接口映射表的入口.

InterfaceMap的第一个值保存了基类的_GetBaseInterfaceMap函数指针.

为在组件类中找不到接口的定义时,通过_GetBaseInterfaceMap

获得基类的interfaceMap,从而获得基类的接口映射表入口….

就这样不断上溯..

         

          通过这些宏,我们实现了接口映射表,并为在类层次中辗转提供了

          方法.这样,组件类就可以通过这张表实现接口的查询和地址定位了.

 

然而这里还有一个大问题

我们说,任何一个接口必须实现AddRefReleaseQueryInterface.

(2:事实上,接口的定义在Idispatch出现后,有了一定的转变,你可将任何

实现了方法和数据的结合看作接口.)…可这里没见什么关于这方面的定义啊..

不错,这里还有一个大阴谋…. 关于这个还得从

#define INTERFACE_PART(theClass, iid, localClass) /

{ &iid, offsetof(theClass, m_x##localClass) }, /

//填充接口影射表

说起….

在实际的工程中,默认的上面的localClass将会被Dispatch取代.

Dispatch又为何物 ? 事实上,CDK1.0,COM开发不用上面这些宏,

在那里你可以清楚的看到问题的实质,没办法,时代变了,就面前的情况来

探讨吧

上面Dispatch的位置,规定置入实现接口的嵌套类的

那么毫无疑问Dispatch就是嵌套类啦.可是你说,这是哪里来的 ?

事实上,CCmdTarget,有这么一片段:

     struct XDispatch

     {

            DWORD m_vtbl;   // place-holder for IDispatch vtable

#ifndef _AFX_NO_NESTED_DERIVATION

            size_t m_nOffset;

#endif

     } m_xDispatch;

它将成为实现接口的嵌套类.

这怎么可能成为嵌套类呢 . .它什么也没有啊简简单单的结构而已啊….

事实在内部,存在一样的虚表vtable指派行为,正是这种行为使得m_xDispatch彻底的

变了,这种行为的引爆器就是组件类构造函数中的EnableAutomation();

代码如下:

void CCmdTarget::EnableAutomation()

{

     ASSERT(GetDispatchMap() != NULL);   // must have DECLARE_DISPATCH_MAP

 

     // construct an COleDispatchImpl instance just to get to the vtable

     COleDispatchImpl dispatch;

 

     // vtable pointer should be already set to same or NULL

     ASSERT(m_xDispatch.m_vtbl == NULL||

            *(DWORD*)&dispatch == m_xDispatch.m_vtbl);

     // sizeof(COleDispatchImpl) should be just a DWORD (vtable pointer)

     ASSERT(sizeof(m_xDispatch) == sizeof(COleDispatchImpl));

 

     // copy the vtable (and other data) to make sure it is initialized

     m_xDispatch.m_vtbl = *(DWORD*)&dispatch;

     *(COleDispatchImpl*)&m_xDispatch = dispatch;

}

 

PART2 深入CCmdTarget看一看COM三大元素的实现

 

   我们之所以深入到CCmdTarget

   不是想只是为了那简简单单的实现

   我们想知道MFCCOM的一大美景:聚合

   是怎么实现的.

   二话不说,摆出架势先 :

public:

     // data used when CCmdTarget is made OLE aware

     long m_dwRef;

     LPUNKNOWN m_pOuterUnknown;  // external controlling unknown if != NULL

     DWORD m_xInnerUnknown;  // place-holder for inner controlling unknown

 

public:

     // advanced operations

     void EnableAggregation();       // call to enable aggregation

     void ExternalDisconnect();      // forcibly disconnect

     LPUNKNOWN GetControllingUnknown();

            // get controlling IUnknown for aggregate creation

 

     // these versions do not delegate to m_pOuterUnknown

     DWORD InternalQueryInterface(const void*, LPVOID* ppvObj);

     DWORD InternalAddRef();

     DWORD InternalRelease();

     // these versions delegate to m_pOuterUnknown

     DWORD ExternalQueryInterface(const void*, LPVOID* ppvObj);

     DWORD ExternalAddRef();

     DWORD ExternalRelease();

 

     // implementation helpers

     LPUNKNOWN GetInterface(const void*);

     LPUNKNOWN QueryAggregates(const void*);

 

     // advanced overrideables for implementation

     virtual BOOL OnCreateAggregates();

     virtual LPUNKNOWN GetInterfaceHook(const void*);

从上面的声明中,你可发现:

   这里声明了两套标准接口方法,

   Externalxx对应于COM模型中的委托IUnknown

  Internalxx对应COM模型中的非委托Iunknown

 有了这两套接口方法,聚合的实现就OK…..

 关于聚合的实现细节,欲知详情,请参阅相关专门

  的书籍,这里不再赘述.

 

通过PART1PART2关于接口的基础构造已经完成.

 

PART3------类厂的由来

 

不用说,COM对象的创建是需要类厂的.

---------------------------------------------------------------------------------------------------------

DECLARE_OLECREATE(CSAM)宏剖析

-----------------------------------------------------------------------------

#define DECLARE_OLECREATE(class_name) /

public: /

     static AFX_DATA COleObjectFactory factory; /

   //定义类厂对象

     static AFX_DATA const GUID guid; /

   //组件类的GUID

 

------------------------------------------------------------------------------------------------------------

                          IMPLEMENT_OLECREATE 宏剖析

                     ------------------------------------------------------------------

#define IMPLEMENT_OLECREATE(class_name, external_name, l, w1, w2, b1,/

 b2, b3, b4, b5, b6, b7, b8) /

     AFX_DATADEF COleObjectFactory class_name::factory(class_name::guid, /

            RUNTIME_CLASS(class_name), FALSE, _T(external_name)); /

//这里要注意的是external_name:ProgID

     AFX_COMDAT const AFX_DATADEF GUID class_name::guid = /

           { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; /

//将组件类的CLSID赋予组件类的成员变量guid.

-----------------------------------------------------------------------------------------------------------

IMPLEMENT_OLECREATE(CSAM, "MFCCOM.SAM",

0x43d242f9, 0x4f7e, 0x4cbb, 0xae, 0xda, 0x77, 0x8d, 0xa1, 0x16, 0xd0, 0xd9)

说明:我们知道,在创建组件类对象时,首先由App核心获得当前状态,

从中取出类厂表,依据CLSID获得相应的类厂对象指针.正是在这里

将类厂和CLSIDProID等信息关联.

 

PART4-------自动化

 

事实上,自动化也是一个极大的主题.自动化技术增强了组件的环境适应性.

对于MFC中的自动化组件,由于默认的接口为dispinterface,所以它对方法的

访问一律采用分发的手段.不同于你意识中,一直牢记的通过vtable[_index]

来访问方法.在使用组件的过程中,首先获得Idispatch接口,然后调用Idispatch

的方法GetIDsOfNames获得programmers希望的方法的令牌(ID),最后通过

Idispatch的方法Invoke来执行.这种技术使得脚本和宏环境可以使用COM

对象,不过对于有预先编译能力的环境来说,会使得组件系统的性能大打

折扣.因为它多了一个反复调用的中间层.

关于Dispatch的支持,基本上构建思路原理同于

上述的Interface..

-----------------------------------------------------------------------------------------------------------------------

DECLARE_DISPATCH_MAP()

                          BEGIN_DISPATCH_MAP(CSAM, CCmdTarget)[.cpp]

       DISP_PROPERTY_NOTIFY(CSAM, "Fook", m_Fook, OnFookChanged, VT_R4)

       DISP_FUNCTION(CSAM, "Post", Post, VT_R4, VTS_NONE)

END_DISPATCH_MAP()

       宏剖析
--------------------------------------------------------------------

#ifdef _AFXDLL

#define DECLARE_DISPATCH_MAP() /

private: /

     static const AFX_DISPMAP_ENTRY _dispatchEntries[]; /

     static UINT _dispatchEntryCount; /

     static DWORD _dwStockPropMask; /

protected: /

     static AFX_DATA const AFX_DISPMAP dispatchMap; /

 

static const AFX_DISPMAP* PASCAL _GetBaseDispatchMap(); /

     virtual const AFX_DISPMAP* GetDispatchMap() const; /

 

#else

#define DECLARE_DISPATCH_MAP() /

private: /

     static const AFX_DISPMAP_ENTRY _dispatchEntries[]; /

     static UINT _dispatchEntryCount; /

     static DWORD _dwStockPropMask; /

protected: /

     static AFX_DATA const AFX_DISPMAP dispatchMap; /

     virtual const AFX_DISPMAP* GetDispatchMap() const; /

#endif

------------------------------------------------------

#ifdef _AFXDLL

#define BEGIN_DISPATCH_MAP(theClass, baseClass) /

     const AFX_DISPMAP* PASCAL theClass::_GetBaseDispatchMap() /

            { return &baseClass::dispatchMap; } /

     const AFX_DISPMAP* theClass::GetDispatchMap() const /

            { return &theClass::dispatchMap; } /

     AFX_COMDAT const AFX_DISPMAP theClass::dispatchMap = /

            { &theClass::_GetBaseDispatchMap, &theClass::_dispatchEntries[0], /

                   &theClass::_dispatchEntryCount, &theClass::_dwStockPropMask }; /

     AFX_COMDAT UINT theClass::_dispatchEntryCount = (UINT)-1; /

     AFX_COMDAT DWORD theClass::_dwStockPropMask = (DWORD)-1; /

     AFX_COMDAT const AFX_DISPMAP_ENTRY theClass::_dispatchEntries[] = /

     { /

#else

#define BEGIN_DISPATCH_MAP(theClass, baseClass) /

     const AFX_DISPMAP* theClass::GetDispatchMap() const /

            { return &theClass::dispatchMap; } /

     AFX_COMDAT const AFX_DISPMAP theClass::dispatchMap = /

            { &baseClass::dispatchMap, &theClass::_dispatchEntries[0], /

                   &theClass::_dispatchEntryCount, &theClass::_dwStockPropMask }; /

     AFX_COMDAT UINT theClass::_dispatchEntryCount = (UINT)-1; /

     AFX_COMDAT DWORD theClass::_dwStockPropMask = (DWORD)-1; /

     AFX_COMDAT const AFX_DISPMAP_ENTRY theClass::_dispatchEntries[] = /

{ /

#endif

#define END_DISPATCH_MAP() /

     { VTS_NONE, DISPID_UNKNOWN, VTS_NONE, VT_VOID, /

            (AFX_PMSG)NULL, (AFX_PMSG)NULL, (size_t)-1, afxDispCustom } }; /

 

思路跟上面的接口映射表的如出一辙.这里不详细叙述.

关于Automation问题,我想过两天,专门写一篇文章谈谈.

因为Automation的确是一门范围很广的技术,不能说有多么的

,不过细节很多,而且普遍的论述概念不清.

在上面几个宏中出现的结构倒需要提一下:

struct AFX_DISPMAP_ENTRY

{

     LPCTSTR lpszName;       // 分发的方法或属性名                       

     long lDispID;           // 接口分发ID (may be DISPID_UNKNOWN)

     LPCSTR lpszParams;      // 传递的参数

     WORD vt;                // 返回值类型或属性类型

     AFX_PMSG pfn;           // normal member On or, OnGet

     AFX_PMSG pfnSet;        // special member for OnSet

     size_t nPropOffset;     // 偏移量,这个地方是核心

     AFX_DISPMAP_FLAGS flags;// flags (e.g. stock/custom)

};

struct AFX_DISPMAP

{

#ifdef _AFXDLL

     const AFX_DISPMAP* (PASCAL* pfnGetBaseMap)();

#else

     const AFX_DISPMAP* pBaseMap;

#endif

     const AFX_DISPMAP_ENTRY* lpEntries;

     UINT* lpEntryCount;

     DWORD* lpStockPropMask;  //是否用备用的名称

};

 

总之,即使在组件类寻到接口时,会按分发的方式来执行方法或更改属性,不会直接执行.

 

 

PART5-------组件得以使用的纽带:几个核心函数

 

  自然,组件的使用不是一呼既来的,从调用接口查询函数开始,

程序内部就开始了漫漫的寻找和创建过程 :

no1.

COleObjectFactory::RegisterAll();

在应用初始化时,调用.目的何在 ? 无非是注册类厂.

(这个地方,本人认为是加载了所有的类厂对象.

因为下面可以直接使用类厂指针链中的类厂指针了)

no2.

AFX_MANAGE_STATE(AfxGetStaticModuleState());

MFC开发的以CwinApp对象为核心的

组件中, AFX_MANAGE_STATE(AfxGetStaticModuleState());

必须在具体操作前调用,因为它掌握着程序所有的信息.

而在ATL,CcomModel对象,内部进行了协调.

一切行为由CcomModel对象来调度.

 

No3.

AfxDllGetClassObject(rclsid, riid, ppv);

你想得到希望的接口指针,获得类厂指针这一步是必须的.

而获得类厂指针是AfxDllGetClassObject(rclsid, riid, ppv);

完成的,它在使用前必须调用

AFX_MANAGE_STATE(AfxGetStaticModuleState());

来监视程序状态,

代码如下:

SCODE AFXAPI AfxDllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)

{

    *ppv = NULL;

    DWORD lData1 = rclsid.Data1;

 

    // search factories defined in the application

    AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

    AfxLockGlobals(CRIT_OBJECTFACTORYLIST);

    for (COleObjectFactory* pFactory = pModuleState->m_factoryList;

           pFactory != NULL; pFactory = pFactory->m_pNextFactory)

    {

           if (pFactory->m_bRegistered != 0 &&

                  lData1 == pFactory->m_clsid.Data1 &&

                  ((DWORD*)&rclsid)[1] == ((DWORD*)&pFactory->m_clsid)[1] &&

                  ((DWORD*)&rclsid)[2] == ((DWORD*)&pFactory->m_clsid)[2] &&

                  ((DWORD*)&rclsid)[3] == ((DWORD*)&pFactory->m_clsid)[3])

           {

                  // found suitable class factory -- query for correct interface

                  SCODE sc = pFactory->InternalQueryInterface(&riid, ppv);

                  AfxUnlockGlobals(CRIT_OBJECTFACTORYLIST);

                  return sc;

           }

    }

    AfxUnlockGlobals(CRIT_OBJECTFACTORYLIST);

#ifdef _AFXDLL

    AfxLockGlobals(CRIT_DYNLINKLIST);

    // search factories defined in extension DLLs

    for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList; pDLL != NULL;

           pDLL = pDLL->m_pNextDLL)

    {

           for (pFactory = pDLL->m_factoryList;

                  pFactory != NULL; pFactory = pFactory->m_pNextFactory)

           {

                  if (pFactory->m_bRegistered != 0 &&

                         lData1 == pFactory->m_clsid.Data1 &&

                         ((DWORD*)&rclsid)[1] == ((DWORD*)&pFactory->m_clsid)[1] &&

                         ((DWORD*)&rclsid)[2] == ((DWORD*)&pFactory->m_clsid)[2] &&

                         ((DWORD*)&rclsid)[3] == ((DWORD*)&pFactory->m_clsid)[3])

                  {

                         // found suitable class factory -- query for correct interface

                         SCODE sc = pFactory->InternalQueryInterface(&riid, ppv);

                         AfxUnlockGlobals(CRIT_DYNLINKLIST);

                         return sc;

                  }

           }

    }

    AfxUnlockGlobals(CRIT_DYNLINKLIST);

#endif

 

    // factory not registered -- return error

    return CLASS_E_CLASSNOTAVAILABLE;

}

 

代码简要说明:

在通过CLSIDIID调用组件导出函数DllGetClassObject,首先

会监测程序环境,然后调用AfxDllGetClassObject,AfxDllGetClassObject

内部的操作是这样的,它会从程序维护的全局信息中(

还有进线程信息、资源句柄等)的注册的类厂表中,根据CLSID搜索

相应的类厂,则从本dll引用的扩展dll中搜索.

 

OK !基本上,MFC对于COM的基础支持,就是通过上面这些宏

来实现的

 

------------------------------------------------------------------------------------

待续                      ATL

------------------------------------------------------------------------------------

                          郑重声明:

                 允许复制、修改、传递或其它行为

                 但不准用于任何商业用途.

                      写于  1/4/2003

                      最后修改: 1/4/2003

                         By TomHornson(@)hotmail.com

------------------------------------------------------------------------------------

 

 

阅读更多
个人分类: 3、Component Tec
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭