V8 javascript 引擎

时间:2024-04-01 21:05:44
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。
V8 javascript 引擎
执行函数 Point 中的第一条语句(this.x = x;)会为对象 Point 创建一个新的属性 x。此时,V8 会:
  • 在 C0 的基础上创建另一个隐藏类 C1,并将属性 x 的信息添加到 C1 中:这个属性的值会被存储在距 Point 对象的偏移量为 0 的地方。
  • 在 C0 中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性 x 之后能够找到 C1 作为新的隐藏类。此时对象 Point 的隐藏类被更新为 C1。
V8 javascript 引擎
执行函数 Point 中的第二条语句(this.y = y;)会添加一个新的属性 y 到对象 Point 中。同理,此时 V8 会:
  • 在 C1 的基础上创建另一个隐藏类 C2,并在 C2 中添加关于属性 y 的信息:这个属性将被存储在内存中离 Point 对象的偏移量为 1 的地方。
  • 在 C1 中添加适当的类转移信息,使得当有另外的以其为隐藏类的对象在添加了属性 y 之后能够找到 C2 作为新的隐藏类。此时对象 Point 的隐藏类被更新为 C2。
V8 javascript 引擎
咋一看似乎每次添加一个属性都创建一个新的隐藏类非常低效。实际上,利用类转移信息,隐藏类可以被重用。下次创建一个 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 会更新所有指向这个对象的指针到新的地址。
一、写在前面的话
随着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代码之间双向调用和通信,现在把代码分享出来供大家研究和参考。
参考: