现在的位置: 首页 > 综合 > 正文

自总结的COM应用

2013年08月12日 ⁄ 综合 ⁄ 共 3531字 ⁄ 字号 评论关闭

组件对象模型COM,是一种以组件为发布单元的对象模型,这种模型使得各软件组件可以用一种统一的方式进行交互,它既提供了组件对象之间进行交互的规范,也提供了实现交互的环境,因为组件对象之间交互的规范不依赖于任何特定的语言,所以它也可以成为不同语言协作开发的一种标准。本管理系统平台程序就是采用组件开发的方法编制的,每一个功能模块都被编译成一个独立的dlllib,可以方便实现封装和调用。

每个COM组件都有一个CLSIDProgID唯一的表示一个组件服务程序,根据这些ID,就可以加载运行组件,并为客户端程序提供服务。而要调用这些组件,则涉及到接口的概念,在COM中,函数是通过VTAB虚函数表来提供其地址的,从另一个角度来看,不管用什么语言开发,编译器产生的代码都能生成这个表。这样就实现了组件的二进制特性轻松实现了组件的跨语言要求。假设有一个指针型变量保存着 VTAB 的首地址,则这个变量就叫接口指针,变量命名的时候,习惯上加上"I"开头,另外为了区分不同的接口,每个接口也都要有一个名字,该名字就和CLSID一样,使用GUID方式,叫IID。组件中必须有3个函数:QueryInterfaceAddRefRelease,这3个函数也组成一个接口,叫"IUnknown"。而用来描述组件接口的文件为IDL文件,它是一种语法很像C的文本文件,IDL经过编译,会生成二进制的等价类型库文件TLB提供给其他语言使用。在IDL中定义的接口函数,参数的方向都需要标明,传入参数[in]、传出[out],这样,如果参数是动态分配的内存指针,那么传入参数就遵守谁调用谁释放的原则,而传出参数为组件申请调用者释放。这种接口的方式在普通组件调用时可行,但必须要加载这个组件的类型库文件,对于脚本程序则无法采用加载的方式进行预编译,那么就要采用双接口,其概念就是在一个VTAB的虚函数表中容纳了三个接口。而对于组件接口的调用又有如下几种方法:#include方法,IDL编译后,为方便C/C++程序员的使用,会产生xxx.hxxx_i.c文件,直接#include后就可以使用了;#Import方法,比较通用的方法,VC会帮我们产生包装类,让我们的调用更方便;加载类型库包装类方法:如果组件提供了 IDispatch 接口,用这个方法调用组件是最简单的,就是上面所提到的自动化接口的“后绑定”。对于操作原始的接口指针是比较麻烦的,需要我们自己控制引用记数、API 调用、异常处理,然而ATL提供2个智能指针的模板包装类,CComPtr<>CComQIPtr<>,这两个类都在<atlbase.h>中声明。CComQIPtr<>包含了CComPtr<>的所有功能,因此我们可以完全用CComQIPtr<>来使用智能接口指针,唯一要说明的一点就是:CComQIPtr<>由于使用了运算符的重载功能,它会自动帮我们调用QueryInterface()函数,因此CComQIPtr<>唯一的缺点就是不能定义 IUnknown *指针。

