COM(三)进程透明性、标准列集、自定义列集

来源:互联网 发布:下载动画的软件 编辑:程序博客网 时间:2024/05/21 18:30

进程透明性

不管是进程内组件还是进程外组件,客户程序可以使用一致的方法创建COM对象

对于进程外组件,创建成功后,可以用返回的对象接口指针调用对象的成员函数,由于客户程序和组件对象不是同一个进程空间中,所以接口的调用是间接进行的,但是客户程序调用接口函数的时候如果调用本进程内的函数一样。

进程外组件的调用方法:

列集:是指客户进程可以透明地调用另一进程中的对象成员函数的一种参数处理机制

列集处理过程包括自定义列集法标准列集法

自定义列集法又称基本列集法,其列集过程完全由对象自身控制,对象指定其代理对象的CLSID,代理对象控制了所有接口的列集、散集以及代理对象和存根代码间的跨进程通信过程。

标准列集法由COM提供缺省的代理对象、存根代码以及一套标准的列集方法,用于处理常用数据类型的列集和散集

标准列集法是自定义列集法的一个特例,但自定义列订法的列集过程完全由对象自身控制。

 

列集

类厂对象的列集过程:

CoMarshalInterfac函数流程分解:

函数首先向对象请求IMarshal接口指针,如果对象实现了IMarshal接口,则对象使用自定义列集方法,否则使用标准列集方法。

1.  调用 IMarshal接口的成员函数GetUnmarshalClass获取对象的CLSID,如果对象没有实现IMarshal接口,则指定使用COM提供的缺省代理对象,其CLSID为CLSID_StdMarshal.

2.  调用IMarshal接口成员函数GetMarshalSizeMax函数确定列集数据包可能的最大值,并分配一定的空间。(列集信息的传输由服务控制管理器—SCM控制)

3.  调用IMarshal接口成员函数MarshalInterface建立列集数据包

服务控制管理器(SCM):使用列集环境来描述进程间所存在的障碍或通道,可能的值如下

MSHCTX_NOSHAREDMEM:不能使用共享内存作为数据传输方式

MSHCTX_DIFFERENTMACHINE:两个进程可能在不同的机器上

这些值由CoMarshalInterface函数及IMarshal::GetUnmarshalClass中的的dwDestContext参数提供

CoMarshalInterface函数及IMarshal::GetUnmarshalClass中的mshlflags参数指示了列集的目的:

MSHLFLAGS_NORMAL:列集马上用于连接一个客户进程

MSHLFLAGS_TABLESTRONG、MSHLFLAGS_TABLEWEAK:列集信息被保存在全局对象表中,连接不一定马上发生,由CoRegisterClassObject函数完成,客户进程中的CoGetClassObject会访问这些列集信息,以便创建代理对象。

 

CoUnmarshalInterface函数流程

1.      根据列集信息中代理对象CLSID值创建代理对象,并请求得到IMarshal接口指针(不管使用什么列集方式,代理对象都必须实现IMarshal接口)

2.      把列集数据包传给IMarshal::UnmarshalInterface函数,代理对象读入数据包中的连接信息建立它与对象之间的连接,并把函数调用接口指针返回给客户

 

创建客户与对象间连接的流程(自定义列集):

由于自定义列集方式下,实现组件程序时,必须自己编写代理对象,而且在组件对象中必须自己处理与代理对象间的连接,这使得组件程序的编写非常复杂,因此,除非特殊需要,一般不使用自定义列集方式。以下情况可以考虑使用:

1.      为了节约跨进程操作或者降低网络通信流量,根据具体对象实现的需要提高性能。

2.      组件对象本身状态是不变的,则代理对象可以拥有组件对象的状态,并且完全代表组件对象提供功能服务,这种情况下,代理对象完全实现了组件的功能。代理对象不需调用另一个进程的组件对象

3.      虽然组件对象的状态不是固定的,但在代理对象中也可以直接访问这些状态值,甚至改变这些状态,例如它可以访问共享内存中的状态值或者一些全局信息,那么代理对象可以不调用组件进程中的对象成员函数,而直接提供功能服务。这些也可以提高代理对象的执行效率。

