V8在執行之前將java script
編譯成了
機器碼的,而非
字節碼或是
直譯它,以此提升效能。更進一步,使用了如
內聯緩存(inline caching)等方法來提高性能。有了這些功能,java script程序與V8引擎的速度媲美二進制編譯。
[4]
V8是Google Chrome瀏覽器內置的JavaScript腳本引擎。
Google Chrome使用V8的API,但引擎的內核部分是獨立於瀏覽器之外的。
V8引擎編譯和執行JavaScript源代碼。
速度是V8追求的主要設計目標之一,它把JavaScript代碼直接編譯成機器碼運行,比起傳統的“中間代碼+解釋器”的引擎,優勢不言而喻。
V8的團隊說Chrome對腳本的解析和執行速度是Firefox和Safari的10倍,是IE的56倍。
-------------------
隨着最近 AJAX 技術的興起,JavaScript 現在已經變成了實現基於 web 的應用程序(例如我們自己的 Gmail)的核心技術。JavaScript 程序從聊聊幾行變成數百 KB 的代碼。JavaScript 被設計於完成一些特定的任務,雖然 JavaScript 在做這些事情的時候通常都很高效,但是性能已經逐漸成為進一步用 JavaScript 開發復雜的基於 web 的應用程序的瓶頸。
V8 是一個全新的 JavaScript 引擎,它在設計之初就以高效地執行大型的 JavaScript 應用程序為目的。V8的JavaScript渲染引擎亮點在於更快速更強壯的JavaScript解析。V8是一個非常反傳統的JavaScript引擎,它能夠在后台動態的對JS的對象進行分類——一個在其他高級語言中很常見但JS本身不支持的特性。V8對JS的解析不是基於反復loop源代碼進行解釋而是直接將JS代碼編譯成機器碼運行。換句話說,V8引擎實際上可以看做是JS的擴展和編譯器——而傳統上類似於JS的解釋型語言恰恰是不需要編譯器的。最后,高級語言的內存管理效能一直是決定其運行效率的重要因素,而當前的JS虛擬機在這方面做的比較基本,對內存的回收也非常保守。V8使用的是非常強勢的內存管理策略,一切在運行堆棧里無用的數據都會被強行回收,從而可以大大提高JS代碼的運行效率。
在一些性能測試中,V8 比 Internet Explorer 的 JScript 、Firefox 中的 SpiderMonkey 以及 Safari 中的 JavaScriptCore 要快上數倍。如果你的 web 程序的瓶頸在於 JavaScript 的運行效率,用 V8 代替你現在的 JavaScript 引擎很可能可以提升你的程序的運行效率。具體會有多大的性能提升依賴於程序執行了多少 JavaScript 代碼以及這些代碼本身的性質。比如,如果你的程序中的函數會被反復執行很多遍的話,性能提升通常會比較大,反過來,如果代碼中有很多不同的函數並且都只會被調用一次左右,那么性能提升就不會那么明顯了。
速度是V8追求的主要設計目標之一,它把JavaScript代碼直接編譯成機器碼運行,比起傳統的“中間代碼+解釋器”的引擎,優勢不言而喻。在
SunSpider測試中,V8的
綜合表現是最好的。據說Mozilla正在開發的
TraceMonkey比V8還要
快20%左右,可惜尚未完工。
用V8解密了一把惡意網頁常用的
Base62加密,結果如下:
本文翻譯自 Google 的開源 java script 引擎 V8 的
在線文檔。其實我都沒有真正翻譯過什么東西,本來我的英文就比較一般,中文語言組織也很弱。而且許多文檔(比如這篇)基本上如果是對此感興趣的人,直接閱讀英文原文文檔肯定都是沒有問題的。不過既然突然心血來潮,就試一試吧,能力總是要鍛煉才會有的。我自己對 Language VM 比較感興趣,V8 其實並不是一個 VM ,因為它是直接編譯為本地機器碼執行的,但是也有不少相通的地方。廢話少說,下面是譯文。
Netscape Navigator 在 90 在年代中期對 java script 進行了集成,這讓網頁開發人員對 HTML 頁面中諸如 form 、frame 和 image 之類的元素的訪問變得非常容易。由此 java script 很快成為了用於定制控件和添加動畫的工具,到 90 年代后期的時候,大部分的 java script 腳本僅僅完成像“根據用戶的鼠標動作把一幅圖換成另一幅圖”這樣簡單的功能。
隨着最近 AJAX 技術的興起,java script 現在已經變成了實現基於 web 的應用程序(例如我們自己的 Gmail)的核心技術。java script 程序從聊聊幾行變成數百 KB 的代碼。java script 被設計於完成一些特定的任務,雖然 java script 在做這些事情的時候通常都很高效,但是性能已經逐漸成為進一步用 java script 開發復雜的基於 web 的應用程序的瓶頸。
V8 是一個全新的 java script 引擎,它在設計之初就以高效地執行大型的 java script 應用程序為目的。在一些
性能測試中,V8 比 Internet Explorer 的 JScript 、Firefox 中的 SpiderMonkey 以及 Safari 中的 java scriptCore 要快上數倍。如果你的 web 程序的瓶頸在於 java script 的運行效率,用 V8 代替你現在的 java script 引擎很可能可以提升你的程序的運行效率。具體會有多大的性能提升依賴於程序執行了多少 java script 代碼以及這些代碼本身的性質。比如,如果你的程序中的函數會被反復執行很多遍的話,性能提升通常會比較大,反過來,如果代碼中有很多不同的函數並且都只會被調用一次左右,那么性能提升就不會那么明顯了。其中的原因在你讀過這份文檔余下的部分之后就會明白了。
V8 的性能提升主要來自三個關鍵部分:
快速屬性訪問
java script 是一門動態語言,屬性可以在運行時添加到或從對象中刪除。這意味着對象的屬性經常會發生變化。大部分 java script 引擎都使用一個類似於字典的數據結構來存儲對象的屬性,這樣每次訪問對象的屬性都需要進行一次動態的字典查找來獲取屬性在內存中的位置。這種實現方式讓 java script 中屬性的訪問比諸如 Java 和 Smalltalk 這樣的語言中的成員變量的訪問慢了許多。成員變量在內存中的位置離對象的地址的距離是固定的,這個偏移量由編譯器在編譯的時候根據對象的類的定義決定下來。因此對成員變量的訪問只是一個簡單的內存讀取或寫入的操作,通常只需要一條指令即可。
為了減少 java script 中訪問屬性所花的時間,V8 采用了和動態查找完全不同的技術來實現屬性的訪問:動態地為對象創建隱藏類。這並不是什么新的想法,基於原型的編程語言 Self 就用 map 來實現了類似的功能(參見
An Efficient Implementation of Self, a Dynamically-Typed Object-Oriented Language Based on Prototypes )。在 V8 里,當一個新的屬性被添加到對象中時,對象所對應的隱藏類會隨之改變。
下面我們用一個簡單的 java script 函數來加以說明:
function Point(x, y) {
this.x = x;
this.y = y;}
this.x = x;
this.y = y;}
當 new Point(x, y) 執行的時候,一個新的 Point 對象會被創建出來。如果這是 Point 對象第一次被創建,V8 會為它初始化一個隱藏類,不妨稱作 C0。因為這個對象還沒有定義任何屬性,所以這個初始類是一個空類。到這個時候為止,對象 Point 的隱藏類是 C0。

