在C++中嵌入V8

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方法来返回。如下例:

  1. // This function returns a new array with three elements, x, y, and z.  
  2. Handle<Array> NewPointArray(int x, int y, int z) {  
  3.   v8::Isolate* isolate = v8::Isolate::GetCurrent();  
  4.   
  5.   // We will be creating temporary handles so we use a handle scope.  
  6.   HandleScope handle_scope(isolate); //声明了HandleScope了  
  7.   
  8.   // Create a new empty array.  
  9.   Handle<Array> array = Array::New(3);  
  10.   
  11.   // Return an empty result if there was an error creating the array.  
  12.   if (array.IsEmpty())  
  13.     return Handle<Array>();  
  14.   
  15.   // Fill out the values  
  16.   array->Set(0, Integer::New(x));  
  17.   array->Set(1, Integer::New(y));  
  18.   array->Set(2, Integer::New(z));  
  19.   
  20.   // Return the value through Close.  
  21.   return handle_scope.Close(array); //这样才能正确返回一个本地handle  
  22. }  

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++整数。例子如下:

  1. Handle<Value> XGetter(Local<String> property,   
  2.                       const AccessorInfo& info) {  
  3.   return Integer::New(x);  
  4. }  
  5.     
  6. void XSetter(Local<String> property, Local<Value> value,  
  7.              const AccessorInfo& info) {  
  8.   x = value->Int32Value();  
  9. }  
  10.        
  11. // YGetter/YSetter are so similar they are omitted for brevity  
  12.   
  13. Handle<ObjectTemplate> global_templ = ObjectTemplate::New();  
  14. global_templ->SetAccessor(String::New("x"), XGetter, XSetter);  
  15. global_templ->SetAccessor(String::New("y"), YGetter, YSetter);  
  16. Persistent<Context> context = Context::New(NULL, global_templ);  

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


访问动态变量

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

  1. class Point {  
  2.    public:  
  3.     Point(int x, int y) : x_(x), y_(y) { }  
  4.     int x_, y_;  
  5.   }  

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

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

  1. Handle<ObjectTemplate> point_templ = ObjectTemplate::New();  
javascript的point对象通过内部field来引用一个c++对象。 这个内部对象只能通过C++代码访问,不能通过javascript访问。 一个对象可以拥有任意多的内部field,它的数量通过如下函数设置:

  1. point_templ->SetInternalFieldCount(1);  

这里,我们设置了一个长度为1的内部field,它的索引从0开始。

然后我们可以添加x和y的访问子:

  1. point_templ.SetAccessor(String::New("x"), GetPointX, SetPointX);  
  2. point_templ.SetAccessor(String::New("y"), GetPointY, SetPointY);  
下面,要将一个C++的point实例和javascript的代码关联。我们通过External对象来作为中介。因为javascript不知道如何处理一个c++对象。

  1. Point* p = ...;  
  2. Local<Object> obj = point_templ->NewInstance();  
  3. obj->SetInternalField(0, External::New(p));  
External实际上是对一个void* 只针的封装。

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

  1. Handle<Value> GetPointX(Local<String> property,  
  2.                         const AccessorInfo &info) {  
  3.   Local<Object> self = info.Holder();  
  4.   Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));  
  5.   void* ptr = wrap->Value();  
  6.   int value = static_cast<Point*>(ptr)->x_;  
  7.   return Integer::New(value);  
  8. }  
  9.   
  10. void SetPointX(Local<String> property, Local<Value> value,  
  11.                const AccessorInfo& info) {  
  12.   Local<Object> self = info.Holder();  
  13.   Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));  
  14.   void* ptr = wrap->Value();  
  15.   static_cast<Point*>(ptr)->x_ = value->Int32Value();  
  16. }  

Interceptors

有两种Interceptor,

  • 命名属性interceptor - 通过属性的字符串名来访问,如: document.theFormName.elementName
  • 索引属性interceptor - 通过索引访问的属性,如 document.forms.elements[0];
下面是SetNamedPropertyHandler中关于MapGet和MapSet的interceptor部分代码

  1. Handle<ObjectTemplate> result = ObjectTemplate::New();  
  2. result->SetNamedPropertyHandler(MapGet, MapSet);  
MapGet的interceptor如下

  1. Handle<Value> JsHttpRequestProcessor::MapGet(Local<String> name,  
  2.                                              const AccessorInfo &info) {  
  3.   // Fetch the map wrapped by this object.  
  4.   map<string, string> *obj = UnwrapMap(info.Holder());  
  5.   
  6.   // Convert the JavaScript string to a std::string.  
  7.   string key = ObjectToString(name);  
  8.   
  9.   // Look up the value if it exists using the standard STL idiom.  
  10.   map<string, string>::iterator iter = obj->find(key);  
  11.   
  12.   // If the key is not present return an empty handle as signal.  
  13.   if (iter == obj->end()) return Handle<Value>();  
  14.   
  15.   // Otherwise fetch the value and wrap it in a JavaScript string.  
  16.   const string &value = (*iter).second;  
  17.   return String::New(value.c_str(), value.length());  
  18. }  

