在C++中嵌入V8

本文翻译自 http://write.blog.csdn.net/postedit


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都会被删除。


请看例子 使用C++调用V8 。下图介绍了例子中代码生成的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];


V8源代码中的示例process.cc展示了一个使用interceptors的例子。下面是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);


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值