资源描述
单击此处编辑母版标题样式,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,*,十. COM跨进程特性,进程外组件,进程内,COM,对象的缺陷,引出函数,列集,列集的概念,接口指针的列集,标准列集过程,总体结构,存根,代理,接口列集器,ORPC,标准列集的实现,自定义列集,IMarshal,接口,例子,1,1.进程外组件,1.1 进程内,COM,对象的缺陷,缺乏错误隔离.,对象的运行时错误将直接引起客户程序的崩溃.,安全环境与权限共享,对象在客户进程中,使用客户进程的安全环境与权限. 这意味着,特权客户程序创建的对象可以进行危险的操作;,较低权限的用户创建的对象可能无法访问一些重要的资源,从而无法达到计划的目的.,很难实现分布式计算,进程外的,COM,组件是实现,DCOM,的基础.,进程外的,COM,对象具有重要的意义.,无论哪种方式(进程内,进程外,远程)对客户而言,是透明的.,2,进程外,COM,组件的实现以,exe,的形式存在.可以独立执行.不能引出任何函数。,DllRegisterServer,和,DllUnregisterServer,.,DllGetClassObject DllCanUnloadNow,注册:,执行时会检测命令行中是否有参数/,RegServer,或/,UnregServer,以决定是否进行注册(注销). 注册表中用,LocalServer32,键代替,InprocServer32,键.,创建类厂:,当客户调用,CoGetClassObject,创建类厂对象时:,在,CoGetClassObject,函数内部,它找到,EXE,组件的程序位置后,发现是一个进程外组件, 于是启动组件进程,然后等待.,组件进程启动后(使用了/,Embedding,参数),调用,CoInitialize,初始化,创建所有的类厂,调用,CoRegisterClassObject,把类厂注册到,COM,中.,客户进程得到了组件的类厂信息,创建类厂,然后创建对象.,组件卸载,:判断是否可以退出的条件与,dll,相类似. 组件进程退出时,使用,CoRevokeClassObject,函数在,COM,库中注销掉其所支持的类厂.,CoRegisterClassObject,与,CoRevokeClassObject,要配对使用,以保证,COM,信息的一致性.,1.2 引出函数,3,进程间通讯方式: 共享内存,管道,消息队列等等。,进程外,COM,组件与客户进程之间使用,RPC,进行通讯.,COM,对,MS RPC,其进行了扩充,以支持面向对象的调用。称为,ORPC (Object Remote Procedure Call) 。 ORPC,使用标准的,RPC,数据包,附加上专用于,COM,的信息,如接口指针标识符。在,ORPC,数据包经过列集后的数据按照,NDR,格式保存(,网络数据表示法,Network Data Representation,)(CORBA,使用,CDR Common Data Representation,Web,服务使用,XML,)。,示意图如下:,2. 进程间通讯,4,在客户进程与组件对象之间是代理对象和存根对象. 代理和存根直接使用系统,RPC.,在机器内部,,RPC,有简化版本,LPC。,在,ORPC,中, 调用请求和返回结果要经过,列集,和,散集,的过程. 其定义如下.,客户程序,(客户进程),代理对象,组件程序,(组件进程),存根代码,LPC/RPC,组件对象,5,列集(,marshaling),和散集(,unmarshaling),marshal,的含义:编组、调度、引导、安排,整顿、配置、汇集、排列、集合,定义:是指客户进程可以透明地调用另一进程中的对象成员函数的一种参数处理机制,在调用过程中如果涉及到数值或指针的传递,则列集过程如下:,数值: 比如一个32位整数, 把四个字节的数据顺序装入到字节流中即可.,地址: 一个进程中的地址对另一个进程没有意义. 因此,列集时是把地址中的数据取出来封装到数据包中,散集时,在客户进程中分配一块内存数据包中的数据拷贝到内存中,然后返回内存地址.,接口指针: 实际上列集更重要的工作在于获取对象的接口指针.客户程序的一个有效接口指针代表客户进程到组件进程的一个连接.列集一个接口指针远比一个一般的指针要复杂.,以下讨论的列集一般都指,接口指针的列集,.,2.1 列集的概念,6,2.2 接口指针的列集,接口指针列集的结果是把它变为一个可以被传输的字节流,字节流的内容唯一地标识了对象和对象所处的环境. (即套间,Apartment,见后, 现在可以理解为运行环境),列集过程分为两种:,标准列集,和,自定义列集,.由于列集要使用到底层的传输协议,而这些代码往往对所有的对象而言是,类似的,所以,COM,提供了标准列集法,凡是没有特别指明的,都是使用标准列集法. 为了效率等因素,对象可以选择自己控制底层的通讯. 称为自定义列集法.,标准列集方式下的接口指针列集数据流. 见下图:,7,其中OXID代表了对象的运行环境(套间). 代理需要使用此OXID来解析成网络或者IPC(进程间通讯)的地址,以便与对象的套间进行联系. OXID的解析工作由OXID解析器(OXID Resolver, OR)完成. OR是RPCSS服务的一部分.,接口指针的列集过程是由COM库函数CoMarshalInterface完成的:,MEOW,FLAGS,IID,STD FLAGS,cPublicRefs,OXID (Object Exporter ID),OID (Object ID),IPID (Interface Pointer ID),cch,secOffset,Host Addresses,Security PackageInfo,签名符号,标准/自定义列集方式,被列集的接口,IID,与标准列集有关的标志,引用计数,对象引出标识符,对象标识符(存根管理器),接口指针标识符(接口存根),字符数,安全信息偏移,Host Addresses,Security PackageInfo,8,HRESULT CoMarshalInterface(,IStream *pStm, /,列集数据的存放位置,是一个流.底层介质可以是磁盘,内存,或自定义的介质. 见结构化存储.,REFIID riid, /,列集指针的类型,IUnknown *pUnk, /,列集指针,当然它应该是,riid,类型的.,DWORD dwDestContext, /,目标环境.,void *long pvDestContext, /,保留.,DWORD mshlflags /,常规列集还是表格列集(写到一个全局的接口表中,可以被多次散集),);,散集过程由函数,CoUnmarshalInterface,完成,HRESULT CoUnmarshalInterface(,IStream *pStm, /,包含有列集内容的流,REFIID riid, /,散集指针类型,void * * ppvObj /,存放散集指针的位置,);,一般而言,除非在进程内(而且套间类型相吻合),散集的接口不是原来的接口本身,而是一个代理.,9,3 标准列集过程,3.1 总体结构,客户进程,代理对象,组件进程,ITF1,客户程序,ITF2,ITF,n,代理,管理器,IRpcChannelBuffer,系统,RPC,组件对象,存根代码,ITF1,ITF2,ITF,n,存根,管理器,系统,RPC,IRpcProxyBuffe,r,IRpcStubBuffer,RPC,通道,RPC,通道,10,当,CoMarshalInterface,第一次确定对象希望使用标准列集时, 就创建一个特殊的,COM,对象: “存根管理器”. (,Stub Manager).,存根管理器与,COM,对象一一对应, 被对象标识符,OID,标识(见接口的列集数据图). 并且拥有一个对,COM,对象的引用. 可以理解为一个进程内的客户.,存根管理器并不知道如何处理,ORPC,请求.它针对每个,COM,接口管理一个“接口存根”对象(,interface stub).,接口存根是用,IPID,来标识. 接口存根知道关于这个接口的所有细节,它知道如何把,ORPC,请求消息中出现的所有,in,参数都散集出来,并且调用实际对象中的方法,然后把,HRESULT,结果和所有,out,参数列集到,ORPC,相应消息中去. 接口存根也有一个对,COM,对象的引用.,3.2 存根,11,接口存根实现,IRpcStubBuffer,接口,class IRpcStubBuffer : public IUnknown, virtual HRESULT Connect(IUnknown *pUnkServer) = 0;,/把接口存根与目标,COM,对象联系起来,virtual void Disconnect() = 0; /,释放对象,virtual HRESULT,Invoke,(RPCOLEMESSAGE *pMessage,IRpcChannelBuffer *pChannel) = 0;,/,当,ORPC,请求到达对象一方时,COM,库会调用,Invoke,方法,*pMessage,包含所有经过列集的,in,参数,也要利用,RPC,通道把处理结果发送回去,virtual IRPCStubBuffer* IsIIDSupported(REFIID iid) = 0;,virtual ULONG CountRefs() = 0;,virtual HRESULT DebugServerQueryInterface(void *ppv) = 0;,virtual void DebugServerRelease(void *pv) = 0;,;,12,当,CoUnmarshalInterface,把一个标准列集得到的对象引用散集出来的时候,它会创建一个“代理管理器”(,proxy manager).,和存根管理器一样,它也不懂,COM,接口的任何知识,它也要针对每一个接口创建一个“接口代理”对象(,Interface proxy),并且把这些对象都聚合在其内部.让客户感觉所有的接口都是从这个代理管理器上实现的.,代理管理器实现了,IUnknown,的三个函数. 并且对,AddRef,和,Release,进行了优化处理,使得这些操作非到最后,只是增减本地的一个引用计数.这样以减少网络开销.,接口代理把客户的调用请求转换成为,ORPC,请求消息(列集,in,参数).并且把,ORPC,相应消息中的,out,消息和,HRESULT,散集出来,返回给客户进程.,3.3 代理,13,每个接口代理实现,IRpcProxyBuffer,接口.,class IRpcProxyBuffer : public IUnknown,virtual HRESULT Connect(IRpcChannelBuffer *pRpcChannelBuffer) = 0;,virtual void Disconnect() = 0;,;,接口代理管理器通过这个接口把接口代理与,RPC,通道连接起来,,Connect,方法把,RPC,通道保存起来,接口代理接到方法请求后,通过,IRpcChannelBuffer,接口的,GetBuffer,和,SendReceive,方法处理远程方法调用,14,接口代理和接口存根分别由代理管理器和存根管理器创建.它们共享同一个,CLSID.,包含两个分叉实现的实体称为,接口列集器,(,Interface marshaler).,接口列集器的类厂没有实现接口,IClassFactory (,有一个成员函数,CreateInstance,以创建对应的,COM,对象) ,相反,它实现了接口,IPSFactoryBuffer., uuid(D5F569D0-593B-101A-B569-08002B2DBF7A),local,object ,interface IPSFactoryBuffer : IUnknown, HRESULT,CreateProxy,(,in IUnknown *pUnkOuter, /,代理管理器指针,in REFIID riid, /,请求的远程接口指针的,IID,out IRpcProxyBuffer *ppProxy, /,输出接口代理指针,out void *ppv,);,/,远程接口指针,HRESULT,CreateStub,( in REFIID riid, /,请求的远程接口指针,IID,in IUnknown *pUnkServer, /,实际对象指针,out IRpcStubBuffer *ppStub,); ,/,输出接口存根指针,接口列集器的,CLSID,存放在注册表中.,3.4 接口列集器,15,3.5,ORPC,通道,为了使用,ORPC,通道,COM,提供了一个通道(,channel),对象, 通道对象封装了,ORPC,的功能,它实现了接口,IRpcChannelBuffer.,typedef struct tagRPCOLEMESSAGE, void *reserved1;,unsigned long dataRepresentation; / endian/ebcdic,编码方式,void *Buffer; /,载荷,ULONG cbBuffer; /,载荷长度,ULONG iMethod; /,方法,void *reserved25;,ULONG rpcFlags; ,RPCOLEMESSAGE;,/ORPC,消息的表示,16,class,IRpcChannelBuffer,: public IUnknown /ORPC,通道, virtual HRESULT GetBuffer(RPCOLEMESSAGE *pMessage,REFIID riid) = 0; /,分配缓冲区,virtual HRESULT,SendReceive,(RPCOLEMESSAGE,pMessage,ULONG *pStatus) = 0; /,发送,ORPC,请求并接收响应,virtual HRESULT FreeBuffer(RPCOLEMESSAGE pMessage) = 0; /,释放缓冲,virtual HRESULT GetDestCtx(DWORD *pdwDestCtx,void *ppvDestCtx) = 0;,virtual HRESULT IsConnected() = 0;,;,17,4 标准列集的实现,COM,已经提供了缺省的代理对象、存根管理器以及,RPC,通道,我们只需实现每一个接口的代理/存根组件。参数和返回值的数据类型是关键。,首先使用,IDL,语言描述接口,编写,IDL,文件。产生,dictionary.,idl,MIDL.exe,是,Win32SDK,提供的工具。它能编译,idl,文档以产生以下代码:命令行:,midl,dictionary.,idl,则产生下面的文件:,dictionary.h ,包含接口说明的头文件,可用于,C,或者,C+,语言;,dictionary_p.c ,该,C,文件实现了接口,IDictionary,的代理和存根;,dictionary_i.c ,该,C,文件定义了,IDL,文件中用到的所有全局描述符,GUID,,包括接口描述符;,dlldata,.c ,该,C,文件包含代理/存根程序的入口函数以及代理类厂所需要的数据结构等。(,DllGetClassObject,等函数),18,准备一个,DEF,文件,LIBRARY,MyLib,DESCRIPTION,IDictionary,Interface Proxy/Stub DLL,EXPORTS,DllGetClassObject,1 PRIVATE,DllCanUnloadNow,2 PRIVATE,GetProxyDllInfo,3 PRIVATE,DllRegisterServer,4 PRIVATE,DllUnregisterServer,5 PRIVATE,创建一个空的,win32,dll,工程 加入以上5个文件,projectsettingsC/C+preprocessor definitionsREGISTER_PROXY_DLL,projectsettingsLinkobject/library modules ,uuid,.lib rpcrt4.lib,以上4,5,6 也可以编写一个,MAKE,文件在编译选项中加入,REGISTER_PROXY_DLL,连接选项中加入,rpcrt4.lib、,uuid,.lib,来完成,编译,注册。(代理与存根都是,DLL,不要与进程外对象混淆),在实际的编程工作中往往并不这样进行处理。因为集成开发环境已经提供了对,IDL,文件的编译支持。,IDE,可以启动,MIDL,对,IDL,进行编译。不需手工编写,makefile,。,并且可以把代理存根和可执行代码编译在一起。,19,5. 自定义列集,为了性能的原因,我们有可能使用自定义列集.接口指针经过自定义列集后的数据流结构如下:,MEOW,FLAGS,IID,CLSID,cb,data,签名符号,标准/自定义列集方式,被列集的接口,IID,自定义代理的,CLSID,自定义列集数据的字节数,自定义列集数据,一个对象如果实现了,IMarshal,则表明它希望使用自定义列集法.,20,5.1,IMarshal,接口,IMashal,接口是使用自定义列集或标准列集的标志:,class IMarshal : public IUnknown,HRESULT GetUnmarshalClass( .) = 0;,/,获取自定义代理的,CLSID,由,CoMashalInterface,调用.,HRESULT GetMarshalSizeMax(.) = 0;,/获取自定义对象引用的大小 由,CoGetMarshalSizeMax,调用,HRESULT MarshalInterface( .) = 0;,/,对接口进行列集,写入流中 由,CoMarshalInterface,调用,HRESULT UnmarshalInterface(.) = 0;,/,从流中散集出接口来 由,CoUnmarshalInterface,调用,HRESULT DisconnectObject(.) = 0;,/,关闭连接 由,CoDisconnectObject,调用,HRESULT ReleaseMarshalData(.) = 0;,/,释放列集数据 由,CoReleaseMarshalData,调用.,;,21,列集过程(第一次)发生在对象进程中,首先向对象查询是否实现了,IMarshal,接口,如果实现了,则调用其,GetUnmarshalClass,成员函数获取代理对象的,CLSID (,如果对象没有实现,IMarshal,接口,则指定使用,COM,提供的缺省代理对象,其,CLSID,为,CLSID_StdMarshal,这是标准列集的过程 )。,调用,GetMarshalSizeMax,函数确定列集数据包最大可能的大小值,并分配一定的空间。,调用,MarshalInterface,成员函数建立列集数据包。,散集过程(第一次)发生在客户进程中,从,stream,中读出,proxy,的,CLSID,根据,CLSID,创建一个,proxy,获取,proxy,的,IMarshal,接口指针,调用,IMarshal:UnmarshalInterface,,把,stream,中的数据传给,proxy,proxy,根据这些数据建立起它与对象之间的连接,并返回客户请求的接口指针,22,自定义列集的要点,对象必须实现,IMarshal,接口,代理对象也必须实现,IMarshal,接口,并且代理对象与进程外对象之间协作,代理对象必须负责所有接口的跨进程操作,典型用途:提高跨进程调用的效率,使用缓存状态等优化技术,23,5.2 自定义列集例子,假定客户已经建立了它与类厂之间的连接,也就是说它通过CoGetClassObject获得了类厂的接口指针,客户要通过类厂创建另一个COM对象,而这个对象使用custom marshaling,客户调用IClassFactory:CreateInstance创建对象,并返回对象的接口指针,注意这里类厂对象使用的标准列集, COM对象使用的自定义列集.,24,通过类厂建立代理对象和组件对象自定义列集过程,25,
展开阅读全文