COM的学习过程中,首先接触的是COM中常见的数据类型,在组件API及接口指针中,除了IUnknown::AddRef()IUnknown::Release()两个函数外,其它所有的函数都是以HRESULT作为返回值的,也就是说,函数在执行后,不仅要返回结果,还要由HRESULT返回函数是否被正常执行了,常见的返回值可见附录1COM中的字符编码方式为UNICODE,它使用2个字节表示一个字符,这样,就可以将各种文字符号进行统一编码,例如WCHARLPCOLESTR等就是你使用UNICODE字符集,由于ASCII码多使用在平时的代码编译,所以在组件开发中,对字符的定义最好在前面加_T,表示使用UNICODE或者MBCS字符集。另外,COM中还有一种特殊的字符串类型BSTR,它实际上是一个指向UNICODE字符串的指针类型,且它的前4个字节使用DWORD保存所定义字符串的字节长度(不含字符串结束符),其定义方法为SysAllocStringATL对其的封装类为CComBSTR,在atlbase.h中定义,ATL的具体说明将在后面给出。另外一种数据类型VARIANT是具有跨语言特性的,同时,它可以表示任意类型的数据。因为从C语言的角度来讲,VARIANT实际上是一个结构,结构中用一个域(vt)表示该变量表示的数据类型,真正的数据则存储在union空间中。ATL对其的封装类为CComVariantATLSTL一样,是由一些小巧、高效和灵活的类组成的集合,它给我们提供了一下四个主要的功能:1、为维护代价很高的数据类型(接口指针、VARIANTBSTRHWND)提供了包装类(对应的类:(CComPtrCComQIPtrCComDispatchDriver智能指针类)CComVariantCComBSTR(CWindowCwindowImplCDialogImplCContainedWindow、和CAxWindow))2、提供了一些类实现了诸如IUnknownIClassFactoryIDispathIPersistXxxIConnectionPointContainerIEnumXxx这些基本的COM接口;3、管理COM服务的类(CComModule),他们用于暴露类对象、自注册和服务器的生命周期管理;4、节省手工录入的向导(wizard)。对于上面提到的数据类型,ATL中提供了丰富的转换宏简化各种数据类型间的转换,宏函数中的简写的含义为:AANSI字符串,也就是MBCSWOLE:宽字符串,也就是UNICODET:中间类型,如果定义了_UNICODE,则T表示W;如果定义了_MBCS,则T表示ACconst的缩写。另外,组件编程中还会涉及到组件程序的注册,一般的使用ATL编写的组件在编译后,IDE就会自动注册,而用MFC编写的就不能自动注册,可以执行菜单“Tools/Register Control”来注册,或命令行运行“regsvr32.exe 文件名来注册。

然后我就开始接触COM组件的实际应用方面,先后接触了简单接口的添加,IDispatch和双接口的相关内容,都是对VC的实际操作,不多做介绍,重在实践。COM中的接口间的通信不再向MFC中通过消息发送等方法实现,而是采用自有的两种方式,一种是回调函数的方式,它是设计COM 通知方法的基础。回调函数本质上是预先把某一函数的指针告诉用户,当有必要的时候,就直接呼叫该函数了,而这个回调函数做了什么,怎么做的,用户是不用关心的。从严格意义上讲,COM中不使用回调函数而是使用回调接口(就是使用一大堆包装好的回调函数集),回调接口,我们也叫接收器接口”(Sink);另一种叫连接点方式,一个 COM 组件允许有多个连接点对象(IConnectionPoint),也就是说可以有多个发生事件的源头,管理这些连接点的接口叫连接点容器”(IConnectionPointContainer),其接口特别简单,因为只有2个函数,一个是 FindConnectionPoint(),表示查找你想要的连接点,另一个是EnumConnectionPoints(),表示列出所有的连接点,然后你去选择使用哪个,在实际的应用中,查找法使用最多,占90%,而枚举法使用只占10%,而且每一个连接点,可以被多个客户端的接收器(Sink)连接,这样的设计在远程DCOM环境下运行效率较低,但由于ATL对其进行了封装,所以很易于实现。COM的错误异常处理有一个专门的接口:ISupportErrorInfo,常见的错误返回值见附录2,另外,调用者得到返回的HRESULT值后,也可以使用宏 HRESULT_SEVERITY()HRESULT_FACILITY()HRESULT_CODE()来取得sev错误程度、fac设备信息和code错误代码。

这是我第二次对COM编程的学习,这次我结合源程序和网上经典的杨老师COM组件设计与应用教程,自己动手编些小程序,学会了COM组件和ActiveX控件的编写、Atl编程的应用、类工厂和IDispatch接口等,还了解了一下COM中常见数据数据类型(BSTRLPSTR),结合实例,学习效果不错,完成程度80%

附录1

常见的HRESULT返回值类型

HRESULT

含义

S_OK

0x00000000

成功

S_FALSE

0x00000001

函数成功执行完成,但返回时出现错误

-->

作者:

抱歉!评论已关闭.