




已阅读5页,还剩12页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C+类的动态组件化技术0640303401蒋浩平关键词COM组件接口生命周期C+类ATL组件类C+基类ATL模板基类继承摘要在组件化编程的时代,如何复用历史累积的大量没有组件特性的C+类?本文从工程的角度对这一问题进行探讨,利用现有组件技术,提出了一套将C+类平滑过渡到COM组件的完整解决方案。1. 问题的提出自从Microsoft公布了COM(Component Object Model,组件对象模型,简称COM)技术以后,Windows平台上的开发模式发生了巨大的变化,以COM为基础的一系列组件技术将Windows编程带入了组件化时代,传统的面向对象的软件开发方法已经逐渐被面向组件的方法所取代。COM标准建立在二进制可执行代码级的基础上,不论何种工具、语言开发的组件,只要符合COM规范,就可复用于VC、VB、Delphi、BC等各种开发环境中。COM的语言无关性将软件复用的层次从源代码级推进到了二进制级,复用更方便,也更安全。然而,COM技术带来全新的软件设计和开发模式的同时,也带来了新的问题。许多软件公司在开发自己的软件产品过程中,都累积了大量C+类,这些代码设计精良,功能完备,以面向对象的标准来检验无可挑剔。然而,这些代码不支持COM,将无法在COM时代继续被复用。如果它们在软件组件化的趋势中被淘汰,那对软件公司和开发人员来说都是极大的损失。COM专家Don Box曾说过,“COM is a super C+”。这给了我们一个启示,是否可以实现一种技术,能够动态的为普通C+类加上一层COM的封装呢?这样,既可以保持这些代码自身的完整和特性,使它们能继续应用于原来的系统,也可以在需要作为组件使用的时候,把它们动态转变成组件,复用于新系统。一个自然而然的想法是,为每一个C+类开发一个只暴露一个接口的COM组件,将原C+类的每个public方法都对应于该接口的一个方法,接口方法的实现可以简单的调用相对应的C+类方法即可。这样,程序逻辑由原有的C+类控制,但COM层的封装则由组件提供。基本思路如下图所示:本文就这一技术展开讨论,最终提供一套由普通C+类平滑过渡到COM组件的完整解决方案。我们选用ATL(Active Template Library,活动模板库,简称ATL)作为COM组件的开发工具,开发环境为Visual Studio 6.0。如没有特殊说明,下文中的“C+类”指没有组件特性C+类,“C+对象”指C+类的实例;“ATL组件类”指用于包装的ATL类,“ATL对象”指ATL组件类的实例。2. 用ATL包装C+类按上述思路将C+对象动态组件化后,所得的组件实际上由两部分组成:ATL组件对象和绑定的C+对象。两者的生命周期互相牵制,但要保持一致。生命周期的管理是C+类动态组件化的首要难点。C+类分为两种,一种是简单的C+类,一种是集合型的C+类。集合型的C+对象管理一组C+对象,负责其创建和删除,维护它们的生命周期。下面,分别就简单C+类和集合型C+类的组件化技术进行说明,展示解决方案的核心技术。2.1. 简单C+类的组件化为使ATL组件类可以自由调用C+类的方法,需要:l 为ATL组件类安插一个指针成员变量,指向C+类l 提供ATL对象和C+对象的绑定机制我们可以在ATL组件类初始化时创建一个C+类,用成员变量m_pCPPObj记录,在析构时删除,从而实现ATL组件类和C+类的天然绑定。但出于灵活性考虑,使得ATL组件对象可以绑定任意C+类的对象,我们为ATL组件类添加一个绑定函数Link2CPPObj(CImplement* pObj)。在ATL组件类的构造函数内,创建一个C+对象,用m_pCPPObj记录。如果调用了Link2CPPObj,则将m_pCPPObj指向的对象删除,改用传入的C+对象。在ATL组件类的的析构函数内,删除其绑定的C+对象。由构造函数和Link2CPPObj函数的定义可知,m_pCPPObj指针总是有意义的。简单C+类组件化的思想如下图所示:2.2. 集合型C+类的组件化集合型C+类的情况有所不同。集合型C+类以数组(array)、列表(list)、映射表(map)的形式管理其它C+对象。集合对象和它管理的元素对象都被包装成组件后,集合型ATL对象可能调用一个“Destroy”方法,期望删除某一个元素ATL对象;这一操作的实质却是,集合型C+对象的“Destroy”方法被调用,将元素C+对象删除了,而元素ATL对象却不知道。这一操作的结果导致了元素的ATL对象存在,而其绑定的C+对象却被删除的情况,两者的生命周期出现了不一致。为了解决这个问题,我们需要在C+对象被删除时,能将ATL对象同时删除;而在ATL对象的引用计数为0需要删除自身时,也能把C+对象删除。可行的解决方案是:l 在C+类中保存一个接口指针,指向绑定在一起的ATL对象;为该接口指针赋值的最佳地点显然是提供绑定机制的Link2CPPObj函数内部,为此,还需要给Link2CPPObj添加一个IUnknown*参数l 在C+类的析构函数中,判断该接口指针是否为空,如果不为空,则Release对接口的引用,引发ATL对象自身的析构现在,技术方案如下图所示:2.3. 内部创建的组件和外部创建的组件集合型C+类组件化后仍然是集合型ATL组件,它可以创建、删除自己管理的组件。这样,组件的创建就可能有两种情况:l 由客户直接创建l 由客户调用集合型组件的接口方法间接创建创建方式的不同导致了组件生命周期管理的复杂性。一般说来,组件的创建者负责维护组件的生命周期。上述两种情况下,分别由客户和集合型组件维护被创建组件的生命周期。然而,另有一种情况是,客户创建了一个组件,然后送交一个集合型组件管理,现在维护组件生命周期的责任就由客户转交给了集合型组件。我们的解决方案必须提供这样的健壮性和灵活性,以维护各种情况下组件的生命周期。我们为ATL组件类添加一个BOO成员m_bInnerManage,作为组件的维护标识。内部维护意味着组件的生命周期由其它组件(集合型组件)维护;外部维护则是由客户维护。缺省情况下,组件是外部创建并维护的,在组件的构造函数内设置外部维护标识。集合型组件创建元素时,需要为元素分别创建一个C+对象和一个ATL对象,然后调用ATL对象的Link2CPPObj函数将两者绑定在一起,在Link2CPPObj函数内修改维护标识。对于第三种情况,可以在外部创建组件由客户转交给集合型组件时,在集合型组件相应方法内重新设置维护标识。2.4. C+基类为了对现有C+类的改动最小,我们设计一个基类封装需要为C+类添加的功能。所有需要动态组件化的C+类都必须从这个基类派生,以保证动态组件化中C+对象与ATL对象生命周期的一致。如下图示:实现代码如下所示:class CCPP2ATLObjBaseCCPP2ATLObjBase ();public:/ IUnknown指针,反指向封装该CPP类的接口IUnknown*m_pAssociATLUnk;protected:virtual CCPP2ATLObjBase ();CCPP2ATLObjBase:CCPP2ATLObjBase()/ 将IUnknown指针初始化为0m_pAssociATLUnk = NULL;CCPP2ATLObjBase:CCPP2ATLObjBase()/ CPP类的对象析构时,Release对接口的引用if (m_pAssociATLUnk)m_pAssociATLUnk-Release();然后,修改现有各个C+类,使之从CCPP2ATLObjBase派生,如下面代码片断所示:class CImplement : public CCPP2ATLObjBase ;必须指出的是,在CCPP2ATLObjBase基类中,我们设置的m_pAssociATLUnk变量存在和现有C+类成员命名冲突的问题。但是,考虑到原C+类并没有组件特性,也应该不会有“IUnknown”型指针,因此,只要各个类的变量命名都按照规范的命名法,出现这种名字冲突的可能性是极小的。2.5. ATL模板基类通过以上分析,我们发现,所有的ATL组件类都需要实现一些相同的功能:l 保留一个指向其绑定C+对象的指针l 提供一个Link2CPPObj函数l 在构造函数中创建一个绑定C+类的对象为了减化编码,我们定义一个带参数的模板基类,实现上述公共功能,模板参数就是绑定的C+类。然后,所有的ATL组件类都从模板基类中派生。现在的技术方案如下图所示:实现代码如下所示:template class CCPP2ATLTemplateBase : protected:/ C+类指针T*m_pCPPObj;/ 标识继承该模板的ATL对象是否由内部维护BOOLm_bInnerManage;public:/* 模板的构造函数,实现如下功能: 1、new一个C+实现类对象 2、缺省情况下,ATL对象由外部维护,将内部维护标识设为FALSE 3、将C+类中对ATL接口的反指指针设置为空*/CAtlCPP2ATLTemplateBase()m_pCPPObj = new T;m_bInnerManage = FALSE;m_pCPPObj-m_pAssociATLUnk = NULL;/* 析构ATL对象时,如果该ATL对象是由外部创建的, 则显式的删除C+对象 如果ATL对象由内部维护,那么什么事都不用做*/virtual CAtlCPP2ATLTemplateBase()if (!m_bInnerManage) if (m_pCPPObj)delete m_pCPPObj;/* Link2CPPObj函数,负责绑定C+对象和ATL接口 1、删除构造函数中new的C+对象,而使用外部传入的C+对象 2、将ATL对象的内部维护标识设为TRUE 3、设置C+基类中的接口指针成员 4、因为ATL接口传送给外部使用,需要增加引用计数*/virtual void Link2CPPObj(T* pObj, IUnknown* pUnk)ASSERT(pObj != NULL);ASSERT(pUnk != NULL);if (m_pCPPObj)delete m_pCPPObj;m_pCPPObj = pObj;m_bInnerManage = TRUE;m_pCPPObj-m_pAssociATLUnk = pUnk;m_pCPPObj-m_pAssociATLUnk-AddRef();然后,每个ATL类都从该模板类派生,如下代码片断所示:class ATL_NO_VTABLE CATLXX : ,/ 添加ATL模板基类public CCPP2ATLTemplateBase3. C+参数类型的自动化包装在本文的技术方案中,C+类的public方法与ATL组件接口中的方法一一对应;相应的,C+类中方法的参数类型也要转换为COM规范所允许的数据类型。在基于COM的自动化(Automation)技术中,Microsoft提供了一套自动化兼容的数据类型VARIANT,定义如下:typedef struct FARSTRUCT tagVARIANT VARIANT;typedef struct FARSTRUCT tagVARIANT VARIANTARG;typedef struct tagVARIANT VARTYPEvt;unsigned shortwReserved1;unsigned shortwReserved2;unsigned shortwReserved3;union BytebVal;/ VT_UI1.ShortiVal;/ VT_I2.longlVal;/ VT_I4.floatfltVal;/ VT_R4.doubledblVal;/ VT_R8.VARIANT_BOOLboolVal;/ VT_BOOL.SCODEscode;/ VT_ERROR.CYcyVal;/ VT_CY.DATEdate;/ VT_DATE.BSTRbstrVal;/ VT_BSTR.DECIMALFAR* pdecVal;/ VT_BYREF|VT_DECIMAL.IUnknownFAR* punkVal;/ VT_UNKNOWN.IDispatchFAR* pdispVal;/ VT_DISPATCH.SAFEARRAYFAR* parray;/ VT_ARRAY|*.ByteFAR* pbVal;/ VT_BYREF|VT_UI1.shortFAR* piVal;/ VT_BYREF|VT_I2.longFAR* plVal;/ VT_BYREF|VT_I4.floatFAR* pfltVal;/ VT_BYREF|VT_R4.doubleFAR* pdblVal;/ VT_BYREF|VT_R8.VARIANT_BOOLFAR* pboolVal;/ VT_BYREF|VT_BOOL.SCODEFAR* pscode;/ VT_BYREF|VT_ERROR.CYFAR* pcyVal;/ VT_BYREF|VT_CY.DATEFAR* pdate;/ VT_BYREF|VT_DATE.BSTRFAR* pbstrVal;/ VT_BYREF|VT_BSTR.IUnknownFAR* FAR* ppunkVal;/ VT_BYREF|VT_UNKNOWN.IDispatchFAR* FAR* ppdispVal;/ VT_BYREF|VT_DISPATCH.SAFEARRAYFAR* FAR* pparray/ VT_ARRAY|*.VARIANTFAR* pvarVal;/ VT_BYREF|VT_VARIANT.voidFAR* byref;/ Generic ByRef.charcVal;/ VT_I1.unsigned shortuiVal;/ VT_UI2.unsigned longulVal;/ VT_UI4.intintVal;/ VT_INT.unsigned intuintVal;/ VT_UINT.char FAR *pcVal;/ VT_BYREF|VT_I1.unsigned short FAR *puiVal;/ VT_BYREF|VT_UI2.unsigned long FAR *pulVal;/ VT_BYREF|VT_UI4.int FAR *pintVal;/ VT_BYREF|VT_INT.unsigned int FAR *puintVal;/ VT_BYREF|VT_UINT.;我们看到,所有简单数据类型都可以在VARIANT中找到对应的定义,但是,在多数的基于C+的系统设计中,方法参数不会仅仅出现简单数据类型,类对象、对象引用、对象指针被频繁的作为参数来传递。以类对象、对象引用或对象指针形式存在的参数,我们称为复杂类型参数。在技术方案中,所有复杂类型参数在ATL接口方法中一律对应接口指针,我们需要提供C+对象(或引用、指针)和ATL接口指针之间的动态转换功能。下文就复杂类型作为传入、传出参数分别进行讨论。3.1. 复杂类型的传入参数ATL接口方法获取一个接口指针参数后,如何将此接口指针转变为C+对象指针?对于ATL对象,可以直接取得m_pCPPObj变量,而接口指针却不能。所以,需要提供一种途径,从ATL接口指针获取ATL组件的m_pCPPObj变量值。我们的设计是,为每个ATL组件提供一个基接口ICPPObjSeeker,实现对绑定C+对象指针(即m_pCPPObj)的查询方法HandleCPPObj。任意ATL接口都从该基接口派生,都可以调用HandleCPPObj方法。在前文就生命周期管理进行讨论时,曾提到这样一种情况:客户创建了一个组件,然后送交集合型组件管理。在集合型组件获取外部创建的组件的同时,需要:l 取得后者的C+对象指针。集合型组件对元素组件管理的实质是通过集合型C+对象对元素的C+对象进行管理,而集合型ATL对象和元素ATL对象之间并没有直接联系l 修改新加入元素组件的维护标识因此,我们为ICPPObjSeeker接口添加PostCPPObj方法,用于实现以上功能。ICPPObjSeeker接口idl定义如下所示,因为ICPPObjSeeker接口和HandleCPPObj、PostCPPObj方法实际上都应用于内部,所以使用“hidden”属性对外隐藏:object,uuid(1E9F7F79-936D-4680-9F8E-34A7DCCFF818),dual,hidden,helpstring(ICPPObjSeeker Interface),pointer_default(unique)interface ICPPObjSeeker : IDispatchid(1), helpstring(取得C+对象的指针), hidden HRESULT HandleCPPObj(out, retval long* pCPPObj);id(2), helpstring(取得C+对象的指针,客户程序不再负责对C+对象生命周期的维护), hidden HRESULT PostCPPObj(out, retval long* pCPPObj);ICPPObjSeeker接口的方法可以放在CCPP2ATLTemplateBase模板基类中统一实现:template class CCPP2ATLTemplateBase : /* HandleCPPObj函数,由ICPPObjSeeker接口定义, 负责取得ATL接口中的C+对象指针*/STDMETHODIMP HandleCPPObj(long *pCPPObj)AFX_MANAGE_STATE(AfxGetStaticModuleState()*pCPPObj = (long)m_pCPPObj;return S_OK;/* PostCPPObj函数,由ICPPObjSeeker接口定义, 负责取得ATL接口中的C+对象指针, 同时标记对象为内部维护,客户不再负责对象的生命周期管理*/STDMETHODIMP PostCPPObj(long *pCPPObj)AFX_MANAGE_STATE(AfxGetStaticModuleState()*pCPPObj = (long)m_pCPPObj;if (m_bInnerManage = FALSE) m_bInnerManage = TRUE;m_pCPPObj-m_pAssociATLUnk = this;m_pCPPObj-m_pAssociATLUnk-AddRef();return S_OK;现在,所有的接口都不再直接从IDispatch派生,而改从ICPPObjSeeker派生,因此,IDispatch的实现也应该在实现ICPPObjSeeker接口的同一级或下级中提供。为了包容IDispatch,我们将ATL模板基类稍作改动:template class ATL_NO_VTABLE CCPP2ATLTemplateBase : public IDispatchImpl;在从该模板类派生ATL类时,将ATL Wizard自动生成的对IDispatch接口的实现注释,而使用新定义的CCPP2ATLTemplateBase,如下代码片断所示:class ATL_NO_VTABLE CATLXX : ,/ 将ATL Wizard生成的对IDispatch接口的支持注释/public IDispatchImpl,/ 添加ATL模板基类public CCPP2ATLTemplateBase3.2. 复杂类型的传出参数从C+指针转换为接口指针基本上不存在困难,为方便使用,我们提供一个基于本技术方案的宏定义,如下代码所示:/* 从C+指针获取对应ATL接口的宏 传入:C+指针,对应的ATL类名,接口IID 传出:接口指针,执行状态HRESULT*/#define CPPOBJ_TO_COM_INTERFACE(pCPPObj, CATLClass, IID_IDefine, ppInterface, hResult ) ASSERT(pCPPObj != NULL); if (pCPPObj-m_pAssociATLUnk != NULL) hResult = pCPPObj-m_pAssociATLUnk- QueryInterface(IID_IDefine, (void *)ppInterface); ATLASSERT(SUCCEEDED(hResult); else CComObject* pComObj; hResult = CComObject:CreateInstance(&pComObj); ATLASSERT(SUCCEEDED(hResult);hResult = pComObj- QueryInterface(IID_IDefine, (void *)ppInterface); ATLASSERT(SUCCEEDED(hResult); if (hResult = S_OK) pComObj-Link2CPPObj(pCPPObj, *ppInterface); 4. 接口的继承与多态C+类的继承应用十分广泛,动态化后的组件应该保留原C+类之间的继承关系。在我们的技术方案中,C+类和接口一一对应,C+类的继承关系也应该体现在各个接口上,如下图所示:4.1. 支持继承的系列ATL模板基类实现接口继承的实质是为派生ATL类添加基接口,而为一个ATL类添加接口的实质则是:l 修改IDL文件,体现接口的继承关系l 在ATL类中提供接口实现修改IDL文件很简单,只需要更改派生接口的基接口即可。在ATL类中添加基接口的实现倒颇费思量,我们的做法是:l 扩展ATL模板基类的意义,每一个ATL组件类都对应一个模板基类,都从该模板基类派生l 派生类的模板基类,从基类的模板基类中派生;CCPP2ATLTemplateBase是模板派生树的根节点,所有的模板都派生自CCPP2ATLTemplateBasel 所有的接口方法,都在对应的模板基类中实现ATL派生类继承自它对应的模板基类,这个模板基类又继承自ATL基类对应的模板基类,而在ATL基类的模板基类中提供了基接口的实现。所以,ATL派生类最终继承了基接口的实现。C+类、ATL类、各模板基类的继承关系如下图所示:假定IBaseItf是基接口,IInheritItf是派生接口。ATL基类对应的模板基类定义如下:/* 模板类CAtlBaseItf,提供了IBaseItf的实现, 用于将IBaseItf接口作为基接口共供其它接口继承*/template class ATL_NO_VTABLE CAtlBaseItf : public CCPP2ATLTemplateBasepublic:/ 基接口方法“BaseFunc”,在此模板类内实现STDMETHOD(BaseFunc)()m_pCPPObj-BaseFunc();return S_OK;ATL派生类对应的模板基类定义如下:/* 模板类CAtlInheritItf,继承了基接口IBaseItf方法的实现, 同时提供了IInheritItf的实现,可以将IInheritItf接口作为基接口共供其它接口继承*/template class ATL_NO_VTABLE CAtlInheritItf : public CAtlBaseItfpublic:/ 派生接口方法“InheritFunc”,在此模板类内实现STDMETHOD(InheritFunc)()m_pCPPObj-InheritFunc();return S_OK;更改IInheritItf接口的IDL定义:object,uuid(8F3902DF-DA55-4802-AB8A-958AFF45B2F4),dual,helpstring(IBaseItf Interface),pointer_default(unique)/ 基接口从ICPPObjSeeker派生interface IBaseItf : ICPPObjSeekerid(1), helpstring(IBaseItf Method) HRESULT BaseFunc();object,uuid(AFEBD472-4BEC-45CE-A5A2-E37537C4744A),dual,helpstring(IInheritItf Interface),p
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 人教版三年级上册第六单元6.3《进一步认识分数》课时练(含答案)
- 数词及其在各种题型中的运用解析教案
- 坟墓81号700字7篇范文
- 早产婴儿养育知识培训课件
- 磐安中考数学试卷
- 南通如皋高二数学试卷
- 房地产交易协议注意事项
- 健身中心促销活动策划方案
- 平顶山3模数学试卷
- 2024年山东金谷集团招聘高校毕业生考试真题
- DB50T 1342-2022 预制菜生产加工行为规范
- 设备吊装搬运施工方案范文
- 医务人员职业暴露与防护讲课
- 全过程造价咨询服务的质量承诺及保证措施
- 体适能评定理论与方法课件
- GB/T 44625-2024动态响应同步调相机技术要求
- 三级物联网安装调试员技能鉴定考试题及答案
- DB1507∕T 107-2024 奶牛乳房炎防治技术规范
- 景区旅游基础设施配套项目清单
- 《胃癌腹膜转移诊治中国专家共识(2023版)》解读
- YBT 189-2014 连铸保护渣水分含量(110℃)测定试验方法
评论
0/150
提交评论