您的位置:首页 > 编程语言 > C语言/C++

在C++中嵌入V8

2015-06-10 10:49 316 查看

Handle和垃圾收集

handle在v8中,用于记录js对象在堆中的位置。v8的垃圾收集器在收集那些不可到达的内存时,会对堆进行整理。它会移动对象的位置,以达到优化内存的目的。

当v8的垃圾收集器移动js对象的位置时,它会同时更新handle的值,让handle能够指向对象新的位置。

v 8中有两种handle

本地handle (LocalHandle) 。 这种handle存在于堆栈之上,并且在析构函数被调用时释放js对象。它的生命周期取决于handle scope, handle scope通常在函数调用的开始位置声明。当handle scope 被删除,垃圾收集器将回收被这些handle指向的对象。

Local Handle通过类 Handle<SomeType>来管理。

注意:handle stack不同于c++的call
stack。但是handle scope可以嵌入到c++的stack中。Handle scope只能是作为栈变量,不同通过new方法分配。

持久Handle (Persistent Handle)。持久Handle不再栈上分配,而且需要手动删除。同本地handle一样,持久handle也引用一个堆上分配的对象。当您希望在多个函数中调用一个对象,或者handle的生命周期超出c++的作用域的时候,就需要用持久handle了。

比如,google chrome就会使用持久handle保持一个DOM节点。创建一个支持hnandle,需要使用Persisten类的构造,删除则通过Persistent::Dispose。

通过Persistent::MakeWeak,持久handle可以被标记为 weak。

持久Handle使用 Persistent<SomeType>。

当你需要创建很多handle的时候,就需要用到handle scope。handle scope是一个handle容器。当handle scope被删除时,handle scope管理的所有handle都会被删除。

注意 Context::New()返回的是一个本地handle,我们为这个本地handle创建了一个持久handle来演示持久handle的用法。

当HandleScope::~HandleScope调用时,如果没有其他的引用,handle scope内的handle将在下次垃圾收集时被移除。

对象source_obj和script_obj将在他们不再被其他任何handle引用,也不会被javascript中的代码引用时被垃圾收集器回收。

但是context是一个持久handle,它将不会被回收,除非主动调用它的Dispose函数。

这里需要注意一个陷阱:你不能从一个声明了handle scope的函数中直接返回一个本地handle。这种情况下,在你返回local handle之前,handle scope就会将该handle删除。

正确的方式,是使用HandleScope::Close方法来返回。如下例:

// This function returns a new array with three elements, x, y, and z.

Handle<Array> NewPointArray(int x, int y, int z) {

v8::Isolate* isolate = v8::Isolate::GetCurrent();

// We will be creating temporary handles so we use a handle scope.

HandleScope handle_scope(isolate); //声明了HandleScope了

// Create a new empty array.

Handle<Array> array = Array::New(3);

// Return an empty result if there was an error creating the array.

if (array.IsEmpty())

return Handle<Array>();

// Fill out the values

array->Set(0, Integer::New(x));

array->Set(1, Integer::New(y));

array->Set(2, Integer::New(z));

// Return the value through Close.

return handle_scope.Close(array); //这样才能正确返回一个本地handle

}

Contexts

在v8中, context是一个分离的、独立的javascript执行环境。当你运行一段js代码时,你必须明确给出一个context。

为什么这是必须的?因为javascript提供了一组内建的函数和对象,这些都可以被javascript代码改变。如果两个完全不相关的javascript代码更改了全局的对象,就会造成不可预知的结果。

在CPU时间和内存角度看,创建一个执行环节似乎是一件很耗费时间和资源的事情,但是,v8通过广泛的缓存策略,仅让你第一次创建context时花销会比较大,后面再创建任意多的context的花销就很小了。

当第一次创建时,v8必须创建内建对象,并解析内建js代码。而后续context只需要创建他们自己的内建对象。

如果v8设置上了snapshot特性(build选项 snapshot=yes,这是默认值),第一次所花费的开销将大幅被优化。

当你创建了一个context后,你可以进入退出它任意多次。当你在context A的时候,你可以进入一个不同的context,B。这时,context B将取代A成为你的当前Context。 当你退出context B的时候,A就自动成为你的当前context:

注意:不同context内的内建函数和对象是相互分离的。

templates

一个template是javascript函数的蓝图。你可以使用一个template来将c++函数和结构体包装到javascript对象中,让javascirpt脚本来使用它。例如,google chrome使用template来包装c++ DOM节点为js对象,并提供全局访问函数。

你也可以创建一组template让所有的context来共享使用。你可以创建任意多的template。但是每个template在context中只有一个实例。

