Mozilla研究—XPCOM语言无关性的实现原理
Mozilla研究—XPCOM语言无关性的实现原理
转载时请注明出处和作者联系方式:http://blog.csdn.net/absurd
作者联系方式:Li XianJing <xianjimli at hotmail dot com>
更新时间:2007-3-8
mozilla是一个以浏览器为中心的软件平台,它在我们平台中占有重要地位。我们用它来实现WEB浏览器、WAP浏览器、邮件系统、电子书和帮助阅读器等应用程序。为此,我最近花了不少时间去阅读mozilla的代码和文档,我将写一系列的BLOG作为笔记,供有需要的朋友参考。本文介绍一下XPCOM语言无关性的实现原理。
本章的题目看起来有点神秘,可能有的朋友已经怀疑我在故作高深了。经过这几周的研究,我发现mozilla的复杂度超出了我的预期。当时和falls_huang聊天时,我说我们打算选择minimo作为浏览器。他说,假设客户发现浏览器有一个BUG,他要你们在一个月内搞定它,你认你们有这个能力吗?我想了一下,说,mozilla里面涉及到的技术我都比较熟悉,应该没有问题吧。那时我对mozilla本身仅仅知道一点皮毛,这个回答只是基于以前的调试经验罢了。
最近发现minimo(基于firebox 1.5的代码)确实存在不少BUG,要解决这些BUG还真不容易。其中XPCOM的语言无关性,对调试就是一个不小的障碍。定位问题是javascript引起的,还是C/C++组件引起的,通常比较麻烦。我想,与其头痛医头脚痛医脚,还不如系统的研究一下这个调用过程,所以才有这篇BLOG的诞生。
语言无关性是组件对象模型(COM)的主要特性之一。这里语言无关性有三重含义:其一是组件可以用不同的语言来实现,比如可以用javascript,也可以用C/C++,理论上还可以用其它语言来实现,不过本文关注的只是javascript和C/C++之间的调用。其二是同一个组件可以被不同的语言调用,从而做到跨语言的重用。其三是这种跨语言调用是透明的,调用者不知道也不必知道被调组件是用什么语言实现的。
如果只是要做前面两点那并不困难,一般的语言都会支持这种机制,不用组件对象模型(COM)一样可以实现。但这样做很麻烦:调用过程序比较繁琐。调用者要熟悉目标语言。调用者和实现者之间的耦合很紧密。
本文有两个目的:其一是弄清楚跨语言调用的实现原理。其二是弄清楚如何实现调用的透明性。尽管这里只是针对javascript和C/C++之间的调用,由于它们是解释型和编译型两种类语言之间的调用,所以正好是最为典型的情况,其它的情况差不多或者更为简单。
(在mozilla中,负责javascript和C/C++组件之间调用的模块称为XPConnect。)
1. javascript调用C/C++的组件。
如果研究过脚本语言的解释器,我们就会知道:脚本语言的函数调用,它的参数不是放在解释器自身的栈里的,而是放一个调用上下文中,以数组或者链表之类的形式存在的。而像C/C++等编译型语言的函数调用,则是压入栈里的,完全由机器的栈管理指令(PUSH /POP)去实现的。
要在javascript里面调用C/C++的函数,关键是如何把脚本语言的参数,从调用上下文里压入到本机的原生(Native)栈里。有人说这很简单啊,一个一个的往里面压就行了。当然没那么简单,这里至少存在两个问题,第一是数据类型问题,在脚本语言中,数据类型不是很严格的,比如数字1,它到底是8位、16位、32位还是64位的?都有可能,我们无法知道具体是哪一种。不知道数据类型就没有办法压栈(即不知道压入的字节数,也不知道数据对齐的方式)。第二是参数进栈的顺序以及栈指针的维护者,这里存在多种方式,到底用哪一种也要考虑。
后者比较容易解决,只是要规定一种方式,大家都遵循就行了。前者的实现相对麻烦一点,为了获得函数参数的类型,javascript的解释器要能够知道被调组件的精确原型,这通常都是通过一种称为类型库的机制来实现的。类型库在XPCOM中称为xpt,xpt实际上是与接口定义语言(IDL)等价的,只是以二进制方式存储,更省空间,更有效率而已。在XPCOM初始化时,它遍历所有的.xpt文件,把接口信息的索引管理起来,当需要该接口的信息时,它再从.xpt文件中加载更详细的信息,这样一来,参数类型的问题就解决了。
mozilla/xpcom/reflect/xptinfo/src下的代码负责管理接口信息。可以通过XPTI_GetInterfaceInfoManager得到接口信息管理器,然后通过GetInfoForIID等函数,以接口ID或接口名称,查询到对应接口的信息。
至于参数的压栈,一般都是用汇编语言实现的,所以是与平台相关的。该函数的名称叫XPTC_InvokeByIndex,它的具体实现在mozilla/xpcom/reflect/xptcall/src/md下对应的平台文件中。
调用的透明性是javascript的解释器保证的。在javascript里面,调用者像调用javascript实现的组件一样调用它,而javascript解释器负责以上的转换。具体代码实现是在xpcwrappednativeinfo.cpp的XPCNativeMember::Resolve函数里,它把Native函数包装成JSFunction。
还有一个问题也值得注意,解释器里通过组件管理器可以创建组件,但它如何由接口实例指针得到其函数指针呢?我们知道C++类的非虚成员和C语言的普通函数没有什么差别,我们无法通过对象实例指针得到其非虚成员函数的指针。但是我们可以通过对象的虚表(vtable)查找到所要的虚函数指针,而接口函数又全部是虚函数,所以从实例得到函数指针就不成问题了。
2. C/C++调用javascript的组件。
同样,在C++里调用javascript的函数,也存在参数转换的问题。只是过程正好相反了:即把参数从栈拿出来,放到javascript的调用上下文中。
在C/C++里调用javascript的组件不需要xpt了,原因是它可以通过其它方式知道接口的精确原型。精确原型从哪里来呢?从由IDL生成的头文件来。有了头文件,这种调用自然就透明化了。
从头文件的函数原型到javascript的转换也是比较复杂的。在XPCOM中,首先是通过一组模板,把对接口的调用映射到PrepareAndDispatch函数上,该函数负责从栈中弹出参数并放到参数数组中,然后经过nsXPCWrappedJS::CallMethod,再到javascript解释器去执行javascript的函数。
MS COM中跨语言的调用是通过IDispatch接口和类型库来实现的,XPConnect采用的方式与之类似,但它不强制要求组件实现IDispatch接口,这减化了组件的开发,同时也降低了可移植性(参数转换依赖于平台)。由此看来,可以说两者各有长短吧。
~~end~~