COM组件技术积累.doc_第1页
COM组件技术积累.doc_第2页
COM组件技术积累.doc_第3页
COM组件技术积累.doc_第4页
COM组件技术积累.doc_第5页
已阅读5页,还剩38页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第1章 COM基本概念1.1 什么是COM所谓COM(Componet Object Model,组件对象模型),是一种说明如何建立可动态互变组件的规范,此规范提供了为保证能够互操作,客户和组件应遵循的一些二进制和网络标准。通过这种标准将可以在任意两个组件之间进行通信而不用考虑其所处的操作环境是否相同、使用的开发者语言是否一致以及是否运行于同一台计算机。COM规范所描述的即是如何编写组件,遵循COM标准的任何一个组件都是可以被用来组合成应用程序的。至于对组件采取的是何种编程语言则是无关紧要的,可以自由选取。作为一个真正意义上的组件,应具备如下特征:1) 实现对开发语言的封装。2) 以二进制形式发布。3) 能够在不妨碍已有用户的情况下被升级。4) 在网络上的位置必须能够被透明的重新分配。在Windows操作系统平台上,有一些用COM形式提供的组件模块极大地丰富了Windows的功能,而且也使Windows功能扩展更加灵活,例如:1) DiretX多媒体软件包。它以COM接口的形式为Windows平台提供了强大的多媒体功能,现广泛用于游戏娱乐软件及其他多媒体软件的开发。2) RDO(remote data objet,远程数据对象)和DAO(data access object,数据访问对象)数据库访问对象库。它以COM自动化对象的形式为数据库应用提供了便捷的操作方法,特别适合于在BASIC语言或其他一些高级语言中使用。而数据访问一致接口OLE DB/ADO(active data object,活动数据对象)更淋漓尽致地发挥了COM接口的作用。3) Internet Client SDK。它提供了一组COM库,为应用系统增加Internet特性提供了底层透明的一致操作。其它还有一些组件如MAPI(messaging API,消息应用编程接口)、ADSI(active directory service interface,活动目录服务接口)等,它们都提供了一致、高效的服务。从整个Windows操作系统来看,COM成了系统的基本软件模型,它带来的是灵活性和高效率,以及应用开发的一致性。1.2 COM对象与C+对象比较 COM对象建立在二进制一级的基础上,而C+对象建立在源代码一级的基础上,但从特性上,可以作一比较。1) 封装性:COM对象的数据成员的封装以组件模型为最终边界,对于用户是完全透明的、不可见的;而C+对象的封装特性只是语义上的封装,对于对象用户是可见的。2) 可重用性COM对象的可重用性表现在COM对象的包容和聚合,一个对象可以完全使用另一个另一个对象的所有功能;而C+对象的可重用表现在C+类的继承性,派生类可以调用其父类的非私有成员函数。3) 多态性C+对象的多态性体现了C+语言用类描述事物的高度抽象的特征;COM对象也具有多态性,但这种多态性需要通过COM对象所具有的接口才能体现出来,就像C+对象的多态性需要通过其虚函数才能体现一样。1.3 COM对象和接口COM提供的是面向对象的组件模型,COM组件提供给客户的是以对象形式封装起来的实体。客户程序和COM组件程序进行交互的实体是COM对象。COM对象包括属性(也称为状态)和方法(也称为操作),对象的状态反映了对象的存在,也是区别于其他对象的要素;而对象所提供的方法就是对象提供给外界的接口,客户必须通过接口才能获得对象的服务。对于COM对象来说,接口是它与外界进行交互的唯一途径,因此,封装特性是COM对象的基本特征。1.3.1 COM对象标识CLSIDCOM组件的位置对于客户来说是透明的,因为客户并不直接去访问COM组件,客户程序通过一个全局标识符进行对象的创建和初始化工作,这个全局标识符就是CLSID。CLSID结构定义上与GUID一致。COM规范采用了128位全局唯一标示符GUID。这是一个随机数。手工创建128位GUID或者编写程序来产生GUID是件很麻烦的事。为此,Microsoft Visual C+提供了两个工具实现这样的目的:UUIDGen.exe和GUIDGen.exe,前者是一个命令行程序,后者是一个基于对话框的应用程序。另外,COM库为我们提供了以下API函数可以产生GUID:HRESULT CoCreateGuid(GUID *pguid)下面为示例工程中.rgs文件中CLSID的定义:Math.Obj.1 = s MyMath ClassCLSID = s 3B28F0D6-D029-484B-80D7-A946EB20E9BD将示例工程的COM组件成功注册后,我们可以根据组件的CLSID在系统的注册表编辑器中找到组件的注册信息,如图1。图1 系统注册表1.3.2 COM对象的数据类型n 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不支持接口n UNICODE:IDL字符串的标准形式,使用2个字节表示一个字符(unsigned short int、WCHAR、_wchar_t、OLECHAR),不会出现乱码,UNICODE的范围是0x0000-0xFFFF共6万多个字符。n 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函数,其他操作是借用BSTR API函数。n VARIANT:具有跨语言的特性,它可以表示(存储)任意类型的数据,既包括了数据本身,也包含了数据的类型,定义在oaidl.h中。 struct tagVARIANT VARTYPE vt; Union short iVal; /VT_I2 long lVal; /VT_I4 float fltVal; /VT_R4 double dblVal; /VT_R8 DATE date; /VT_DATE BSTR bstrVal; /VT_BSTR . short* piVal; /VT_BYREF|VT_I2 long* plVal; /VT_BYREF|VT_I4 float* pfltVal; /VT_BYREF|VT_R4 double* pdbVal; /VT_BYREF|VT_R8 DATE* pdate; /VT_BYREF|VT_DATE BSTR* pbstrVal; /VT_BYREF|VT_BSTR;typedef tagVARIANT VARIANT;VARIANT使用示例: VARIANT va; :VariantInit(&va); /初始化 Int a = 2012; Va.vt = VT_I4; /指明long数据类型 Va.IVal = a; /赋值 :VariantClear();Windows定义的VARIANT相关函数: VariantInt 将变量初始化为VT_EMPTY; VariantClear 消除并初始化VARIANT; VariantChangeType 改变VARIANT的类型; VariantCopy 释放与目标VARIANT相连的内存并赋值源VARIANT COleVariant:对VARIANT结构的封装 COleVariant v1(“This is a test ”); /直接构造 COleVariant v2 = “This is a test”; /结果时VT_BSTR类型,值为”This is a test” COleVariant v3(long)2012); COleVariant v4 = (long)2012; /结果时VT_I4类型,值为2012_variant_t: 用于COM的VARIANT类1.4 描述性语言IDL和MIDL编译器COM规范在采用OSF的DCE规范描述远程调用接口IDL(interface description language,接口描述语言)的基础上,进行扩展形成了COM接口的描述语言。接口描述语言提供了一种不依赖于任何语言的接口描述方法,因此,它可以成为组件程序和客户端程序之间的共同语言。 COM规范使用的IDL接口描述语言不仅可用于定义COM接口,同时还定义了一些常用的数据类型,也可以描述自定义的数据结构,对于接口成员函数,我们可以指定每个参数的类型、输入输出特性,甚至支持可变长度的数组的描述。IDL中所有数据、方法、接口、类和库的特性都由属性信息来描述。属性信息中由括号括起来,作为它们描述的对象的前缀。1) in: 输入型参数,从调用者传递到被调用者,被调用者对输入型参数的更改不传回调用者。2) out: 输出型参数,从被调用者返回调用者,而被调用者不关心参数的初始值。3) In,out: 输入输出型参数在调用的时候传到被调用者,同时,被调用者可以对参数进行修改,这个修改在调用返回的时候会被复制回调用者。(PS:非指针类型一定是输入型参数。输出型参数和输入输出型参数一定是指针类型)4) retval: 返回一个与方法的物理HRESULT不相关的逻辑结果,与out一起使用,且只能有一个,放在参数的最后。5) string: 参数所指向的是一个字符串类型参数,以Null终止。6) size_is: 指针数组中元素个数由另一个参数说明。7) length_is: 用来设置在序列化时需要复制的元素数量。 下面IDL示例为工程Math的IDL文件。IDL中接口定义示例:object,uuid(CED2CE33-5419-49E0-AE72-CB1E4D2B0C8F),oleautomation,nonextensible,pointer_default(unique)interface IMyMath : IUnknown helpstring(方法 Add) HRESULT Add(in Element* pElement, out DWORD* pValue); helpstring(方法 Operate) HRESULT Operate(in Element* pElement, function fun, out DWORD* pValue); helpstring(方法 Sum) HRESULT Sum(in DWORD ColCount, in DWORD RowCount, in, size_is(ColCount*RowCount)DWORD* pNums, out, retval DWORD* pResult);IDL中enum定义示例:typedef uuid(9CE9F449-3894-44AD-9F15-1DE67E915329),version(1.0),helpstring(Enum of function)enum function fAdd = 0, fSub, fMul function;IDL中struct定义示例:typedef uuid(9CE9F449-3894-44AD-9F15-1DE67E915329),version(1.0),helpstring(Enum of function)enum function fAdd = 0, fSub, fMul function;IDL中union定义示例:typedef uuid(994A75FF-6FC8-4802-AA42-4E04776BD521), version(1.0), helpstring(“NUMBER) union NUMBER case(1) long i; case(2) float f; NUMBER ; IDL类定义示例:uuid(12881436-9C8F-457E-851F-25CCD3F25D30),version(1.0),library MathLibimportlib(stdole2.tlb);uuid(3B28F0D6-D029-484B-80D7-A946EB20E9BD)coclass MyMathdefault interface IMyMath;interface IMyMath2;Microsoft Visual C+ 提供了MIDL工具,可以把IDL接口描述文件编译成C/C+兼容的接口描述文件(.h)和C文件(.c),可以被组件程序和客户程序所使用。XX_i.h:一个同C和C+兼容的,包含IDL中所描述的所有接口声明的头文件;XX_i.c:一个定义有IDL文件中所用的所有GUID的C文件1.5 IUnknown接口COM定义的每一个接口都必须从IUnknow继承过来,其原因在于IUnknow接口提供了非常重要的特性:生存期控制和接口查询。客户程序只能通过接口和COM对象进行通信,虽然客户程序可以不管对象内部的实现细节,但它要控制对象的存在与否。1.5.1 QueryInterface接口方法介绍按照COM规范,一个COM对象可以实现多个接口,客户端程序可以在运行时刻对COM对象的接口进行询问,如果对象实现了该接口,则对象可以提供这样的接口服务。QueryInterface函数的说明:HRESULT QueryInterface( in REFIID iid, ou void* ppV);函数的输入参数iid为接口标识符IID,输出参数ppv为查询得到的结果接口指针,如果没有实现iid所标识的接口,则输出参数ppv指向空(NULL)。函数的返回值为一个32位的整数,反映了查询的结果,其含义有三种情况:1) S_OK,查到了指定的接口,接口指针存放在ppv输出参数中;2) E_NOINTERFACE,对象不支持所指定的接口,*ppv为NULL;3) E_UNEXPECTED,发生了意外错误,*ppv为NULL。对于调用QueryInterface函数,COM规范给出了以下一些规则:1) 对于同一个对象的不同接口指针,查询得到的IUnknown接口必须完全相同。也就是说每个COM对象的IUnknown接口指针是唯一的。2) 接口自反性。对于一个接口查询其自身总应该成功3) 接口对称性。如果从一个接口指针查询到另一个接口指针,则从第二个接口指针再回到第一个接口指针必定成功,如:IMyMath* pIMyMath = NULL;HRESULT hr = :CoCreateInstance( CLSID_MyMath, NULL, CLSCTX_INPROC_SERVER, IID_IMyMath, (void*)&pIMyMath );IMyMath2* pIMyMath2 = NULL;hr = pIMyMath-QueryInterface(IID_IMyMath2,(void*)&pIMyMath2);4) 接口传递性。如果从第一个接口指针查询到第二个接口指针,从第二个接口指针可以查询到第三个接口指针,则从第三个接口指针一定可以查询到第一个接口指针。5) 接口查询时间无关性。如果某一个时刻可以查询到某一个接口指针,则以后任何时候再查询到同样的接口指针,一定可以查询成功。1.5.2 引用计数 IUnknown引入了“引用计数”(reference counting)方法,可以有效地控制对象的生存周期,解决内存管理的问题。COM对象通过引用计数来决定是否继续生存下去。IUnknown的接口成员函数AddRef和Release分别完成引用计数的增1和减1操作。如果一个COM对象实现了多个接口,则可以采用同样的计数技术,只要引用计数不为0,就表明该COM对象的客户仍然在使用它(前提是客户程序正确地操作了引用计数),它就继续生存下去;反之,如果引用计数减到0,则表明客户不再使用该对象了,于是它就可以被清除。n 需要调用AddRef方法的情形:1) 当把一个非空指针写到局部变量中时2) 当被调用方把一个非空接口指针写到方法或者函数的out或者in,out参数中时3) 当被调用方返回一个非空接口指针作为函数的实际结果时4) 当把一个非空接口指针写到对象的一个数据成员中时5) 注意:QueryInterface内含AddRef,不需要再调用n 需要调用Release方法的情形:1) 在改写一个非空局部变量或者数据成员之前2) 在离开非局部变量的作用域(scope)之前3) 在被调用方要改写方法或者函数的in,out参数,并且参数的初始值为非空时。注意,对于传入的out参数,不需要释放4) 在改写一个对象的非空数据成员之前5) 在离开一个对象的析构函数之前,并且这时还有一个非空接口指针作为数据成员n 特殊情况1) 当调用方把一个非空接口指针通过in参数传递给一个函数或者方法时,既不需要调用AddRef,也不需要调用Release,因为在调用堆栈中,临时变量的生命周期只是“用于初始化形式参数”的表达式的生命周期的一个子集。1.6 重要概念:套间什么是套间?根据COM技术内幕的观点,COM没有定义自己新的线程模型,而是直接利用了Win32线程,或者说对其做了改造、包装。 COM本质论是这样定义的:套间定义了一组对象的逻辑组合,这些对象共享一组并发性和冲入限制。每个COM对象都属于某一个套间,一个套间可以包含多个COM对象。 MSDN上解释说,可以把进程中的组件对象想象为分成了很多组,每一组就是一个套间。属于这个套间的线程,可以直接调用组件,不属于这个套间的线程,要通过代理才能调用组件。 最直接的说,COM库为了实现简化多线程编程的构想,提出了套间的概念。套间是一个逻辑上的概念,它把Win32里的线程、组件等,按照一定的规则结合在一起,并且以此提供一种模式,用于多线程并发访问COM组件。可以把套间看做COM对象的管理者,它通过调度,切换COM对象的执行环境,保证COM对象的多线程调用正常运行。COM和线程不是包含关系,而是对应和关联关系。1.6.1 单进程套间 STASingle-threaded Apartments, 一个套间只关联一个线程,COM库保证对象只能由这个线程访问(通过对象的接口指针调用其他方法),其他线程不得直接访问这个对象(可以间接访问,但最终还是由这个线程访问)。COM库实现了所有调用的同步,因为只有关联线程访问COM对象。如果有N个调用同时并发,N-1个调用处于阻塞状态,如图2。对象的状态(也就是对象的成员变量的值)肯定是正确变化的,不会出现线程访问冲突而导致对象状态错误。图2 STA实现过程/创建一个STA套间并和当前线程关联:CoInitialize(NULL);/或者:CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);.:CoUninitialize();上述代码创建了一个STA,然后套间把当前的线程和自己关联在一起,线程被标记为套间线程,只有这个线程能直接调用COM对象。1.6.2 多进程套间 MTAMultithreaded Apartments,一个套间可以对应多个线程,COM对象可以被多个线程并发访问,如图3。所以这个对象的作者必须在自己的代码中实现线程保护、同步工作,保证可以正确改变自己的状态。 图3 MTA实现过程/创建一个MTA套间并和当前线程关联:CoInitializeEx(NULL,COINIT_COINIT_MULTITHREADED);.:CoUninitialize(); 第一次如此调用的时候,会创建一个MTA,然后套间把当前线程和自己关联在一起,线程被标记为自由线程。以后第二个线程再调用(同一个进程中)的时候,这个MTA会把第二个线程也关联在一起。一个MTA可以关联多个线程。所有的关联线程都可以调用套间中的组件。这就涉及到同步问题,需要组件编写者解决。 这个对于作为业务逻辑组件或干后台服务的组件非常适合。因为作为一个分布式的服务器,同一时间可能有几个服务请求到达,如果排队进行调用,那么将是不能想象的。1.6.3 NA套间 COM+为了进一步简化多线程编程,引入的中立线程套间概念。 NA/TNA/NAT,Neutral Apartment/ Thread Neutral Apartment / Neutral Threaded Apartment。这种套间只和对象相关联,没有关联的线程,因此任何线程都可以直接访问里面的对象,不存在STA还是MTA。1.7 线程模型属性组件编写者可以实现:同一个组件,既可以在STA中运行,也可以在MTA中运行,还可以在两种环境中同时存在。可以说组件有一种属性说明可以在哪种环境中生存,属性名叫做“线程模型”。这个属性为ThreadModel,可以取以下值:1 Main Thread Apartment(Single)2 Single Thread Apartment(Apartment)3 Free Thread Apartment(Free)4 Any Apartment(Both)5 Neutral Apartment(N/A)根据ATL项目向导创建COM服务器时,当我们为COM组件添加一个COM类时就可以确定其线程模型。 选择原则是根据组件的功能选择: 如果组件做I/O,首选是Free,因为可以相应其他客户端调用。如果组件和用户交互,首选Apartment,保持消息依次调用。COM+首选N/A,如果没有定义,COM库默认为是Main Tread Apartment。Apartment简单,Free强大但要自己实现同步。 当是进程内组件时,根据注册表项InprocServer32ThreadingModel和线程的不同,列于下表:创建线程关联的套间种类ThreadingModel键值组件对象最后所在套间STAApartment创建线程的套间STAFree进程内的MTA套间STABoth创建线程的套间STA“”或Single进程内的主STA套间STANeutral进程内的NA套间MTAApartment新建的一个STA套间MTAFree进程内的MTA套间MTABoth进程内的MTA套间MTA“”或Single进程内的主STA套间MTANeutral进程内的NA套间 进程内的主STA套间是进程中第一个调用CoInitialize的线程所有关联的套间(即进程中的第一个STA套间)。 当时进程外组件时,由主函数调用CoIntializeEx或CoIntialize指定组件所在套间,与上表相同。第2章 COM服务器2.1 COM服务器类型介绍n 进程内COM组件在Windows中一个正在被执行的程序被称作是一个进程。每一个应用程序(EXE)都将以一个单独的进程运行,每一个进程都有一个4GB的地址空间。一个进程中的一个地址同另一个进程中的某个地址是不同的。由于进程内组件驻留在客户端程序的地址空间中,因此它们共享同一进程,它们也可以共享同一地址空间。当客户端程序得到组件的一个接口指针时,连接客户和组件的唯一中介是接口的二进制结构。当客户查询组件的某个接口时,它所请求的实际上是具体特定格式的一块内存。当组件返回一个接口指针时,他告诉客户的实际上是此块内存的地址。由于接口是在客户和组件都能够访问的内存中,因此这种情况实际上与当客户和组件在同一EXE文件中时是相同的。n 进程外COM服务器不同EXE中的组件和客户将在不同的进程中运行,客户和组件之间的交互就会跨越进程边界。在某些情况下,进程外COM服务器也可以分为“本地服务器”和“远程服务器”。远程服务器指的是运行于另外一个不用的机器上的进程外服务器。对于进程间的通信,有几种不同的方式,如动态数据交换(DDE)、命名管道以及共享内存等。远程COM服务器所用的方法是远程过程调用(RPC),本地服务器则是通过本地过程调用(LPC)。LPC是同一机器上不同进程间通信的一种方法,它是基于远程过程调用(RPC)的用于单机上进程间通信的专利技术。将函数调用的参数从一个进程的地址空间传到另外一个进程的地址空间中需要一种方法“调整”。若两个进程都在同一个机器上,则调整过程是直接的:只需将参数数据从一个进程的地址空间复制到另外一个进程的地址空间中。若参与参数传递的两个进程在不同的地址空间中,那么考虑到不同机器在数据表示方面的不同,如整数的字节顺序可能会不一样,必须将参数数据转换成标准的格式。2.2 代理和存根当调用Win32函数时,系统实现上将调用一个DLL中的函数,而此函数将通过LPC调用Windows中的实际代码。这种结构可以将用户进程同Windows代码隔离开。由于不同的进程具有不用的地址空间,因此用户不可能对操作系统造成破坏。COM使用的结构与此类似。客户将同一个模仿组件的DLL进行通信。这个DLL可以为客户完成参数的调整及LPC调用。在COM中,此DLL(也是一个组件)被称作是一个代理。用COM术语来说,一个代理就是同另一个组件行为相同的组件。代理必须是DLL形式的,因为它们需要访问客户进程的地址空间以便对传给接口函数的数据进行调整客户程序和进程外COM服务器之间的调用关系如图4所示。客户程序只与同一进程中的LPC返回结果LPC调用组件存根存根DLL代理DLL中代理对象代理(proxy)对象打交道,组件程序只与同一进程中的存根DLL打交道,LPC调用只在代理对象和存根DLL之间进行。组件还需要一个被称作是残根的DLL,以对从客户传来的数据进行反调整,残根DLL也将对传回给客户的数据进行调整。组件对象组件程序(组件进程)客户程序(客户进程)代理DLL中代理对象LPC返回结果LPC调用组件存根存根DLL图4 客户端与组件调用关系 远程COM服务器时,即组件程序运行在不同的机器上,则代理DLL和存根DLL就通过RPC方式进行网络上的过程调用,从而实现分布式组件对象模型。因此RPC比LPC更为复杂,LPC相当于一个优化了的RPC实现。2.3 列集与散集代理DLL和存根DLL除了完成LPC调用之外,它还需要对参数和返回值进行翻译和传递,客户程序调用的参数,首先经过代理DLL的处理,它把参数以及其他的一些调用信息组装成一个数据包传递给组件进程,这个过程称为参数列集(marshaling);组件进程接收到数据包之后,要进行解包操作,把参数信息提取出来,这个过程被称为散集(unmarshaling)。函数的返回值和输出参数在返回的过程中也要进行列集和散集操作,只是在存根DLL一端进行列集,在代理DLL一端进行散集,最后把散集后的结果返回给客户程序。列集过程可通过两种方式实现:第一种列集方法为自定义列集法(custom marshaling),也称为基本列集法(basic marshaling architecture),其列集过程完全由对象自身控制,对象指定其代理对象的CLSID,代理对象控制了其所有接口的列集过程,包括接口参数的列集和散集,以及代理对象和存根代码之间的跨进程通信过程。第二种列集方法为标准列集法(standard marshaling),这是由COM提供缺省的代理对象和存根代码,因为列集过程涉及到操作系统的一些复杂特性的编程,如共享内存操作或其他跨进程数据传输机制,甚至通过网络协议传输数据,所以COM提供了缺省的代理和存根代码以及一套标准的列集方法,可以处理常用数据类型的列集和散集,包括指针类型和接口指针类型。标准列集法的原理以及其列集过程与自定义列集法完全一致,事实上标准列集法就是自定义列集法的一个特例,但两者有一个基本的不同:自定义列集法其列集过程完全由对象自身控制,所以自定义列集法以整个对象为列集单位,即对象指定的代理对象和存根代码必须处理对象支持的所有接口;而标准列集法使用COM提供的标准代理对象和存根代码,实际上该代理对象和存根代码只是接口列集过程的管理器,因此,标准列集法是以接口为列集单位,COM提供的很多标准接口,其列集过程已经由COM库提供了,程序员只需要提供自定义接口的列集代码即可。标准列集的优点:组件无需实现IMarshal接口及代理组件,但组件需要为自己生成一个代理/存根组件(Proxy/Stub),由于可通过MIDL由IDL文件自动生成,效率高,代码正确性有保证。除非有一些特殊目的,否则一般采用标准列集法。下面着重介绍标准列集法。n 用标准的列集散集函数列集函数:HRESULT CoMarshalInterface( in IStream *pStm, /写列集状态的位置 in REFIID riid, /列集指针类型 in ,iid_is(riid) IUnknown *pItf,/列集指针 in DWORD dwDestCtx, /目标的MSHCTX in void *pvDestCtx, /保留必须为0 in DWORD dwMshlFlags /标志常规还是表格列集);散集函数:HRESULT CoUnmarshalInterface( in IStream *pStm, /读取列集状态 in REFIID riid, /散集指针类型 out ,iid_is(riid) void *ppv, /存放散集指针的位置);n 用列集辅助函数和散集辅助函数列集辅助函数:在原有基础上进行了简单封装,同样适用于同一进程内的线程间传递接口指针HRESULT CoMarshalInterThreadInterfaceInStream( in REFIID riid, in ,iid_is(riid) IUnknown *pItf, out IStream *ppStm);散集辅助函数:在原有基础上进行了简单封装,同样适用于同一进程内的线程间传递接口指针HRESULT CoGetInterfaceAndReleaseStream( in IStream *pStm, /读取列集状态 in REFIID riid, /散集指针类型 out ,iid_is(riid) void *ppv,/存放散集指针的位置);n 全局接口表(GIT, Global Interface Table):允许接口指针被进程内所有套件访问。一个进程拥有一个GIT,其所包含的经过列集的接口指针,在同一进程中可以被有效的散集多次IGoballnterfaceTable接口定义: MIDL_INTERFACE(00000146-0000-0000-C000-000000000046) IGlobalInterfaceTable : public IUnknown public: virtual HRESULT STDMETHODCALLTYPE RegisterInterfaceInGlobal( /* annotationin */ _in IUnknown *pUnk, /* annotationin */ _in REFIID riid, /* annotationout */ _out DWORD *pdwCookie) = 0; virtual HRESULT STDMETHODCALLTYPE RevokeInterfaceFromGlobal( /* annotationin */ _in DWORD dwCookie) = 0; virtual HRESULT STDMETHODCALLTYPE GetInterfaceFromGlobal( /* annotationin */ _in DWORD dwCookie, /* annotationin */ _in REFIID riid, /* annotationiid_isout */ _deref_out void *ppv) = 0; ;下面结合实例介绍列集散集函数方法:实例工程PrintServer中已实现一个本地COM服务器,其中暴露的IMyFun接口中包含了两个方法:PringOne 和 PrintTwo。这两个方法只是简单返回了两个不同的字符串,方便在客户端调用时区分。/ PrintServer.idl : PrintServer 的 IDL文件 object,uuid(7EC1667C-1037-48D4-ACF0-F1159414793B),pointer_default(unique)interface IMyFun : IUnknownid(1), helpstring(方法PrintOne) HRESULT PrintOne(out, string BSTR *pbstr);id(2), helpstring(方法PrintTwo) HRESULT PrintTwo(out, string BSTR *pbstr);uuid(C606F70F-EF78-47DD-A051-3C93CC093E1D),version(1.0),library PrintServerLibimportlib(stdole2.tlb);uuid(FCD6C50B-EFEB-4E9D-9714-09D5A5341D43)coclass MyFundefault interface IMyFun;下面介绍客户端中实现跨套间调用接口方法的实例。首先获取接口COM对象实例。/生成MTA套间并关联HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);if( FAILED(hr) )printf(CoinitializeEx failed!);CloseHandle(g_heventWritten);return 0;/创建实例对象 IMyFun * pTest = NULL;hr = :CoCreateInstance(CLSID_MyFun,0,CLSCTX_LOCAL_SERVER,IID_IMyFun,(void*)&pTest);if( FAILED(hr) )printf(CoCreateInstaance failed!);CloseHandle(g_heventWritten);:CoUninitialize();return 0;列集实现函数:/列集实现函数HRESULT WritePtrToGlobalVariable(IMyFun *pRacer, HGLOBAL & rhglobal )/存放列集指针 IStream *pStmPtr = NULL;/分配并封装内存块HRESULT hr = CreateStreamOnHGlobal(0,FALSE,&pStmPtr);if( SUCCEEDED(hr) )/将列集对象写入内存块hr = CoMarshalInterface( pStmPtr,IID_IMyFun, pRacer, MSHCTX_INPROC ,0,MSHLFLAGS_TABLESTRONG );/抽出底层内存的句柄if( SUCCEEDED(hr) )hr = GetHGlobalFromStream( pStmPtr, &g_rhglobal);pStmPtr-Release();SetEvent(g_heventWritten);return hr;散集实现函数:/散集函数HRESULT ReadPtrFromGlobalVariable(HGLOBAL rhglobal,IMyFun *&rpRacer)IStream *pStmPtr = NULL;/现有内存封装块从输入传入HRESULT hr = Cr

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论