4.      如果组件对象本身也是一个代理对象,则客户调用的代理对象就成了代理的代理对象,这样多级代理显示是低效率的,这种情况下,可以缩短代理的路径,从客户进程一眇调用到目标组件对象,风前月下是本地进程之间的调用还是网络环境下的远程调用,这种多级对象代理应该避免。

 

标准列集

特点:处理列集和散集是以接口为基本代码单元,而不是针对整个组件对象,因此,针对特定接口的列集代码和散集代码分别称为"接口代理"和"接口存根"

接口代理与代理对象的区别:接口代理只负责特定接口的列集过程,过对象代理或代理对象负责整个对象的列集处理过程.

客户进程中代理对象中有一部分代码专门管理接口代理,被称为代理管理器,负责在必要的时候装卸载 接口代理 代码.

接口代理本身也是COM对象,代理对象用聚合的方式把所有的接口代理组成一个大的整体,形成一个大的COM对象,它实现了所有组件对象支持的接口

每一个 接口代理 都实现了两个接口:1. 它所代理的接口 2. 接口lRpcProxyBuffer,但是代理对象 只聚合代理的接口(ITF1),而不聚合IRpcProxyBuffer

IRpcProxyBuffer作用:代理管理器 内部使用 接口代理 的该接口,使 接口代理 与RPC 通道连接起来。

 

代理对象相对应的是 存根代码 也称为 存根对象,实际上它是进程中的一组代码,包括 存根管理器 和 接口存根。

存根管理器:负责管理所有的接口存根,包括接口存根的装卸、接口存根与 组件对象的连接等。

 

COM要求同一个接口的 接口代理 和 接口存根 在同一个COM对象上实现,通常称为接口代理/存根对象。虽然 接口代理 和 接口存根 在同一个COM对象上实现,但在使用上是完全分开的,它们位于不同的进程中。

 

代理管理器和 存根管理器 如何创建 接口代理 和接口存根?

1.      代理管理器从注册表中找到代理/存根对象的CLSID

HKEY_CLASS_ROOT\Interface下与组件对象ID同名键下ProxyStubClsid32子键中指定了代理/存根对象的CLSID。

函数LookUpInRegistry(组件ID)可以该CLSID。

2.      调用CoGetClassObject函数得到专用于代理/存根对象创建的类厂的IPSFactoryBuffer接口。

3.      调用 IPSFactoryBuffer接口的CreateProxy成员函数创建接口代理(CreateStub成员函数创建接口存根)

 

客户进程和 组件进程是通过RPC进行通信的。接口代理和接口存根分别与接口IRpcChannelBuffer打交道,实现双方通讯。

1.  接口代理调用IRpcChannelBuffer::GetBuffer获得一个数据缓冲区,用于存放接口成员函数的参数信息。

2.  接口代理调用IRpcChannelBuffer::SendReceive函数。

3.  组件进程中的PRC通道调用相应接口存根的IRpcStubBuffer::Invoke成员函数。

4.  接口存根调用完组件对象的成员函数后,调用IRpcChannelBuffer::GetBuffer获得一个缓冲区,用以存放输出参数和返回值。

5.  接口存根函数返回后,RPC通道把结果缓冲区会回给接口代理,于是接口代理的IRpcChannelBuffer::SendReceive函数返回,接口代理把调用结果从缓冲区中取出来,并返回给客户。

大多数情况下,接口代理与接口存根间的通信是按同步方式进行的,即SendReceive函数必须等组件进程处理完整个调用后才返回,COM也允许异步方式调用或者 输入同步方式进行。

 

接口代理和接口存根的列集和散集过程:

1.      如果组件对象的接口成员函数的参数是一般的数据类型或者数据结构,则按接口语义进行列集或散集处理。

2.      函数参数中包含接口指针作为输出参数,并且在组件对象的成员函数中需要创建新的组件对象,并把组件对象的接口指针传给另一进程中的客户,实际上是一个连接创建另一个连接的过程,接口存根在处理接口指针时,调用CoMarshalInterface函数,并把列集数据包放到缓冲区中传给客户进程。接口代理接到缓冲区后,调用CoUnmarshallInterface函数把列集数据解译出来返回给客户。

