从C++到COM,学习笔记(7)

转自:http://blog.sina.com.cn/s/blog_701b41e40100m4jn.html

组件的跨进程调用和Marshal
 
##关于这个东西,其实我一直都没有太搞明白,笔记断了这么长时间也是如此。只能先把自己理解的整理一下,以后再删补吧##
 
一. 跨进程调用的必要性
前面已经说了,对COM而言,一开始的目标就是增强重用性。藏身于DLL的COM组件能够部分解决这个问题,一个DLL文件可以在物理内存中只存在一份,然后被映射到不同的客户的进程,完成同样的任务。这样每个客户可以避免重复编写这部分功能的程序,也可以避免在内存中同时留有功能重复的二进制代码。
然而,DLL的解决方案并不是十全十美。首先,DLL和客户共享内存空间,组件的危险代码可能危及到客户进程的安全,而安全性在很多实际应用中是优先考虑的。其次,DLL被载入意味着DLL组件必须在客户所在的计算机上保留一份拷贝,这无法实现网络式的远程分布应用。这些都是为什么要考虑进程外的组件以及跨进程调用的原因。
 
二. 调用的实现思想
和进程内COM组件不同,进程外COM组件的最大特点就是组件和客户不在一个内存空间中。因为这个,带来了一定安全,也因为这个导致了很多调用上不便。
虽然名义上COM组件都是通过接口和客户打交道,但是最终的使用还是通过函数调用的。如果在同一个进程中,调用函数只不过是一个参数压栈(甚至可能都不需要)然后地址跳转的过程而已,但是当需要调用另外一个进程的函数时,问题就变的复杂很多。
COM利用Proxy和Stub的方式来处理进程间调用,从而把进程间通信的部分掩盖了起来。Proxy和客户端在同一进程内,是进程内调用的组件,负责被客户端调用,而Stub则和组件在同一进程内,负责以进程内调用的形式调用COM组件。对于客户端来说,永远都是做进程内调用,它并不了解自己调用的函数的具体实现是在什么地方。同样的对于组件服务器端来说,永远都是在进程内被调用,它不关心真正调用自己的函数在什么地方。而Proxy和真正的进程内COM组件的区别在于,Proxy仅仅是把调用用一种特别的方式包装好,然后通过一些进程间共享的方法传递到进程外的COM组件。然后,在COM组件端由Stub组件来把收到的东西还原成标准的调用,再去调用COM组件提供的接口函数。然后原路返回。通过这样处理后,进程间的客户端调用的过程和组件服务器端接受调用的过程可以认为和进程内调用完全一样,因为中间的通信部分可以认为是透明的。
 
三. Marshal/Unmarshal
上面说的“包装好”对应的术语就是Marshal。那本Inside COM里面翻译成调整,潘爱民翻译成列集,感觉上还是潘翻译的好一些。对应列集的,在Stub中的还原工作称为Unmarshal,潘翻译成散集。不管翻译如何,Marshal的本质工作就是把标准的函数调用用一些特殊的方法打包成一大堆数据,Unmarshal则正好相反。打包成的数据可以用任何方式在进程间传输,这依赖于操作系统的实现。
因此,进程间调用的问题其实就是这个列集和散集的实现问题了。
对于接口和函数,有IID确保问题不大,所以需要考虑的就是参数和返回值如何传输。如果参数是一个int变量之类的话,简单传输即可;如果是一个int指针之类的话,可以先分配一片内存空间,把里面的内容都复制过来,然后整体传输。这些都好办,但是如果是一个函数指针呢?
当进程间有函数指针的传输时,Marsha/Unmarshal就会变得非常复杂。解决的思想是对这个函数建立一个Proxy/Stub对,从而让这个函数能够被跨进程的调用——实际上,传递函数指针的目的不就是为了让对方能够使用这个函数么。
 
举个例子(或许不对,我还没有仔细研究过),客户端调用进程外COM组件的QueryInterface()函数查询某接口IA,那么将会如何呢?
首先注意,这个调用本身就是进程外的,是通过Proxy/Stub的。当Stub向COM组件查询到了接口IA后,这本质上就是一个指向函数指针数组的指针,单纯返回这个毫无意义。所以当QueryInterface()的Stub端把COM组件的返回结果做Marshal数据发回给Proxy端时,其实需要实现这些函数的Stub端,并把相应的信息一起发给QueryInterface()的Proxy端。QueryInterface()的Proxy端接收到了这些信息之后,在客户端内实现这些函数的Proxy端,建立它们之间的关系,然后把这些Proxy端的入口函数指针重新组成一个数组,再把这个数组指针返回给客户端。这样,当客户端需要调用返回来的接口中的某函数时,它可以直接使用,跟进程内COM组件的调用完全一样。
 
Marshal/Unmarshal是一个很复杂的东西,不过思想还是比较自然的,实现也应该比较通用。因此COM除了支持用户自己实现Marshal,也提供标准的Marshal方法供用户使用,避免用户在此浪费时间。换句话说,对于程序员来说,或许并不需要了解太多深奥的Marshal实现细节也能完美实现进程间调用,这不知道算好事还是坏事呢?

你可能感兴趣的:(COM)