執行函數 Point 中的第一條語句(this.x = x;)會為對象 Point 創建一個新的屬性 x。此時,V8 會:
- 在 C0 的基礎上創建另一個隱藏類 C1,並將屬性 x 的信息添加到 C1 中:這個屬性的值會被存儲在距 Point 對象的偏移量為 0 的地方。
- 在 C0 中添加適當的類轉移信息,使得當有另外的以其為隱藏類的對象在添加了屬性 x 之后能夠找到 C1 作為新的隱藏類。此時對象 Point 的隱藏類被更新為 C1。

執行函數 Point 中的第二條語句(this.y = y;)會添加一個新的屬性 y 到對象 Point 中。同理,此時 V8 會:
- 在 C1 的基礎上創建另一個隱藏類 C2,並在 C2 中添加關於屬性 y 的信息:這個屬性將被存儲在內存中離 Point 對象的偏移量為 1 的地方。
- 在 C1 中添加適當的類轉移信息,使得當有另外的以其為隱藏類的對象在添加了屬性 y 之后能夠找到 C2 作為新的隱藏類。此時對象 Point 的隱藏類被更新為 C2。

咋一看似乎每次添加一個屬性都創建一個新的隱藏類非常低效。實際上,利用類轉移信息,隱藏類可以被重用。下次創建一個 Point 對象的時候,就可以直接共享由最初那個 Point 對象所創建出來的隱藏類。例如,如果又一個 Point 對象被創建出來了:
- 一開始 Point 對象沒有任何屬性,它的隱藏類將會被設置為 C0。
- 當屬性 x 被添加到對象中的時候,V8 通過 C0 到 C1 的類轉移信息將對象的隱藏類更新為 C1 ,並直接將 x 的屬性值寫入到由 C1 所指定的位置(偏移量 0)。
- 當屬性 y 被添加到對象中的時候,V8 又通過 C1 到 C2 的類轉移信息將對象的隱藏類更新為 C2,並直接將 y 的屬性值寫入到由 C2 所指定的位置(偏移量 1)。
盡管 java script 比通常的面向對象的編程語言都要更加動態一些,然而大部分的 java script 程序都會表現出像上述描述的那樣的運行時高度結構重用的行為特征來。使用隱藏類主要有兩個好處:屬性訪問不再需要動態字典查找了;為 V8 使用經典的基於類的優化和內聯緩存技術創造了條件。關於內聯緩存的更多信息可以參考
Efficient Implementation of the Smalltalk-80 System 這篇論文。
動態機器碼生成
V8 在第一次執行 java script 代碼的時候會將其直接編譯為本地機器碼,而不是使用中間字節碼的形式,因此也沒有解釋器的存在。屬性訪問由內聯緩存代碼來完成,這些代碼通常會在運行時由 V8 修改為合適的機器指令。
在第一次執行到訪問某個對象的屬性的代碼時,V8 會找出對象當前的隱藏類。同時,V8 會假設在相同代碼段里的其他所有對象的屬性訪問都由這個隱藏類進行描述,並修改相應的內聯代碼讓他們直接使用這個隱藏類。當 V8 預測正確的時候,屬性值的存取僅需一條指令即可完成。如果預測失敗了,V8 會再次修改內聯代碼並移除剛才加入的內聯優化。
例如,訪問一個 Point 對象的 x 屬性的代碼如下:
point.x
在 V8 中,對應生成的機器碼如下:
; ebx = the point objectcmp [ebx, <hidden class offset>], <cached hidden class>
jne <inline cache miss>
mov eax, [ebx, <cached x offset>]
jne <inline cache miss>
mov eax, [ebx, <cached x offset>]
如果對象的隱藏類和緩存的隱藏類不一樣,執行會跳轉到 V8 運行系統中處理內聯緩存預測失敗的地方,在那里原來的內聯代碼會被修改以移除相應的內聯緩存優化。如果預測成功了,屬性 x 的值會被直接讀出來。
當有許多對象共享同一個隱藏類的時候,這樣的實現方式下屬性的訪問速度可以接近大多數動態語言。使用內聯緩存代碼和隱藏類實現屬性訪問的方式和動態代碼生成和優化的方式結合起來,讓大部分 java script 代碼的運行效率得以大幅提升。
高效的垃圾收集
V8 會自動回收不再被對象使用的內存,這個過程通常被稱為“垃圾收集(Garbage Collection)”。為了保證快速的對象分配和縮短由垃圾收集造成的停頓,並杜絕內存碎片,V8 使用了一個 stop-the-world, generational, accurate 的垃圾收集器,換句話說,V8 的垃圾收集器:
- 在執行垃圾回收的時候會中斷程序的執行。
- 大部分情況下,每個垃圾收集周期只處理整個對象堆的一部分,這讓程序中斷造成的影響得以減輕。
- 總是知道內存中所有的對象和指針所在的位置,這避免了非 accurate 的垃圾收集器中普遍存在的由於錯誤地把對象當作指針而造成的內存溢出的情況。
在 V8 中,對象堆被分成兩部分:用於為新創建的對象分配空間的部分和用於存放在垃圾收集周期中生存下來的那些老的對象的部分。如果一個對象在垃圾收集的過程中被移動了,V8 會更新所有指向這個對象的指針到新的地址。
一、寫在前面的話
隨着google io大會上對android 2.2系統展示,一個經過高度優化的android系統(從dalvik虛擬機,到瀏覽器)呈現在大家面前。開發者們會非常自然地將目光落在dalvik虛擬機方面的改進(包括ndk工具對jni聯機單步調試的支持),很多應用接口的調整以及以此為基礎的新的應用程序(偶是屬於那種喜新不厭舊,找抽性質的人)。對於android 2.2在瀏覽器方面的優化和改進,在google io大會上只提到了已經全面支持v8 javascript引擎,這種引擎會將瀏覽器的運行速度提升2-3倍(盡管firefox已經官方發表聲明說他們在未來的firefox中會使用一個叫做tracemonkey的javascript引擎,它要比v8更快,但目前來看v8引擎是所有現存javascript引擎中最快的)。
hoho,好東西嘛,自然少不了偶了,下面偶就把自己對v8引擎的一些使用方面的心得體會簡單地寫一下,希望能夠對游戲開發者或者應用程序引擎開發者有一些用處。(稍微表達一下對google的意見,雖然android 2.2已經正式發布了,但source code還沒有發布出來,偶等得花兒都謝了。)
二、v8引擎特性簡介
v8引擎的最根本的特性就是運行效率非常高,這得益於v8與眾不同的設計。
從技術角度來看,v8的設計主要有三個比較特別的地方:
(1)快速對象屬性存取機制
javascript這語言很邪門,很不規范,但是動態特性很高,甚至可以在運行時增加或減少對象的屬性,傳統的javascript引擎對於對象屬性存取機制的實現方法是——為運行中的對象建立一個屬性字典,然后每次在腳本中存取對象屬性的時候,就去查這個字典,查到了就直接存取,查不到就新建一個屬性。
如此設計雖然很方便,但是很多時間都浪費到這個“查字典”的工作上了。而v8則采取另外一種方式——hidden class(隱藏類?!偶怕翻譯得不貼切因此直接把原文寫上來了)鏈的方式,在腳本中每次為對象添加一個新的屬性的時候,就以上一個hidden class為父類,創建一個具有新屬性的hidden class的子類,如此往復遞歸進行,而且上述操作只在該對象第一次創建的時候進行一次,以后再遇到相同對象的時候,直接把最終版本的hidden class子類拿來用就是了,不用維護一個屬性字典,也不用重復創建。
這樣的設計體現了google里面天才工程師們的才華(當然第一次運行的時候肯定要慢一些,所以google那邊強調,v8引擎在多重循環,以及重復操作一些對象的時候速度改善尤為明顯,大概這種設計也是其中的一個原因吧,當然最主要的原因還在動態機器碼生成機制)
(2)動態機器碼生成機制
這一點可以類比一下java虛擬機里面的jit(just in time)機制,地球人都知道,java的運行效率很低,尤其在使用多重循環(也就是,for循環里面還有個for循環里面還有for循環*^&@*#^$。。。就當此注釋是廢話好了)的時候,sun為了解決這個問題,在jvm虛擬機里面加入了jit機制,就是在.class運行的時候,把特別耗時的多重循環編譯成機器碼(也就是跟exe或elf中保存的代碼一樣的可執行二進制代碼),然后當下次再運行的時候,就直接用這些二進制代碼跑,如此以來,自然運行效率就提高了。android 2.2在dalvik里面也已經加入了jit技術,所以會有如此大的性能提升,但是對於一個javascript引擎中引入此技術來提高腳本的運行效率,偶還是第一次看到(或許是偶孤陋寡聞了,歡迎對此有研究的朋友不吝斧正)。
這種設計在本文的下半部分,研究如何在c++程序中嵌入v8引擎、執行javascript腳本的時候,會有更加深入的理解,因為每次運行腳本之前,首先要調用compile的函數,需要對腳本進行編譯,然后才能夠運行,由此可以看到動態代碼生成機制的影響深遠。
這種設計的好處在於可以極大限度地加速javascript腳本運行,但是自然也有一些問題,那就是移植的問題,目前從v8的代碼上來看,v8已經支持ia32(也就是x86了),arm,x64(64位的,偶現在還沒那么幸運能用上64位的機器),mips(是apple們用的),其他的javascript引擎,只需要把代碼重新編譯一下,理論上就能夠在其他不同的硬件平台上跑了,但是從這個動態機器碼生成的機制來看,雖然v8很好,很強大,但是把它弄到其他的平台上似乎工作量不小。
(3)高效的垃圾回收機制
垃圾回收,從原理上來說就是對象的引用計數,當一個對象不再被腳本中其他的對象使用了,就可以由垃圾回收器(garbage collector)將其釋放到系統的堆當中,以便於下一次繼續使用。
v8采用的是stop-the-world(讓世界停止?!其實真正的意思就是在v8進行垃圾回收的時候,中斷整個腳本的執行,回收完成后再繼續執行腳本,如此以來,可以集中全部cpu之力在較短的時間內完成垃圾回收任務,在正常運行過程中堅決不回收垃圾,讓全部cpu都用來運行腳本)垃圾回收機制。從偶的英文水平來看,其他的描述,諸如:快速、正確、下一代之類的都是浮雲,stop-the-world才是根本。
以上是偶對v8設計要點和特性方面的簡單研究,英語好的朋友可以無視偶在上面的聒噪,直接看v8的design elements原文,原文的地址如下:
三、下載和編譯v8的方法
ok,既然v8引擎這么好,那么現在就開始動手,搞一個出來玩玩。與以往一樣,偶的開發環境是slackware13.1。
關於v8引擎的下載和編譯方法,英文好的朋友可以直接看google code上面的介紹,具體的鏈接地址如下:
偶在此只是簡單地把要點提一下,順便聊聊注意事項:
(1)v8可以在winxp, vista, mac os, linux(arm和intel的cpu都行)環境下編譯。
(2)基本的系統要求:
a、svn版本要大於等於1.4
b、win xp要打sp2補丁(現在最新的補丁應該是sp3了)
c、python版本要大於等於2.4
d、scons版本要大於等於1.0.0(google這幫家伙們還真能折騰,用gmake就那么費勁嗎?非要弄個怪異的編譯工具,這個scons是基於python的自動化編譯工具,功能上跟linux下面的Makefile非常類似,不一樣的是Makefile的腳本是gmake的語法,而scons的配置腳本的語法則是python,看來v8引擎的開發者們是python的鐵桿粉絲,這個scons的安裝方法偶就不再聒噪了,python install setup.sh,相信熟悉python的朋友一定非常清楚了。)
e、gcc編譯器的版本要大於4.x.x
(3)v8的下載地址:
svn checkout
http://v8.googlecode.com/svn/trunk/ v8-read-only
(4)基本的編譯方法:
a、查看v8配置腳本中參數的方法:scons --help
b、查看scons命令本身提供參數的方法:scons -H (這里的“H”一定要大寫)
c、設置環境變量:
export GCC_VERSION=44(這個一定要設置,否則會導致一大堆錯誤,天知道google guys們是如何編寫scons的配置腳本的,個人感覺他們寫這個編譯腳本的時候應該是用mac book,在leopard系統上玩的,而偶還在用價廉物美的lenovo,使用slackware。。。)
d、開始編譯,編譯的命令很簡單:scons mode=release library=shared snapshot=on
e、經過漫長的編譯過程,會看到一個叫做libv8.so的庫(當然用library=static可以編譯出libv8.a的靜態庫),把這個so庫手工拷貝到/usr/local/lib,然后,ldconfig一下就好了,然乎把v8-read-only/include目錄下的幾個.h文件拷貝到/usr/local/include目錄下。到此為止,v8引擎已經順利地安裝到了機器上。
f、經過e以后,我們可以簡單地測試一下是否能夠工作。還需要編譯一個可執行程序出來,例如——shell程序。編譯的方法非常簡單:scons sample=shell,然后就是等待即可。
好了,經過上面的過程,大家應該能夠很順利地生成libv8.so這個庫了,下一步偶開始研究如何在自己的c++代碼中調用這個庫了。
四、v8引擎的調用方法
1、基本概念
在使用v8引擎之前,必須知道三個基本概念:句柄(handle),作用域(scope),上下文環境(context,大爺的老外的這個context就是繞口,沒法翻譯成中文,可以簡單地理解為運行環境也可以)
(1)句柄(Handle)
從實質上來說,每一個句柄就是一個指向v8對象的指針,所有的v8對象必須使用句柄來操作。這是先決條件,如果一個v8對象沒有任何句柄與之相關聯,那么這個對象很快就會被垃圾回收器給干掉(句柄跟對象的引用計數有很大關系)。
(2)作用域(Scope)
從概念上理解,作用域可以看成是一個句柄的容器,在一個作用域里面可以有很多很多個句柄(也就是說,一個scope里面可以包含很多很多個v8引擎相關的對象),句柄指向的對象是可以一個一個單獨地釋放的,但是很多時候(尤其是寫一些“有用”的程序的時候),一個一個地釋放句柄過於繁瑣,取而代之的是,可以釋放一個scope,那么包含在這個scope中的所有handle就都會被統一釋放掉了。
(3)上下文環境(Context)
從概念上講,這個上下文環境(以前看一些中文的技術資料總出現這個詞,天知道當初作者們是如何想的,不過這事情就是約定俗成,大家都這么叫也就習慣了)也可以理解為運行環境。這就好比是linux的環境變量,在執行javascript腳本的時候,總要有一些環境變量或者全局函數(這些就不用偶解釋了吧?!就是那些直接拿過來就用,根本不需要關心這些變量或者函數在什么地方定義的)。偶們如果要在自己的c++代碼中嵌入v8引擎,自然希望提供一些c++編寫的函數或者模塊,讓其他用戶從腳本中直接調用,這樣才會體現出javascript的強大。從概念上來講,java開發中,有些功能jvm不提供,大家可以用c/c++編寫jni模塊,通過java調用c/c++模塊來實現那些功能。而類比到javascript引擎,偶們可以用c++編寫全局函數,讓其他人通過javascript進行調用,這樣,就無形中擴展了javascript的功能。java+jni的開發模式與javascript+c++module是一樣的思路,只是java更加復雜,系統庫更加豐富;而javascript相對java來說比較簡單,系統庫比較少。僅此而已。
2、開始在c++代碼中嵌入v8引擎
(1)基本的編譯方法
基本的編譯方法很簡單,只要上面安裝v8引擎的過程中沒有什么問題,就可以直接把v8引擎作為一個普通的動態鏈接庫來使用,例如:在編譯的時候加入-I/usr/local/include,在鏈接的時候加入-L/usr/local/lib -lv8就足夠了。這里需要提一句,由於v8引擎是完全使用c++編寫的(hoho,最近linus在blog上跟人吵架,聲稱c++是垃圾程序員使用的垃圾語言,鬧得沸沸揚揚。偶也十分喜歡c語言,但是在此不對linus的言論做任何評論,好東西嘛能用、會用就是了。)
例如:
g++ -c test.cpp -I/usr/local/include
g++ -o test test.o -L/usr/local/lib -lv8
(2)在使用v8引擎中定義的變量和函數之前,一定不要忘記導入v8的名字空間
using namespace v8;
(3)在c++程序中簡單地執行v8腳本引擎的方法如下:
// 創建scope對象,該對象銷毀后,下面的所有handle就都銷毀了
HandleScope handle_scope ;
// 創建ObjectTemplate對象,這個對象可以用來注冊c++的全局函數供給javascript調用
// 在此演示中先可以忽略
Handle<ObjectTemplate> global_templ = ObjectTemplate::New() ;
// 創建運行環境
Handle<Context> exec_context ;
// 創建javascript腳本的存儲對象,該對象存放從文件中讀取的腳本字符串
Handle<String> js_source ;
// 創建用於存放編譯后的腳本代碼的對想
Handle<Script> js_compiled ;
// 從文件中把javascript腳本讀入js_source對象
js_source = load_js(js_fname) ;
// 把c++編寫的函數注冊到全局的ObjectTemplate對象中,
// 例如,在偶的代碼中,有一個叫做set_draw_color的函數,那么這個函數在javascript腳本
// 中如果希望調用,應該叫什么名字呢?這一句——String::New("set_draw_color")就用來指定
// 在腳本中的函數名稱,FunctionTemplate用來表示在c++中的函數,利用指向函數的指針把該函數
// 封裝成函數對象。以下的幾個Set都是相同的功能,就是用來把c++函數注冊到腳本的運行環境中。
global_templ->Set(String::New("set_draw_color"),
FunctionTemplate::New(set_draw_color)) ;
global_templ->Set(String::New("draw_line"),
FunctionTemplate::New(draw_line)) ;
global_templ->Set(String::New("commit"),
FunctionTemplate::New(commit)) ;
global_templ->Set(String::New("clear"),
FunctionTemplate::New(clear)) ;
global_templ->Set(String::New("draw_bmp"),
FunctionTemplate::New(draw_bmp)) ;
// 新建執行對象,把剛剛注冊了c++函數的global_templ關聯到腳本的運行環境中去
exec_context = Context::New(NULL, global_templ) ;
// 創建運行環境的作用域,當然,言外之意,v8可以支持多個配置不同的運行環境
Context::Scope context_scope(exec_context) ;
// 注意,這里就是編譯javascript腳本的源代碼了
js_compiled = Script::Compile(js_source) ;
if(js_compiled.IsEmpty()) {
LOG("run_js, js_compiled is empty!") ;
return ;
}
// 最后這一句就是運行,執行剛剛從文件中載入以及編譯的javascript腳本了
js_compiled->Run() ;
(4)由javascript調用的c++模塊的編寫方法
以剛剛的set_draw_color這個函數為例,在javascript中的調用方法假定為:
set_draw_color(r, g, b) ;
例如:
// 設置為紅色
set_draw_color(255, 0, 0) ;
雖然調用此函數看上去非常簡單,但在c++中該如何編寫這個函數呢?該如何從javascript中得到相應的行參呢?
參見如下代碼:
static Handle<Value> set_draw_color(const Arguments & args) {
int r, g, b ;
if(args.Length() == 3) {
r = args[0]->Int32Value() ;
g = args[1]->Int32Value() ;
b = args[2]->Int32Value() ;
g_canv_ptr->SetDrawColor(r, g, b) ;
}
return Undefined() ;
}
這里的const Arguments & args就用來解決從javascript向c++傳遞參數的問題。args.Length()用來返回在javascript腳本中一共傳入了多少個參數,而Arguments類本身是重載了“[]”運算符的,因此,可以使用類似普通數組的下標的方式對參數進行存取。至於Int32Value()這類的函數,是在Handle<Value>類中有定義的,可以通過查看v8.h頭文件得到所有的Handle類型對象的定義,例如:Handle<Number>,Handle<Integer>,Handle<String>,Handle<Function>等等,總之,源碼之下了無秘密,大家可以查看源代碼得到所有問題的解答。
(5)從c++代碼中調用javascript腳本中編寫的函數的方法
javascript調用c++函數,只是實現了單方向地調用;那么如何在v8中實現雙方向的調用呢?也就是由c++代碼去調用javascript中的函數。這
一點十分有用,例如,偶可以在c++代碼中捕獲鍵盤或鼠標事件,對於這些事件的處理方法(例如:鼠標在屏幕上的坐標,鍵盤按下的鍵值),則可以把c++代碼中采集到的數據傳入腳本中定義的函數,根據腳本上定義的函數去處理,由此可以極大地加強c++代碼的靈活性。
例如,偶在javascript中定義了一個OnClick函數,作用是在鼠標點擊的地方貼一張圖片,那么偶的javascript可以這樣寫:
function OnClick(x, y) {
draw_bmp(x, y, 4) ;
commit() ;
}
先不論具體的實現細節,先看這個函數的參數,x和y,那么偶該如何從c++代碼中把鼠標點按的x和y坐標傳給OnClick函數呢?畢竟這個函數是在javascript中定義的。
具體的方法其實很簡單,前半部分與定義和調用javascript的步驟一致,只不過從js_compiled->Run(),這一句以后,還沒有完,還要繼續做下面的事情:
Handle<String> js_func_name ;
Handle<Value> js_func_val ;
Handle<Function> js_func ;
Handle<Value> argv[argc] ;
Handle<Integer> int_x ;
Handle<Integer> int_y ;
// 這一句是創建函數名對象
js_func_name = String::New("OnClick") ;
// 從全局運行環境中進行查找,看看是否存在一個叫做“OnClick”的函數
js_func_val = exec_context->Global()->Get(js_func_name) ;
if(!js_func_val->IsFunction()) {
LOG("on_click, js_func_val->IsFunction check failed!") ;
} else {
// 利用handle的強制類型轉換,把js_func_val轉換成一個函數對象
js_func = Handle<Function>::Cast(js_func_val) ;
// 初始化參數,所有數據都要定義成javascript可以識別的數據類型,例如Integer對象
// javascript中是沒有內建數據類型的(int, char, short是c/c++中的用的類型)
int_x = Integer::New(x) ;
int_y = Integer::New(y) ;
// 把這些對象放到argv數組中去
argv[0] = int_x ;
argv[1] = int_y ;
// 利用函數對象去調用該函數,當然需要傳入腳本的運行環境,以及參數個數和參數的值。
js_func->Call(exec_context->Global(), argc, argv) ;
}
ok,到此為止,偶已經把c++->javascript以及javascript->c++的雙向調用,以及參數傳遞方法講完了。
其他的v8引擎的特性還需要進一步探索和研究。
偶自己寫了一個簡單的驗證程序,該程序使用sdl庫來作為c/c++模塊的繪圖工具,然后向v8導出了若干繪圖函數(例如畫線,貼圖等函數),然后通過javascript在屏幕上可以隨心所欲地畫圖。本程序在linux下面編譯和運行通過,此驗證效果還不錯,包含了v8引擎的c++和javascript代碼之間雙向調用和通信,現在把代碼分享出來供大家研究和參考。
參考: