V8 JavaScript引擎研究(四)在应用程序中使用V8
V8引擎在C++程序中使用简介
V8引擎可以被嵌入在任何C++程序中使用。
V8的APIs提供了对JavaScript代码的编译和执行功能、与C++函数互掉、访问数据结构、错误处理、及安全检查等功能。在应用程序中可将V8当做一个C++库来使用,访问V8的APIs需要包含V8的头文件v8.h。
使用V8非常简单,首先看一个简单示例程序:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "include/libplatform/libplatform.h" #include "include/v8.h" using namespace v8; class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { public: virtual void* Allocate(size_t length) { void* data = AllocateUninitialized(length); return data == NULL ? data : memset(data, 0, length); } virtual void* AllocateUninitialized(size_t length) { return malloc(length); } virtual void Free(void* data, size_t) { free(data); } }; int main(int argc, char* argv[]) { // 初始化V8 V8::InitializeICU(); V8::InitializeExternalStartupData(argv[0]); Platform* platform = platform::CreateDefaultPlatform(); V8::InitializePlatform(platform); V8::Initialize(); // 创建一个新的Isolate ArrayBufferAllocator allocator; Isolate::CreateParams create_params; create_params.array_buffer_allocator = &allocator; Isolate* isolate = Isolate::New(create_params); { Isolate::Scope isolate_scope(isolate); // 创建一个分配在栈上的handle scope. HandleScope handle_scope(isolate); // 创建一个context. Local<Context> context = Context::New(isolate); // 关联context Context::Scope context_scope(context); // 创建一个包含JavaScript代码的字符串 Local<String> source = String::NewFromUtf8(isolate, "'Hello' + ', World!'", NewStringType::kNormal).ToLocalChecked(); // 编译JavaScript代码 Local<Script> script = Script::Compile(context, source).ToLocalChecked(); // 运行代码并产生结果 Local<Value> result = script->Run(context).ToLocalChecked(); String::Utf8Value utf8(result); printf("%s\n", *utf8); } // 释放资源 isolate->Dispose(); V8::Dispose(); V8::ShutdownPlatform(); delete platform; return 0; }
- 一个isolate代表一个堆上的虚拟机实例。
- 一个handle是指向一个对象的指针,所有的V8对象都通过handle来访问,这是为了便于垃圾回收器工作。
- 一个handle scope可以想象成是一系列handle的容器,当结束使用handle后,无需依次删除每个handle,可直接简单的删除scope即可。
- 一个context代表一个执行环境上下文,允许单独的、无关的JavaScript代码运行在一个V8实例中。可以指定JavaScript代码运行在任何context中。
Handles简介
一个handle提供了一个对堆上对象地址的引用,V8垃圾回收器通过对释放不再使用的对象来达到回收内存的作用。在垃圾回收周期期间,对象的位置可能发生移动,当一个对象发生移动后,所有指向它的handle都会被更新。
Handle的分类
分配在栈上的局部handle
生命周期由其所在的handle scope决定,当handle scope被删除后,垃圾回收器将会回收其中的所有handle。这类handle典型使用在一个函数中。
可以像这样声明一个局部对象类:Local<Class>。
*注意:一个handle的栈不是C++调用栈的一部分,但handle scope是嵌入在C++调用栈中的。Handle scope只能通过栈内存来分配,不能使用new操作来分配。
分配在堆上的持久handle
当需要不仅是在局部保持对一个对象的引用时,使用堆handle。如在Chrome浏览器中,DOM节点都是使用堆handle。
可以像这样声明持久对象:Persistent<Class>。
Contexts简介
一个context代表一个执行环境上下文,允许单独的、无关的JavaScript代码运行在一个V8实例中。可以指定JavaScript代码运行在任何context中。
context机制的引入是非常必要的,JavaScript提供了非常多的内建函数和对象,由于这些函数和对象可以在JavaScript代码执行时被改变,因此如果不提供一个单独的执行环境context,将会产生许多问题。如两个完全不相关的函数都修改了同一个内建的对象。
每次创建一个context都需要创建一系列的内建对象,这将产生很大的消耗。V8使用了缓存机制来解决这一问题,当创建完第一个context后,后续再需创建context时,代价非常小。V8同时使用了snapshot机制(可通过命令 snapshot=yes开启,默认开启)来高度优化第一次创建context所带来的消耗。
当一个context被创建后,可以多次进出此context。例如,当在context A中时,进入了context B,此时当前的context更新为B,当退出B时,当前的context又被转回A,见下图所示:
在V8中使用context的绝佳实例是,每个浏览器中的每个iframe都使用一个单独的context。
Templates简介
Template被用来封装C++函数和对象来给JavaScript对象使用。例如,Chrome封装了C++中的DOM节点作为JavaScript对象。
Template分为函数template和对象template。
可以像下面这样的方式来使用template:
Local<ObjectTemplate> global = ObjectTemplate::New(isolate); global->Set(String::NewFromUtf8(isolate, "log"), FunctionTemplate::New(isolate, LogCallback));
V8引擎的扩展和绑定
当JavaScript引擎所提供的能力不能满足业务需求时,如引擎本身对HTML5的支持程度不够,这时应用程序可以扩展V8引擎的能力。V8提供两种方式来扩展引擎:动态的扩展机制以及静态的绑定机制。
V8扩展
V8通过提供一个基类Extension和一个全局注册函数,来扩展JavaScript借口。
示例代码:
class MyExtension : public v8::Extension { public: virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(v8::Handle<v8::String> name) { // 可根据name来返回不同的函数 return v8::FunctionTemplate::New(MyExtension::MyFunc); } static v8::Handle<v8::Value> MyFunc(const v8::Arguments& args) { // 自定义处理 return v8::Undefined(); } }; MyExtension extension; RegisterExtension(&extension);
首先创建基于Extension基类的子类,然后实现GetNativeFunction。接着将子类的实例注册到V8引擎中,这样,当JavaScript代码中调用MyFunc时,就能执行自定义的MyFunc函数。
整个过程非常简单。
V8绑定
绑定的原理非常简单,即使用IDL文件或接口文件来生成绑定文件,然后将这些文件与V8引擎一起编译。
一个简单的IDL文件如下:
module mymodule { interface [ InterfaceName=MyObject ] MyObj { readonly attribute long myAttr; DOMString myMethod(DOMString myArg); } }
完成好IDL文件后,可以使用WebKit的工具来通过将IDL文件转换成V8的绑定文件。V8的绑定文件即是标准的C++文件,包含了在IDL文件中定义的函数和对象。当绑定文件连同V8一起编译后,即可在JavaScript代码中直接使用自定义的函数和对象。