安全模型

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


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

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


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

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

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


异常

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


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

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

  1. TryCatch trycatch;  
  2. Handle<Value> v = script->Run();  
  3. if (v.IsEmpty()) {    
  4.   Handle<Value> exception = trycatch.Exception();  
  5.   String::AsciiValue exception_str(exception);  
  6.   printf("Exception: %s\n", *exception_str);  
  7.   // ...  
  8. }  

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


继承

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


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

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

  1. // Create an object "bicycle"   
  2. function bicycle(){   
  3. }   
  4. // Create an instance of bicycle called roadbike  
  5. var roadbike = new bicycle()  
  6. // Define a custom property, wheels, on roadbike   
  7. roadbike.wheels = 2  
这个方法添加到了已经存在的对象实例上。

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

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

  1. // First, create the "bicycle" object  
  2. function bicycle(){   
  3. }  
  4. // Assign the wheels property to the object's prototype  
  5. bicycle.prototype.wheels = 2  
这样所有的bicycle()实例都有了wheels属性。

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

  1. Handle<FunctionTemplate> biketemplate = FunctionTemplate::New();  
  2. biketemplate->PrototypeTemplate().Set(  
  3.     String::New("wheels"),  
  4.     FunctionTemplate::New(MyWheelsMethodCallback)->GetFunction();  
  5. )  

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

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

  1. 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


  1. #include <v8.h>  
  2.   
  3. using namespace v8;  
  4. int main(int argc, char* argv[]) {  
  5.   
  6.   // Create a stack-allocated handle scope.  
  7.   HandleScope handle_scope;  
  8.   
  9.   // Create a new context.  
  10.   Persistent<Context> context = Context::New();  
  11.   
  12.   // Enter the created context for compiling and  
  13.   // running the hello world script.  
  14.   Context::Scope context_scope(context);  
  15.   
  16.   // Create a string containing the JavaScript source code.  
  17.   Handle<String> source = String::New("'Hello' + ', World!'");  
  18.   
  19.   // Compile the source code.  
  20.   Handle<Script> script = Script::Compile(source);  
  21.   
  22.   // Run the script to get the result.  
  23.   Handle<Value> result = script->Run();  
  24.   
  25.   // Dispose the persistent context.  
  26.   context.Dispose();  
  27.   
  28.   // Convert the result to an ASCII string and print it.  
  29.   String::AsciiValue ascii(result);  
  30.   printf("%s\n", *ascii);  
  31.   return 0;  
  32. }  

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的生命周期:

  1. #include <v8.h>  
  2.   
  3. using namespace v8;  
  4. int main(int argc, char* argv[]) {  
  5.   
  6.   // Create a stack-allocated handle scope.  
  7.   HandleScope handle_scope;  
  8.   // >>>>>>>>>>>>>>>>>>>>>>>>从这里开始,是HandleScope的生命周期的开始  
  9.   // 从此之后的所有Local Handle都这个handle_scope对象进行管理  
  10.   
  11.   
  12.   // Create a new context.  
  13.   Persistent<Context> context = Context::New();   //Persistent Handle  
  14.   
  15.   // Enter the created context for compiling and  
  16.   // running the hello world script.  
  17.   Context::Scope context_scope(context);  
  18.   
  19.   // Create a string containing the JavaScript source code.  
  20.   Handle<String> source = String::New("'Hello' + ', World!'"); //Local Handle  
  21.   
  22.   // Compile the source code.  
  23.   Handle<Script> script = Script::Compile(source); //Local Handle  
  24.   
  25.   // Run the script to get the result.  
  26.   Handle<Value> result = script->Run(); //Local Handle  
  27.   
  28.   // Dispose the persistent context.  
  29.   context.Dispose();  
  30.   
  31.   // Convert the result to an ASCII string and print it.  
  32.   String::AsciiValue ascii(result);  
  33.   printf("%s\n", *ascii);  
  34.   return 0;  
  35.   // <<<<<<<<<<<<<<<<<<<<<<<到这里,handle_scope的生命周期结束,其析构函数将被调用,其内部的所有Local Handle将被释放  
  36. }  

Context

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

  1. Persistent<Context> context = Context::New();   
  2. Context::Scope context_scope(context);  
这段代码就是申请一个Persistent contetxt,并通过Context::Scope切换到该context中。在这个Demo中,
  1. Context::Scope context_scope(context);  
之后的所有操作都执行在context中。

我们还可以使用

  1. Persistent<Context> context_Ex = Context::New();   
  2. Context::Scope context_scope_Ex(context_Ex);  
来切换到context_Ex中去。




从这张图可以比较清楚的看到Handle,HandleScope,以及被Handle引用的对象之间的关系。从图中可以看到,V8的对象都是存在V8的Heap中,而Handle则是对该对象的引用。







  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值