在javascript中,function和object有很大的重叠。在java和c++中,我们通过定义一个类来创建一个新的类型,而在javascirpt中,我们通过创建一个新的function,然后用这个function作为构造函数来创建一个实例。javascript对象的内存组织方式和功能都非常接近于创建它的函数。这影响到了v8 template的工作方法。

有两种template:

function template。这是创建一个独立函数的模板。创建一个javascript的template实例,需要通过template的GetFunction方法。你也可以给这个function template关联一个回调函数。当javascript函数实例调用时,这个回调会被访问。

object template。每个function template都会有一个关联的object template。这是为关联创建该对象的函数。你可以为object template关联两个类型的回调:

accessor callbacks : 当脚本访问特定对象的属性时调用;
interceptor callbacks 当脚本访问任意对象属性时调用;

下面的代码战士了如何使用template为全局对象增加内建函数的方法
// Create a template for the global object and set the

// built-in global functions.

Handle<ObjectTemplate> global = ObjectTemplate::New();

global->Set(String::New("log"), FunctionTemplate::New(LogCallback));

// Each processor gets its own context so different processors

// do not affect each other.

Persistent<Context> context = Context::New(NULL, global);

Accessors

一个accessor是一个c++的回调函数。当javascript脚本方位一个对象属性时,它被调用并返回一个值。

accessor通过object template来设置。使用SetAccessor方法。该方法需要提供一个属性名和关联的两个回调(读写)作为参数。



访问全局静态变量

假设有两个C++整型变量,x和y,被javascript当做全局变量来使用。要实现它,javascript需要调用C++的accessor函数。这些accessor函数通过Integer::New将C++整型转换为javascript整数;通过Int32Value将javascript整数转为c++整数。例子如下:

Handle<Value> XGetter(Local<String> property,

const AccessorInfo& info) {

return Integer::New(x);

}

void XSetter(Local<String> property, Local<Value> value,

const AccessorInfo& info) {

x = value->Int32Value();

}

// YGetter/YSetter are so similar they are omitted for brevity

Handle<ObjectTemplate> global_templ = ObjectTemplate::New();

global_templ->SetAccessor(String::New("x"), XGetter, XSetter);

global_templ->SetAccessor(String::New("y"), YGetter, YSetter);

Persistent<Context> context = Context::New(NULL, global_templ);

注意:object template和context是同时创建的。object template可以被一次创建并被多个context使用。

访问动态变量

假设x和y是C++类Point的成员变量:

class Point {

public:

Point(int x, int y) : x_(x), y_(y) { }

int x_, y_;

}

如果让C++的point实例可以被javascript使用,我们需要为每个C++ point创建一个javascript对象,并且在javascript对象和c++对象之间建立关联。

第一步,创建针对Point对象的object template

Handle<ObjectTemplate> point_templ = ObjectTemplate::New();

javascript的point对象通过内部field来引用一个c++对象。 这个内部对象只能通过C++代码访问,不能通过javascript访问。 一个对象可以拥有任意多的内部field,它的数量通过如下函数设置:

point_templ->SetInternalFieldCount(1);

这里,我们设置了一个长度为1的内部field,它的索引从0开始。
然后我们可以添加x和y的访问子:

point_templ.SetAccessor(String::New("x"), GetPointX, SetPointX);

point_templ.SetAccessor(String::New("y"), GetPointY, SetPointY);

下面,要将一个C++的point实例和javascript的代码关联。我们通过External对象来作为中介。因为javascript不知道如何处理一个c++对象。

Point* p = ...;

Local<Object> obj = point_templ->NewInstance();

obj->SetInternalField(0, External::New(p));

External实际上是对一个void* 只针的封装。

下面,我们就可以实现对x和y的get和set访问了:

Handle<Value> GetPointX(Local<String> property,

const AccessorInfo &info) {

Local<Object> self = info.Holder();

Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));

void* ptr = wrap->Value();

int value = static_cast<Point*>(ptr)->x_;

return Integer::New(value);

}

void SetPointX(Local<String> property, Local<Value> value,

const AccessorInfo& info) {

Local<Object> self = info.Holder();

Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));

void* ptr = wrap->Value();

static_cast<Point*>(ptr)->x_ = value->Int32Value();

}

Interceptors

有两种Interceptor,

命名属性interceptor - 通过属性的字符串名来访问,如: document.theFormName.elementName
索引属性interceptor - 通过索引访问的属性,如 document.forms.elements[0];

下面是SetNamedPropertyHandler中关于MapGet和MapSet的interceptor部分代码

Handle<ObjectTemplate> result = ObjectTemplate::New();

result->SetNamedPropertyHandler(MapGet, MapSet);

MapGet的interceptor如下