注:如果客户调用过程中新的接口指针指向同一个对象,如果新接口的代理/存根对象还没有被装入到内存中,则COM提供的标准IUnknown列数器QueryInterface成员函数会处理新接口列集和散集过程,它会调用接口代理/存根的类厂接口IPSFactoryBuffer创建接口代理和存根。

 

接口代理接口存根的接口

IRpcProxyBuffer:接口由接口代理实现,用于代理管理器通过此接口控制接口代理与RPC通道的连接。Connect成员函数建立接口代理与RPC通道的连接,Disconnect成员函数取消它与RPC通道的连接。一旦连接建立起来,以后接口代理就可以使用此RPC通道进行跨进程的通信。

接口代理装载过程:一个接口代理模块可以代理多个接口,代理对象聚合了所有的接口代理,当代理对象的QueryInterface被执行时,它向所有已经装入到内存中的接口代理询问是否支持某个接口,如果某个接口代理模块被聚合了多个接口,只要注册表信息正常,代理管理器会自动找到合适的代码并装入到内存中,并且,以后再请求另一个接口时,不必再装入新的代码模块。

IRpcStubBuffer:由接口存根实现,接口存根在组件进程中运行,IRpcStubBuffer接口把RPC通道与组件对象连接起来,Connect和Disconnect成员函数分别用于建立或取消与组件对象的连接;Invoke成员函数完成对组件对象成员函数的实际调用。

接口存根装载过程:由于存根管理器并不用聚合的方式管理接口存根,所以它所支持的多个接口必须具有继承关系,存根管理器在创建新的接口存根之前,必须先对已经装入到内存中的接口存根调用 IRpcStubBuffer::IsIIDSupported成员函数,询问它是否支持指定接口的散集处理。具有继承关系的两个接口,可以通过IRpcStubBuffer::IsIIDSupported函数在同一个接口存根上实现。

由于接口代理和接口存根是动态加载的,所以即使在客户运行过程中,一次请求返回错误,然后安装了新的代理/存根组件程序进行注册,则下次请求就可以得到正常的结果。

 

标准列集的实现

COM已经提供了缺省的代理对象、存根管理器以及RPC通道,我们只需要实现每一个接口的代理/存根模块。一量系统中安装了某个接口的代理/存根程序并正确地进行了注册,则代理管理器和存根管理器会在需要的时候自动加载接口代理和接口存根。因此,从实现的角度来讲,我们的任务就是针对接口实现代理/存根程序。

代理/存根组件是一个DLL程序,实现了接口代理、接口存根以及相应的类厂(类厂支持IPSFactoryBuffer接口),通过IPSFactoryBuffer::CreateProxy和IPSFactoryBuffer::CreateStub成员函数创建接口代理和接口存根对象。

接口代理对象支持两个接口:1.本身提供列集特性的接口  2.IRpcProxyBuffer接口(只有Connect 和 Disconnect成员函数),用于被代理管理器创建或取消它与RPC通道的连接

 

在同一台机器上的进程外组件:COM会根据注册表中的接口信息,在两个进程中使用相同的代理/存根程序,程序只要保证接口代理/存根程序中对参数的列集和散集格式一致,参数传递就不会有问题。

不同机器上的进程外组件:对参数的列集和散集最好使用统一的数据格式表示,以保证参数传递的正确性。

如果一个进程外组件实现了多个自定义COM接口,则我们必须实现代理/存根程序,并注册到系统中,然后才能真正使用这些接口,但如果发现OLE中有适合自己用途的接口,可以直接拿来使用,而不必再自定义新的接口,这样可以避免编写代理/存根程序。

 

Microsoft提供了MIDL实用工具帮助我们自定义接口的代理/存根程序。

1.  使用IDL(接口描述语言)语言建立接口描述文件

2.  运行MIDL工具,它会根据接口供述文件生成一些C语言源代码文件,用这些源代码文件我们可以创建代理/存根组件程序。(为我们提供接口代理/存根组件的一种标准实现方法)

3.  编写DEF文件

4.  编写MAK文件

5.  编译连接得到接口/存根组件程序

6.  运行regsvr32.exe注册组件程序

0 0
原创粉丝点击