V8 javascript 引擎

V8是一個由 丹麥 Google開發的 開源 java script引擎,用於 Google Chrome中。 [2] Lars Bak是這個項目的組長。 [3]
 
V8在執行以前將java script 編譯成了 機器碼的,而非 字節碼或是 直譯它,以此提高效能。更進一步,使用瞭如 內聯緩存(inline caching)等方法來提升性能。有了這些功能,java script程序與V8引擎的速度媲美二進制編譯。 [4]
 
V8 組譯器是基於 Strongtalk組譯器。 [5]
 
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 代碼以及這些代碼自己的性質。好比,若是你的程序中的函數會被反覆執行不少遍的話,性能提高一般會比較大,反過來,若是代碼中有不少不一樣的函數而且都只會被調用一次左右,那麼性能提高就不會那麼明顯了。
 
和Mozilla的 SpiderMonkey同樣, Google Chrome瀏覽器的JavaScript引擎 Google V8也是一個開源的獨立引擎,可內嵌於任何C++工程之中。
    速度是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;}
 
當 new Point(x, y) 執行的時候,一個新的 Point 對象會被建立出來。若是這是 Point 對象第一次被建立,V8 會爲它初始化一個隱藏類,不妨稱做 C0。由於這個對象尚未定義任何屬性,因此這個初始類是一個空類。到這個時候爲止,對象 Point 的隱藏類是 C0。
 
map_trans_a
執行函數 Point 中的第一條語句(this.x = x;)會爲對象 Point 建立一個新的屬性 x。此時,V8 會:
  • 在 C0 的基礎上建立另外一個隱藏類 C1,並將屬性 x 的信息添加到 C1 中:這個屬性的值會被存儲在距 Point 對象的偏移量爲 0 的地方。
  • 在 C0 中添加適當的類轉移信息,使得當有另外的以其爲隱藏類的對象在添加了屬性 x 以後可以找到 C1 做爲新的隱藏類。此時對象 Point 的隱藏類被更新爲 C1。
map_trans_b
執行函數 Point 中的第二條語句(this.y = y;)會添加一個新的屬性 y 到對象 Point 中。同理,此時 V8 會:
  • 在 C1 的基礎上建立另外一個隱藏類 C2,並在 C2 中添加關於屬性 y 的信息:這個屬性將被存儲在內存中離 Point 對象的偏移量爲 1 的地方。
  • 在 C1 中添加適當的類轉移信息,使得當有另外的以其爲隱藏類的對象在添加了屬性 y 以後可以找到 C2 做爲新的隱藏類。此時對象 Point 的隱藏類被更新爲 C2。
map_trans_c
咋一看彷佛每次添加一個屬性都建立一個新的隱藏類很是低效。實際上,利用類轉移信息,隱藏類能夠被重用。下次建立一個 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>]
 
若是對象的隱藏類和緩存的隱藏類不同,執行會跳轉到 V8 運行系統中處理內聯緩存預測失敗的地方,在那裏原來的內聯代碼會被修改以移除相應的內聯緩存優化。若是預測成功了,屬性 x 的值會被直接讀出來。
 
當有許多對象共享同一個隱藏類的時候,這樣的實現方式下屬性的訪問速度能夠接近大多數動態語言。使用內聯緩存代碼和隱藏類實現屬性訪問的方式和動態代碼生成和優化的方式結合起來,讓大部分 java script 代碼的運行效率得以大幅提高。
 
高效的垃圾收集
 
V8 會自動回收再也不被對象使用的內存,這個過程一般被稱爲「垃圾收集(Garbage Collection)」。爲了保證快速的對象分配和縮短由垃圾收集形成的停頓,並杜絕內存碎片,V8 使用了一個 stop-the-world, generational, accurate 的垃圾收集器,換句話說,V8 的垃圾收集器:
  • 在執行垃圾回收的時候會中斷程序的執行。
  • 大部分狀況下,每一個垃圾收集週期只處理整個對象堆的一部分,這讓程序中斷形成的影響得以減輕。
  • 老是知道內存中全部的對象和指針所在的位置,這避免了非 accurate 的垃圾收集器中廣泛存在的因爲錯誤地把對象看成指針而形成的內存溢出的狀況。
在 V8 中,對象堆被分紅兩部分:用於爲新建立的對象分配空間的部分和用於存放在垃圾收集週期中生存下來的那些老的對象的部分。若是一個對象在垃圾收集的過程當中被移動了,V8 會更新全部指向這個對象的指針到新的地址。
 
1、寫在前面的話
隨着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尚未發佈出來,偶等得花兒都謝了。)
 
2、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原文,原文的地址以下:
 
3、下載和編譯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++代碼中調用這個庫了。
 
4、v8引擎的調用方法
一、基本概念
在使用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來講比較簡單,系統庫比較少。僅此而已。
 
二、開始在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代碼之間雙向調用和通訊,如今把代碼分享出來供你們研究和參考。
 
參考:
相關文章
相關標籤/搜索