Mozilla研究—XPCOM語言無關(guān)性的實現(xiàn)原理
轉(zhuǎn)載時請注明出處和作者聯(lián)系方式:http://blog.csdn.net/absurd
作者聯(lián)系方式:Li XianJing <xianjimli at hotmail dot com>
更新時間:2007-3-8
mozilla是一個以瀏覽器為中心的軟件平臺,它在我們平臺中占有重要地位。我們用它來實現(xiàn)WEB瀏覽器、WAP瀏覽器、郵件系統(tǒng)、電子書和幫助閱讀器等應(yīng)用程序。為此,我最近花了不少時間去閱讀mozilla的代碼和文檔,我將寫一系列的BLOG作為筆記,供有需要的朋友參考。本文介紹一下XPCOM語言無關(guān)性的實現(xiàn)原理。
本章的題目看起來有點神秘,可能有的朋友已經(jīng)懷疑我在故作高深了。經(jīng)過這幾周的研究,我發(fā)現(xiàn)mozilla的復(fù)雜度超出了我的預(yù)期。當(dāng)時和falls_huang聊天時,我說我們打算選擇minimo作為瀏覽器。他說,假設(shè)客戶發(fā)現(xiàn)瀏覽器有一個BUG,他要你們在一個月內(nèi)搞定它,你認你們有這個能力嗎?我想了一下,說,mozilla里面涉及到的技術(shù)我都比較熟悉,應(yīng)該沒有問題吧。那時我對mozilla本身僅僅知道一點皮毛,這個回答只是基于以前的調(diào)試經(jīng)驗罷了。
最近發(fā)現(xiàn)minimo(基于firebox 1.5的代碼)確實存在不少BUG,要解決這些BUG還真不容易。其中XPCOM的語言無關(guān)性,對調(diào)試就是一個不小的障礙。定位問題是javascript引起的,還是C/C++組件引起的,通常比較麻煩。我想,與其頭痛醫(yī)頭腳痛醫(yī)腳,還不如系統(tǒng)的研究一下這個調(diào)用過程,所以才有這篇BLOG的誕生。
語言無關(guān)性是組件對象模型(COM)的主要特性之一。這里語言無關(guān)性有三重含義:其一是組件可以用不同的語言來實現(xiàn),比如可以用javascript,也可以用C/C++,理論上還可以用其它語言來實現(xiàn),不過本文關(guān)注的只是javascript和C/C++之間的調(diào)用。其二是同一個組件可以被不同的語言調(diào)用,從而做到跨語言的重用。其三是這種跨語言調(diào)用是透明的,調(diào)用者不知道也不必知道被調(diào)組件是用什么語言實現(xiàn)的。
如果只是要做前面兩點那并不困難,一般的語言都會支持這種機制,不用組件對象模型(COM)一樣可以實現(xiàn)。但這樣做很麻煩:調(diào)用過程序比較繁瑣。調(diào)用者要熟悉目標(biāo)語言。調(diào)用者和實現(xiàn)者之間的耦合很緊密。
本文有兩個目的:其一是弄清楚跨語言調(diào)用的實現(xiàn)原理。其二是弄清楚如何實現(xiàn)調(diào)用的透明性。盡管這里只是針對javascript和C/C++之間的調(diào)用,由于它們是解釋型和編譯型兩種類語言之間的調(diào)用,所以正好是最為典型的情況,其它的情況差不多或者更為簡單。
(在mozilla中,負責(zé)javascript和C/C++組件之間調(diào)用的模塊稱為XPConnect。)
1. javascript調(diào)用C/C++的組件。
如果研究過腳本語言的解釋器,我們就會知道:腳本語言的函數(shù)調(diào)用,它的參數(shù)不是放在解釋器自身的棧里的,而是放一個調(diào)用上下文中,以數(shù)組或者鏈表之類的形式存在的。而像C/C++等編譯型語言的函數(shù)調(diào)用,則是壓入棧里的,完全由機器的棧管理指令(PUSH /POP)去實現(xiàn)的。
要在javascript里面調(diào)用C/C++的函數(shù),關(guān)鍵是如何把腳本語言的參數(shù),從調(diào)用上下文里壓入到本機的原生(Native)棧里。有人說這很簡單啊,一個一個的往里面壓就行了。當(dāng)然沒那么簡單,這里至少存在兩個問題,第一是數(shù)據(jù)類型問題,在腳本語言中,數(shù)據(jù)類型不是很嚴格的,比如數(shù)字1,它到底是8位、16位、32位還是64位的?都有可能,我們無法知道具體是哪一種。不知道數(shù)據(jù)類型就沒有辦法壓棧(即不知道壓入的字節(jié)數(shù),也不知道數(shù)據(jù)對齊的方式)。第二是參數(shù)進棧的順序以及棧指針的維護者,這里存在多種方式,到底用哪一種也要考慮。
后者比較容易解決,只是要規(guī)定一種方式,大家都遵循就行了。前者的實現(xiàn)相對麻煩一點,為了獲得函數(shù)參數(shù)的類型,javascript的解釋器要能夠知道被調(diào)組件的精確原型,這通常都是通過一種稱為類型庫的機制來實現(xiàn)的。類型庫在XPCOM中稱為xpt,xpt實際上是與接口定義語言(IDL)等價的,只是以二進制方式存儲,更省空間,更有效率而已。在XPCOM初始化時,它遍歷所有的.xpt文件,把接口信息的索引管理起來,當(dāng)需要該接口的信息時,它再從.xpt文件中加載更詳細的信息,這樣一來,參數(shù)類型的問題就解決了。
mozilla/xpcom/reflect/xptinfo/src下的代碼負責(zé)管理接口信息??梢酝ㄟ^XPTI_GetInterfaceInfoManager得到接口信息管理器,然后通過GetInfoForIID等函數(shù),以接口ID或接口名稱,查詢到對應(yīng)接口的信息。
至于參數(shù)的壓棧,一般都是用匯編語言實現(xiàn)的,所以是與平臺相關(guān)的。該函數(shù)的名稱叫XPTC_InvokeByIndex,它的具體實現(xiàn)在mozilla/xpcom/reflect/xptcall/src/md下對應(yīng)的平臺文件中。
調(diào)用的透明性是javascript的解釋器保證的。在javascript里面,調(diào)用者像調(diào)用javascript實現(xiàn)的組件一樣調(diào)用它,而javascript解釋器負責(zé)以上的轉(zhuǎn)換。具體代碼實現(xiàn)是在xpcwrappednativeinfo.cpp的XPCNativeMember::Resolve函數(shù)里,它把Native函數(shù)包裝成JSFunction。
還有一個問題也值得注意,解釋器里通過組件管理器可以創(chuàng)建組件,但它如何由接口實例指針得到其函數(shù)指針呢?我們知道C++類的非虛成員和C語言的普通函數(shù)沒有什么差別,我們無法通過對象實例指針得到其非虛成員函數(shù)的指針。但是我們可以通過對象的虛表(vtable)查找到所要的虛函數(shù)指針,而接口函數(shù)又全部是虛函數(shù),所以從實例得到函數(shù)指針就不成問題了。
2. C/C++調(diào)用javascript的組件。
同樣,在C++里調(diào)用javascript的函數(shù),也存在參數(shù)轉(zhuǎn)換的問題。只是過程正好相反了:即把參數(shù)從棧拿出來,放到javascript的調(diào)用上下文中。
在C/C++里調(diào)用javascript的組件不需要xpt了,原因是它可以通過其它方式知道接口的精確原型。精確原型從哪里來呢?從由IDL生成的頭文件來。有了頭文件,這種調(diào)用自然就透明化了。
從頭文件的函數(shù)原型到javascript的轉(zhuǎn)換也是比較復(fù)雜的。在XPCOM中,首先是通過一組模板,把對接口的調(diào)用映射到PrepareAndDispatch函數(shù)上,該函數(shù)負責(zé)從棧中彈出參數(shù)并放到參數(shù)數(shù)組中,然后經(jīng)過nsXPCWrappedJS::CallMethod,再到javascript解釋器去執(zhí)行javascript的函數(shù)。
MS COM中跨語言的調(diào)用是通過IDispatch接口和類型庫來實現(xiàn)的,XPConnect采用的方式與之類似,但它不強制要求組件實現(xiàn)IDispatch接口,這減化了組件的開發(fā),同時也降低了可移植性(參數(shù)轉(zhuǎn)換依賴于平臺)。由此看來,可以說兩者各有長短吧。
~~end~~