Handle<Value> JsHttpRequestProcessor::MapGet(Local<String> name,

const AccessorInfo &info) {

// Fetch the map wrapped by this object.

map<string, string> *obj = UnwrapMap(info.Holder());

// Convert the JavaScript string to a std::string.

string key = ObjectToString(name);

// Look up the value if it exists using the standard STL idiom.

map<string, string>::iterator iter = obj->find(key);

// If the key is not present return an empty handle as signal.

if (iter == obj->end()) return Handle<Value>();

// Otherwise fetch the value and wrap it in a JavaScript string.

const string &value = (*iter).second;

return String::New(value.c_str(), value.length());

}

安全模型

”同源“策略阻止不同源内的数据相互访问。

v8通过一个context定义一个”源“。默认情况下,跨context访问是不允许的。为了访问其他context,你需要一个安全token或者安全回调。

安全token可以是任意值,但是通常会使一个符号,一个全局唯一的字符串。你也可以通过SetSecurityToken来得到一个特殊的安全token。如果你不指定的话,v8会在你创建context时自动生成一个。

当一个对全局变量访问产生时,v8的安全系统首先检测全局对象的安全token和试图访问它的代码的安全token是否一致,如果token一致,那么访问将被授权。

如果token不一致,那么v8将通过一个回调来判断是否允许访问。

你可以通过SetAccessCheckCallbacks方法来设置这样一个回调。

异常

当有错误发生时,v8将抛出异常。如,当脚本或者函数试图访问一个不存在的属性时,或者对一个非函数进行调用时。

当操作不成功时,v8会返回一个空的handle。你的代码需要检查v8的返回值是否为空。这一点很重要。 通过函数IsEmpty()

你可以通过TryCatch来捕捉异常,如

TryCatch trycatch;

Handle<Value> v = script->Run();

if (v.IsEmpty()) {

Handle<Value> exception = trycatch.Exception();

String::AsciiValue exception_str(exception);

printf("Exception: %s\n", *exception_str);

// ...

}

如果value返回为空,而且你没有一个TryCatch对象,那么你的代码会跳出。如果有TryCatch,异常就会被捕捉,你的代码会继续进行。

继承

javascript是一种无类别的面向对象语言,它使用原型继承来代替类继承。这让习惯传统面向对象语言的程序员困惑。

基于类的面对对象语言,如java, C++,是建筑在对类和对象严格区分的基础上的。javascript是一种基于原型的语言,因而没有这么严格的区分,它只有简单的对象。

javascript不能直接支持类继承,但是javacript的原型机制可以很简单的的实现自定义属性和方法的添加。在javascript中,你可以自定义对象的属性,如

// Create an object "bicycle"

function bicycle(){

}

// Create an instance of bicycle called roadbike

var roadbike = new bicycle()

// Define a custom property, wheels, on roadbike

roadbike.wheels = 2

这个方法添加到了已经存在的对象实例上。

加入我创建另外一个bycycle的实例,如mountainbike,那么mountainbike.wheels将返回undefined,除非你用同样的方法添加它。

有时,很需要这种需求。希望为每个bycycle对象的实例头添加相同的书写。这是javascript原型对象有用的地方。使用原型对象,通过关键字prototype

// First, create the "bicycle" object

function bicycle(){

}

// Assign the wheels property to the object's prototype

bicycle.prototype.wheels = 2

这样所有的bicycle()实例都有了wheels属性。

同样的场景,在V8中通过template实现。每个FunctionTemplate都有一个PrototypeTemplate方法,可以设置function的原型。你可以设置属性、绑定c++函数等,这些都通过PrototypeTemplate作用到所有的实例上。如

Handle<FunctionTemplate> biketemplate = FunctionTemplate::New();

biketemplate->PrototypeTemplate().Set(

String::New("wheels"),

FunctionTemplate::New(MyWheelsMethodCallback)->GetFunction();

)

这将使所有的
biketemplate
实例都有wheels方法。当实例的wheels被调用时,C++函数MyWheelsMethodCallback就会被调用。

V8的FunctionTemplate类还提供了一个公共方法Inhert(),你可以通过它来继承任意的FunctionTemplate,如

void Inherit(Handle<FunctionTemplate> parent);

数组对象解析

Handle<Object> self = args.Holder();

Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));

void* ptr = wrap->Value();

CloudApp* cloudapp = static_cast<CloudApp*>(ptr);

if ( args[0]->IsArray() )

{

int i = args.Length();

i = 0;

}

Handle<Array> arr = Handle<Array>::Cast(args[0]);

int size = arr->Length();

CloudApp app[2];

for (int i = 0; i < size; i++ )

{

Handle<Value> va = arr->Get(Integer::New(i));

Handle<External> field = Handle<External>::Cast(va->ToObject()->GetInternalField(0));

void *obj_ptr = field->Value();

CloudApp *p = static_cast<CloudApp*>(obj_ptr);

app[i] = *p;

}

