com之 代理(proxy)与存根(stub)
什么是代理和存根 ?
打个比方,你到自动取款机上去取款;你就是客户,取款机就是你的代理;你不会在乎
钱具体放在那里,你只想看到足够或更多的钱从出口出来(这就是com的透明性)。你同银行之间的操作完全是取款机代理实现。 你的取款请求通过取款机,传到另一头,银行的服务器,他也没有必要知道你在哪儿取钱,他所关心的是你的身份,和你取款多少。当他确认你的权限,就进行相应的操作,返回操作结果给取款机,取款机根据服务器返回结果,从保险柜里取出相应数量的钱给你。你取出卡后,操作完成。 取款机不是直接同服务器连接的,他们之间还有一个“存根”,取款机与存根通信,服务器与存根通信。从某种意义上说存根就是服务器的代理。
图1 组件间通信
如上图两个组件之间不是直接通信,而是通过代理和存根来之间的通信来间接实现的。图中的channel是com库的一部分。
COM里,只有进程外组件才会用到代理(proxy)和存根(stub)。
代理在客户的进程内创建,存根在组件com对象的进程中创建。 每个接口的每个函数都有自己的代理和存根。
为什么要用代理和存根 ?
客户为什么要用代理和存根,而不直接同对象连接呢? 给你一个理由,对客户来说,他与所有com对象的连接都是通过指针来调用的, 而对服务来说,调用对象的接口函数也是通过指针来完成的,然而,指针只有在同一进程内才会有效。这样,代理和存根为了完成这个使命也就产生了。
代理和存根的作用不只这些,他还要打包所有的参数(包括接口指针),产生 RPC(远程进程调用),通向另一个进程,或者对象运行所在的另一台机器。
图2 代理的结构
上图所显示的代理结构支持参数的标准列集。每个接口的代理实现了IRpcProxyBuffer接口,用于内聚各个部分之间的相互通信。当代理准备把已列集的参数传递过进程边界时,他调用IRpcChannelBuffer 接口的方法(该接口由channel实现)。channel调用RPC运行库使数据传输到目的地。
图3 存根的结构
如上图所示,每个接口的存根被连接到对象的相应接口上。chnnel分发传入的消息到适当的接口的存根。所有的组件通过IRpcChannelBuffer接口与chnnel交流,这个接口提供了与RPC运行库的连接。
列集(marshalling)
说到代理和存根,自然少不了列集,什么是列集?
列集,对函数参数进行打包处理得过程,因为指针等数据,必须通过一定得转换,才能被另一组件所理解,列集完成后,RPC调用就会产生。可以说列集是一种数据格式的转换方法。
列集有3种方式:
1. 类型库列集
它可以列集与OLEAUTOMATION兼容的任何接口,意思是你的接口的返回值必须是HRESULT,所使用的参数的类型也应该是与C++的VARIANT结构兼容。
2. 通过创建Stub / proxy DLL
这个DLL的源代由MIDL产生。你必须在服务器和客户机上都注册这个DLL(这是标准的marshal 方式)使用吃方法时,最好把stub / proxy代码编译作为一个独立的组件。
3. 自定义marshaling
自定义marshal要求在你的组件中必须实现IMarshal接口。当COM需要marchal时,他首先通过QueryInterface看你是否支持IMarshal接口,如果你实现了该接口,也就是说,由你控制了你的COM的所有参数和返回值的打包、解包的方法模式。
代理和存根dll的建立
使用工具MIDL,对一个IDL文件,MIDL会分析自动产生相应的代理/存根 DLL的相关文件。
怎么使用代理和存根
对于你来说代理和存根的使用是透明的,你根本不用去关心如何使用他们,com库会知道怎么做。