第1章COM基本概念§1.1什么是COM所谓COM(ComponetObjectModel,组件对象模型),是一种说明如何建立可动态互变组件的规范,此规范提供了为保证能够互操作,客户和组件应遵循的一些二进制和网络标准。通过这种标准将可以在任意两个组件之间进行通信而不用考虑其所处的操作环境是否相同、使用的开发者语言是否一致以及是否运行于同一台计算机。COM规范所描述的即是如何编写组件,遵循COM标准的任何一个组件都是可以被用来组合成应用程序的。至于对组件采取的是何种编程语言则是无关紧要的,可以自由选取。作为一个真正意义上的组件,应具备如下特征:1)实现对开发语言的封装。2)以二进制形式发布。3)能够在不妨碍已有用户的情况下被升级。4)在网络上的位置必须能够被透明的重新分配。在Windows操作系统平台上,有一些用COM形式提供的组件模块极大地丰富了Windows的功能,而且也使Windows功能扩展更加灵活,例如:1)DiretX多媒体软件包。它以COM接口的形式为Windows平台提供了强大的多媒体功能,现广泛用于游戏娱乐软件及其他多媒体软件的开发。2)RDO(remotedataobjet,远程数据对象)和DAO(dataaccessobject,数据访问对象)数据库访问对象库。它以COM自动化对象的形式为数据库应用提供了便捷的操作方法,特别适合于在BASIC语言或其他一些高级语言中使用。而数据访问一致接口OLEDB/ADO(activedataobject,活动数据对象)更淋漓尽致地发挥了COM接口的作用。3)InternetClientSDK。它提供了一组COM库,为应用系统增加Internet特性提供了底层透明的一致操作。其它还有一些组件如MAPI(messagingAPI,消息应用编程接口)、ADSI(activedirectoryserviceinterface,活动目录服务接口)等,它们都提供了一致、高效的服务。从整个Windows操作系统来看,COM成了系统的基本软件模型,它带来的是灵活性和高效率,以及应用开发的一致性。§1.2COM对象与C++对象比较COM对象建立在二进制一级的基础上,而C++对象建立在源代码一级的基础上,但从特性上,可以作一比较。1)封装性:COM对象的数据成员的封装以组件模型为最终边界,对于用户是完全透明的、不可见的;而C++对象的封装特性只是语义上的封装,对于对象用户是可见的。2)可重用性COM对象的可重用性表现在COM对象的包容和聚合,一个对象可以完全使用另一个另一个对象的所有功能;而C++对象的可重用表现在C++类的继承性,派生类可以调用其父类的非私有成员函数。3)多态性C++对象的多态性体现了C++语言用类描述事物的高度抽象的特征;COM对象也具有多态性,但这种多态性需要通过COM对象所具有的接口才能体现出来,就像C++对象的多态性需要通过其虚函数才能体现一样。§1.3COM对象和接口COM提供的是面向对象的组件模型,COM组件提供给客户的是以对象形式封装起来的实体。客户程序和COM组件程序进行交互的实体是COM对象。COM对象包括属性(也称为状态)和方法(也称为操作),对象的状态反映了对象的存在,也是区别于其他对象的要素;而对象所提供的方法就是对象提供给外界的接口,客户必须通过接口才能获得对象的服务。对于COM对象来说,接口是它与外界进行交互的唯一途径,因此,封装特性是COM对象的基本特征。§1.3.1COM对象标识——CLSIDCOM组件的位置对于客户来说是透明的,因为客户并不直接去访问COM组件,客户程序通过一个全局标识符进行对象的创建和初始化工作,这个全局标识符就是CLSID。CLSID结构定义上与GUID一致。COM规范采用了128位全局唯一标示符GUID。这是一个随机数。手工创建128位GUID或者编写程序来产生GUID是件很麻烦的事。为此,MicrosoftVisualC++提供了两个工具实现这样的目的:UUIDGen.exe和GUIDGen.exe,前者是一个命令行程序,后者是一个基于对话框的应用程序。另外,COM库为我们提供了以下API函数可以产生GUID:下面为示例工程中.rgs文件中CLSID的定义:将示例工程的COM组件成功注册后,我们可以根据组件的CLSID在系统的注册表编辑器中找到组件的注册信息,如图1。HRESULTCoCreateGuid(GUID*pguid)Math.Obj.1=s'MyMathClass'{CLSID=s'{3B28F0D6-D029-484B-80D7-A946EB20E9BD}'}图1系统注册表§1.3.2COM对象的数据类型HRESULT:一个双字节的值,其最高位(bit)如果是0表示成功,1表示错误。常见的HRESULT值:HRESULT值含义S_OK0x00000000成功S_FALSE0x00000001函数成功执行完成,但返回时出现错误E_INVALIDARG0x80070057参数有错误E_OUTOFMEMORY0x8007000E内存申请错误E_UNEXPECTED0x8000FFFF未知的异常E_NOTIMPL0x80004001未实现功能E_FAIL0x80004005没有详细说明的错误E_POINTER0x80004003无效的指针E_HANDLE0x80070006无效的句柄E_ABORT0x80004004终止操作E_ACCESSDENIED0x80070005访问被拒绝E_NOINTERFACE0x80004002不支持接口UNICODE:IDL字符串的标准形式,使用2个字节表示一个字符(unsignedshortint、WCHAR、_wchar_t、OLECHAR),不会出现乱码,UNICODE的范围是0x0000-0xFFFF共6万多个字符。BSTR:一个OLECHAR*类型的Unicode字符串。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,自带字符串长度信息。API函数函数作用SysAllocString()申请一个BSTR指针,并初始化为一个字符串SysFreeString()释放BSTR内存SysAllocStringLen()申请一个指定字符长度的BSTR指针,并初始化为一个字符串SysAllocStringByteLen()申请一个指定字节长度的BSTR指针,并初始化为一个字符串SysReAllocStringLen()重新申请BSTR指针CString函数函数作用AllocSysString()从CString得到BSTRSetSysString()重新申请BSTR指针,并复制到CString中CComBSTR函数ATL的BSTR包装类,在atlbase.h中定义,具体查看MSDN_bstr_tC++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTRAPI函数。VARIANT:具有跨语言的特性,它可以表示(存储)任意类型的数据,既包括了数据本身,也包含了数据的类型,定义在oaidl.h中。VARIANT使用示例:structtagVARIANT{VARTYPEvt;Union{shortiVal;//VT_I2longlVal;//VT_I4floatfltVal;//VT_R4doubledblVal;//VT_R8DATEdate;//VT_DATEBSTRbstrVal;//VT_BSTR…..short*piVal;//VT_BYREF|VT_I2long*plVal;//VT_BYREF|VT_I4float*pfltVal;//VT_BYREF|VT_R4double*pdbVal;//VT_BYREF|VT_R8DATE*pdate;//VT_BYREF|VT_DATEBSTR*pbstrVal;//VT_BYREF|VT_BSTR};};typedeftagVARIANTVARIANT;Windows定义的VARIANT相关函数:COleVariant:对VARIANT结构的封装_variant_t:用于COM的VARIANT类§1.4描述性语言IDL和MIDL编译器COM规范在采用OSF的DCE规范描述远程调用接口IDL(interfacedescriptionlanguage,接口描述语言)的基础上,进行扩展形成了COM接口的描述语言。接口描述语言提供了一种不依赖于任何语言的接口描述方法,因此,它可以成为组件程序和客户端程序之间的共同语言。COM规范使用的IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,我们可以指定每个参数的类型、输入输出特性,甚至支持可变长度的数组的描述。IDL中所有数据、方法、接口、类和库的特性都由属性信息来描述。属性信息中由括号括起来,作为它们描述的对象的前缀。1)in:输入型参数,从调用者传递到被调用者,被调用者对输入型参数的更改不传回调用者。2)out:输出型参数,从被调用者返回调用者,而被调用者不关心参数的初始值。3)In,out:输入输出型参数在调用的时候传到被调用者,同时,被调用者可以对参数进行修改,这个修改在调用返回的时候会被复制回调用者。(PS:非指针类型一定是输入型参数。输出型参数和输入输出型参数一定是指针类型)4)retval:返回一个与方法的物理HRESULT不相关的逻辑结果,与out一起使用,且只能有一个,放在参数的最后。COleVariantv1(“Thisisatest”);//直接构造COleVariantv2=“Thisisatest”;//结果时VT_BSTR类型,值为”Thisisatest”COleVariantv3((long)2012);COleVariantv4=(long)2012;//结果时VT_I4类型,值为2012VariantInt——将变量初始化为VT_EMPTY;VariantClear——消除并初始化VARIANT;VariantChangeType——改变VARIANT的类型;VariantCopy——释放与目标VARIANT相连的内存并赋值源VARIANTVARIANTva;::VariantInit(&va);//初始化Inta=2012;Va.vt=VT_I4;//指明long数据类型Va.IVal=a;//赋值::VariantClear();5)string:参数所指向的是一个字符串类型参数,以Null终止。6)size_is:指针数组中元素个数由另一个参数说明。7)length_is:用来设置在序列化时需要复制的元素数量。下面IDL示例为工程Math的IDL文件。IDL中接口定义示例:IDL中enum定义示例:IDL中struct定义示例:typedef[uuid(9CE9F449-3894-44AD-9F15-1DE67E915329),version(1.0),helpstring(Enumoffunction)]enumfunction{fAdd=0,fSub,fMul}function;[object,uuid(CED2CE33-5419-49E0-AE72-CB1E4D2B0C8F),oleautomation,nonextensible,pointer_default(unique)]interfaceIMyMath:IUnknown{[helpstring(方法Add)]HRESULTAdd([in]Element*pElement,[out]DWORD*pValue);[helpstring(方法Operate)]HRESULTOperate([in]Element*pElement,functionfun,[out]DWORD*pValue);[helpstring(方法Sum)]HR