Handle & HandleScope

#include <v8.h>

using namespace v8;

int main(int argc, char* argv[]) {

// Create a stack-allocated handle scope.

HandleScope handle_scope;

// Create a new context.

Persistent<Context> context = Context::New();

// Enter the created context for compiling and

// running the hello world script.

Context::Scope context_scope(context);

// Create a string containing the JavaScript source code.

Handle<String> source = String::New("'Hello' + ', World!'");

// Compile the source code.

Handle<Script> script = Script::Compile(source);

// Run the script to get the result.

Handle<Value> result = script->Run();

// Dispose the persistent context.

context.Dispose();

// Convert the result to an ASCII string and print it.

String::AsciiValue ascii(result);

printf("%s\n", *ascii);

return 0;

}

Handle

在V8中,内存分配都是在V8的Heap中进行分配的,JavaScript的值和对象也都存放在V8的Heap中。这个Heap由V8独立的去维护,失去引用的对象将会被V8的GC掉并可以重新分配给其他对象。而Handle即是对Heap中对象的引用。V8为了对内存分配进行管理,GC需要对V8中的所有对象进行跟踪,而对象都是用Handle方式引用的,所以GC需要对Handle进行管理,这样GC就能知道Heap中一个对象的引用情况,当一个对象的Handle引用为发生改变的时候,GC即可对该对象进行回收(gc)或者移动。因此,V8编程中必须使用Handle去引用一个对象,而不是直接通过C++的方式去获取对象的引用,直接通过C++的方式去直接去引用一个对象,会使得该对象无法被V8管理。

Handle分为Local和Persistent两种。从字面上就能知道,Local是局部的,它同时被HandleScope进行管理。persistent,类似与全局的,不受HandleScope的管理,其作用域可以延伸到不同的函数,而Local是局部的,作用域比较小。Persistent Handle对象需要Persistent::New, Persistent::Dispose配对使用,类似于C++中new和delete.Persistent::MakeWeak可以用来弱化一个Persistent
Handle,如果一个对象的唯一引用Handle是一个Persistent,则可以使用MakeWeak方法来如果该引用,该方法可以出发GC对被引用对象的回收。

HandleScope

一个函数中,可以有很多Handle,而HandleScope则相当于用来装Handle(Local)的容器,当HandleScope生命周期结束的时候,Handle也将会被释放,会引起Heap中对象引用的更新。HandleScope是分配在栈上,不能通过New的方式进行创建。对于同一个作用域内可以有多个HandleScope,新的HandleScope将会覆盖上一个HandleScope,并对Local Handle进行管理。下面通过代码来讲解HandleScope的生命周期:

#include <v8.h>

using namespace v8;

int main(int argc, char* argv[]) {

// Create a stack-allocated handle scope.

HandleScope handle_scope;

// >>>>>>>>>>>>>>>>>>>>>>>>从这里开始,是HandleScope的生命周期的开始

// 从此之后的所有Local Handle都这个handle_scope对象进行管理

// Create a new context.

Persistent<Context> context = Context::New(); //Persistent Handle

// Enter the created context for compiling and

// running the hello world script.

Context::Scope context_scope(context);

// Create a string containing the JavaScript source code.

Handle<String> source = String::New("'Hello' + ', World!'"); //Local Handle

// Compile the source code.

Handle<Script> script = Script::Compile(source); //Local Handle

// Run the script to get the result.

Handle<Value> result = script->Run(); //Local Handle

// Dispose the persistent context.

context.Dispose();

// Convert the result to an ASCII string and print it.

String::AsciiValue ascii(result);

printf("%s\n", *ascii);

return 0;

// <<<<<<<<<<<<<<<<<<<<<<<到这里,handle_scope的生命周期结束,其析构函数将被调用,其内部的所有Local Handle将被释放

}

Context

Context值得是JavaScript的执行环境。每个JavaScript都必须执行在一个Context中。Context有多个,而且可以在不同的Context中进行切换。

[cpp] view
plaincopyprint?

Persistent<Context> context = Context::New();

Context::Scope context_scope(context);

这段代码就是申请一个Persistent contetxt,并通过Context::Scope切换到该context中。在这个Demo中,

[cpp] view
plaincopyprint?

Context::Scope context_scope(context);

之后的所有操作都执行在context中。

我们还可以使用

[cpp] view
plaincopyprint?

Persistent<Context> context_Ex = Context::New();

Context::Scope context_scope_Ex(context_Ex);

来切换到context_Ex中去。



从这张图可以比较清楚的看到Handle,HandleScope,以及被Handle引用的对象之间的关系。从图中可以看到,V8的对象都是存在V8的Heap中,而Handle则是对该对象的引用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: