参考V8的英文文档,前面基本上是翻译, 后面是C++和Javascript的调用方式示例
1. Handle
l handle可以理解为指针,所有V8的对象都必须通过Handle来访问,因为V8垃圾收集机制决定了;
l handle都是被v8的垃圾收集器来管理的;
l handle在传递时都采用传值模式
Handle有两种类型:
1. Local Handle继承至Handle:代表局部变量, 它的生命周期有HandleScope来决定,通常都是在调用的函数栈上, HandleScope只能在c++的堆栈上分配,不能用new来分配,HandleScope析构时Local对象同时被释放
2. Persistent Handle继承至Handle:它的生命周期是全局的,释放的时候需要手工调用。
Persistent::New
用于分配对象
Persistent::Dispose
用于释放对象
V8会定期收集垃圾(无效的Handle),垃圾收集的机制是V8性能的关键(分代模式)
2. Scope
Scope可以理解为Handle的容器,可以包含很多很多的Handle。
Scope可以分:HandleScope、Context::Scope
1. 栈分配器,管理所有的Local Handle对象
2. HandleScope是构建在c++栈上面的
3. 它对localhandle的管理范围是在它的生存周期内,直到又创建了一个新的HandleScope
4. 当新分配一个HandleScope之后,新的Local Handle将在新的HandleScope内分配,直到这个HandleScope析构
5. HandleScope析构以后, 它所管理的Local Handle都将被释放,对于js而言,这个时候访问的就是undefined
6. Context::Scope仅仅管理Context,在构造中Context::Enter, 在析构中Context::Leave
3. Isolate
isolate代表了一个独立的v8引擎,简单说它是完全独立的,一个isolate中的对象不能被另外一个isolate使用。可以让每个线程拥有一个不同的isolate,来并行执行不同的JavaScript。对于isolate来说,任何时刻最多只能有一个现在对它访问,因此多线程并在执行同一个isolate的时候,需要有锁机制。
4. Context
Context表示执行环境,JavaScript代码可以运行在不同的执行环境里面,而且必须显示的指定运行在哪个执行环境。
a) 之所有Context,是因为JavaScript有内建的对象,需要隔离不同Context的内建对象
b) 第一次构建Context对象消耗比较大,后面就比较快了(snapshot)
c) 可以从一个Context快速切换到另外一个Context
d) 在C++代码中可以通过Context::GetCurrent()获取当前的Context
5. Template
Template代表了C++中JavaScript中的函数和对象,可以有很多的templates类型,在一个Context中只能有一个具体的Template的实例,有两种类型的template:
1. Function template
2. Object template
ObjectTemplate用于创建javascript中的object,添加到ObjectTemplate中的属性会添加到用ObjectTemplate创建出来的所有Javascript的object中。
1. 每个Function template关联着一个Object template
Handle<ObjectTemplate> global = ObjectTemplate::New(); global->Set(String::New("log"), FunctionTemplate::New(LogCallback));
2. 可以将自己定义的Object作为内嵌类型,以供其他js使用
Persistent<Context> context = Context::New(NULL, global);
6. Accessors
当Object的对象被js访问时被调用,会触发C++的回调函数,有两种类型的Accessor
1. Static Global变量
所谓静态全局变量指的是JavaScript中全局的变量。
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);
2. 动态变量
表示类中的成员变量,比如:
class Point { public: Point(int x, int y) : x_(x), y_(y) { } int x_, y_; }
需要建立一个JavaScript Object和C++对象关联起来(比如很多point对象,如何在JavaScript中管理?)。每个JavaScript对象都需要保存一个索引(成员变量)指向C++对象,但是这个变量在JavaScript中是访问不到,只能在C++中可以访问,可以理解为内部字段。
Handle<ObjectTemplate> point_templ = ObjectTemplate::New();
point_templ->SetInternalFieldCount(1);
这里内部字段的个数设置为1,表示当前对象有一个内部字段(索引为0),指向了C++对象。一个对象可以有多个内部字段。
增加x&y的Accessors:
point_templ.SetAccessor(String::New("x"), GetPointX, SetPointX); point_templ.SetAccessor(String::New("y"), GetPointY, SetPointY);
包装一个C++ 对象,并和JavaScript 的ObjectTemplate关联:
Point* p = ...; Local<Object> obj = point_templ->NewInstance(); obj->SetInternalField(0, External::New(p));
通过NewInstance构建一个实例,然后通过SetInternalField把C++对象和JavaScript对象关联起来。
External 对象只能用于从内部字段中恢复索引,相当于JavaScript对象到C++对象的一个桥接,上面例子中:Local<External> wrap就代表了JavaScript对象。
7. Intercetors
能够指定JavaScript访问对象属性时的一个callback。为了效率的原因,有两种不同的interceptor:
命名属性interceptor:通过属性的名称来访问
索引属性interceptor:通过属性的索引来访问
Handle<ObjectTemplate> result = ObjectTemplate::New(); result->SetNamedPropertyHandler(MapGet, MapSet);
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()); }
3. accessor和interceptor的区别是, 前者处理某个特性的属性,后者处理所有的属性
Security Model:V8有一套安全检测机制,不从你创建的context环境去访问这个context,缺省是不允许的,如果希望能够访问,就需要使用Security token或者Security callback机制。Security token可以是唯一的字符串。当构建context的时候,可以通过SetSecurityToken
来设定token,如果你没有设定,V8会自己生成一个。或者通过回调SetAccessCheckCallbacks 来询问另外一个context是否允许访问。
8. Exceptions
JavaScript会抛出异常,在C++中也捕捉到。
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); // ... }
9. Inheritance
JavaScript采用原型(prototype)来实现继承方式的。在V8中每个FunctionTemplate
有一个PrototypeTemplate
方法,这个方法给template设定了一个function'sprototype,PrototypeTemplate
能够设置相应的函数模板:
Handle<FunctionTemplate> biketemplate = FunctionTemplate::New(); biketemplate->PrototypeTemplate().Set( String::New("wheels"), FunctionTemplate::New(MyWheelsMethodCallback)->GetFunction(); )
V8的FunctionTemplate提供了继承函数(Inherit),你能够从其他的functiontemplate里面继承过来。
10. v8的编译(vs编译)
1. 下载cygwin (必须用chromium上面的版本)
svn co ttp://src.chromium.org/svn/trunk/deps/third_party/cygwin@66844 third_party/cygwin
2. 下载python:
svn co ttp://src.chromium.org/svn/trunk/deps/third_party/cygwin@66844 third_party/cygwin
3. 注意:cygwin&python都必须放在v8的源码根目录下third_party下面
4. 在cygwin环境下(自己建立Cygwin.bat,修改里面的脚本路径),进入v8的源码目录:
/cygdrive/d/data/v8/third_party/python_26/python.exe /cygdrive/d/data/v8/build/gyp_v8 -Dtarget_arch=x64
5. 在v8源码目录下, build目录会生成all.sln
6. 用vs打开并编译
11. C++和JavaScript相互调用
10.1. Global的使用方式
可以创建Global对象模版,设置到Context中:
HandleScope handle_scope;
//注册全局的函数
Handle<ObjectTemplate>global_temp = ObjectTemplate::New();
global_temp->Set(String::New("print"), FunctionTemplate::New(v8helper::Print));
Persistent<Context>context = Context::New(NULL, global_temp);
Context::Scope context_scope(context);
从Context获取Global对象,再设置属性:
HandleScope handle_scope;
Persistent<Context>context = Context::New();
Context::Scope context_scope(context);
Local<Object>global_temp = Context::GetCurrent()->Global(); global_temp->Set(String::New("print"),
FunctionTemplate::New(v8helper::Print)->GetFunction());
10.2. JavaScript的全局变量和C++的变量关联(普通类型)
在JavaScript中有一个全局变量,它的相关修改都和c++中某个全局变量保持一致,这里变量是简单类型。
//定义全局C++变量
int32_t x = 0;
//定义该变量的访问器
Handle<Value> XGetter(Local<String>property, constAccessorInfo& info)
{ return Integer::New(x);
}
void XSetter(Local<String> property,Local<Value>value, constAccessorInfo& info)
{
x = value->Int32Value();
}
//生成对象模版
Handle<ObjectTemplate> global_templ= ObjectTemplate::New();
//设置对象的访问器
global_templ->SetAccessor(String::New("x"),XGetter, XSetter);
//TODO: 将global_templ设置到context里面就可以在JavaScript中用x来访问了, 在JavaScript中对变量x的访问, 响应的C++函数XGetter/XSetter会被调用
10.3. 从JavaScript中获取C++对象
template<class T>
static T* Unwrap(Handle<Object> obj,int index=0){
Handle<External>field = Handle<External>::Cast(obj->GetInternalField(index));
void* ptr = field->Value();
return static_cast<T*>(ptr);
}
内部对象的含义和使用后面会介绍。
10.4. JavaScript的全局变量和C++的变量关联(对象)
JavaScript中有一个全局对象,它的修改和C++中的全局对象对应,包括可以操控C++对象中的成员函数,属性等
class Obj
{
public:
Obj(int x) : x_(x) { }
public:
static v8::Handle<v8::Value> setX(const v8::Arguments & args)
{
printf("Obj::setX\n");
Obj *o = v8helper::Unwrap<Obj>(args.Holder());
if(args.Length() > 0)
{
o->x_ = args[0]->Int32Value();
}
return Integer::New(o->x_);
}
static v8::Handle<v8::Value> getX(const v8::Arguments & args)
{
printf("Obj::getX\n");
Obj *o = v8helper::Unwrap<Obj>(args.Holder());
return Integer::New(o->x_);
}
static Handle<Value> getPropertyX(Local<String>property, constAccessorInfo& info)
{
printf("Obj::getPropertyX\n");
return Integer::New(v8helper::Unwrap<Obj>(info.Holder())->x_);
}
static void setPropertyX(Local<String> property,Local<Value>value, constAccessorInfo& info)
{
printf("Obj::setPropertyX\n");
v8helper::Unwrap<Obj>(info.Holder())->x_= value->Int32Value();
}
public:
int x_;
};
Obj*g_o = new Obj(10);
void test_object(int argc, char* argv[])
{
HandleScope handle_scope;
//注册全局的函数
Handle<ObjectTemplate>global_temp = ObjectTemplate::New();
global_temp->Set(String::New("print"), FunctionTemplate::New(v8helper::Print));
Persistent<Context>context = Context::New(NULL, global_temp);
Context::Scope context_scope(context);
//准备JavaScript中的对象
Handle<ObjectTemplate>object_temp = ObjectTemplate::New();
object_temp->SetInternalFieldCount(1);
//创建JavaScript对象
Local<Object>obj = object_temp->NewInstance();
//关联到c++对象
obj->SetInternalField(0,External::New(g_o));
//关联到C++对象的成员函数
obj->Set(String::New("setX"), FunctionTemplate::New(Obj::setX)->GetFunction());
obj->Set(String::New("getX"), FunctionTemplate::New(Obj::getX)->GetFunction());
//关联到C++对象的属性
obj->SetAccessor(String::New("x"), Obj::getPropertyX, Obj::setPropertyX);
//将JavaScript对象放入到执行环境中去
context->Global()->Set(String::New("obj"),obj, (PropertyAttribute)(v8::ReadOnly));
//在JavaScript中运行
Handle<Script>script = Script::Compile(String::New("obj.setX(15);print(obj.getX());obj.x=10;print(obj.getX())"));
Handle<Value>result = script->Run();
context.Dispose();
}
注意:这种方式下C++暴露给JavaScript的对象是全局的,在JavaScript中可以直接使用,不需要先new一个出来(用new出来的方式下面再介绍),其实也建议不要用new这种方式容易导致对象被提前释放了。
10.5. C++中调用JavaScript中的函数,并传入参数
{
HandleScope handle_scope;
//在JavaScript中运行
Handle<Script>script = Script::Compile(String::New("functionprocess(request){print(obj.x)}"));
Handle<Value>result = script->Run();
//获取JavaScript中的函数
Handle<String>process_name = String::New("process");
Handle<Value>process_val = context->Global()->Get(process_name);
//如果不是函数直接返回
if (!process_val->IsFunction()) return;
//转换成函数
Handle<Function>process_fun = Handle<Function>::Cast(process_val);
//如果process要调用多次,可以用Persistent类型, 自己维护它的范围
Persistent<Function>process = Persistent<Function>::New(process_fun);
{
TryCatch try_catch;
//调用函数,并传入参数
const int argc = 1;
Handle<Value>argv[argc]= { obj };
Handle<Value>result = process->Call(context->Global(), argc,argv);
if (result.IsEmpty()) {
String::Utf8Valueerror(try_catch.Exception());
//todo
}
else
{
//todo
}
}
process.Dispose();
}
10.6. 关于ObjectTemplate的一个问题
//准备JavaScript中的对象
Handle<ObjectTemplate>object_temp = ObjectTemplate::New();
object_temp->SetInternalFieldCount(2);
/*
//关联到C++对象的成员函数
object_temp->Set(String::New("setX"),FunctionTemplate::New(Obj::setX)->GetFunction());
object_temp->Set(String::New("getX"),FunctionTemplate::New(Obj::getX)->GetFunction());
//关联到C++对象的属性
object_temp->SetAccessor(String::New("x"),Obj::getPropertyX, Obj::setPropertyX);
*/
//创建JavaScript对象
Local<Object>obj = object_temp->NewInstance();
//关联到c++对象
obj->SetInternalField(0,External::New(g_o));
//关联到C++对象的成员函数
obj->Set(String::New("setX"),FunctionTemplate::New(Obj::setX)->GetFunction());
obj->Set(String::New("getX"),FunctionTemplate::New(Obj::getX)->GetFunction());
//关联到C++对象的属性
obj->SetAccessor(String::New("x"), Obj::getPropertyX,Obj::setPropertyX);
//创建JavaScript对象
Local<Object>obj1 = object_temp->NewInstance();
//关联到c++对象
obj1->SetInternalField(1,External::New(g_o));
//将JavaScript对象放入到执行环境中去
context->Global()->Set(String::New("obj"),obj, (PropertyAttribute)(v8::ReadOnly));
context->Global()->Set(String::New("obj1"), obj1,(PropertyAttribute)(v8::ReadOnly));
看上面的例子中,蓝色和红色部分,再ObjectTemplate或者在Object添加属性都可以,在前者添加NewInstance后,所有Object都生效,在后者添加只在当前这个Object生效。
10.7. 通过interceptor访问对象属性
可以通过interceptor来访问属性,它的优势是可以不需要些很多访问器,只需要写一个就可以了:
static Handle<Value>MapGet(Local<String> name,const AccessorInfo&info)
{
string key = v8helper::ObjectToString(name);
printf("MapGet:%s\n",key.c_str());
if(key != "x") returnHandle<Value>();
return Integer::New(v8helper::Unwrap<Obj>(info.Holder())->x_);
}
static Handle<Value> MapSet(Local<String>name, Local<Value> value_obj,const AccessorInfo&info)
{
string key = v8helper::ObjectToString(name);
printf("MapSet:%s\n",key.c_str());
if(key != "x") returnHandle<Value>();
int value = value_obj->Int32Value();
v8helper::Unwrap<Obj>(info.Holder())->x_= value_obj->Int32Value();
return Integer::New(v8helper::Unwrap<Obj>(info.Holder())->x_);
}
HandleScope handle_scope;
Persistent<Context>context = Context::New();
Context::Scope context_scope(context);
//注册全局的函数
Local<Object>global_temp = Context::GetCurrent()->Global();
global_temp->Set(String::New("print"), FunctionTemplate::New(v8helper::Print)->GetFunction());
//准备JavaScript中的对象
Handle<ObjectTemplate>object_temp = ObjectTemplate::New();
object_temp->SetInternalFieldCount(1);
object_temp->SetNamedPropertyHandler(Obj::MapGet, Obj::MapSet);
//关联到C++对象的属性
// object_temp->SetAccessor(String::New("x"), Obj::getPropertyX,Obj::setPropertyX);
//创建JavaScript对象
Local<Object>obj = object_temp->NewInstance();
//关联到c++对象
obj->SetInternalField(0,External::New(g_o));
//将JavaScript对象放入到执行环境中去
context->Global()->Set(String::New("obj"),obj, (PropertyAttribute)(v8::ReadOnly));
{
HandleScope handle_scope;
//在JavaScript中运行
Handle<Script>script = Script::Compile(String::New("print(obj.x);obj.x=15;print(obj.x);"));
Handle<Value>result = script->Run();
}
context.Dispose();
注意:例子中是通过名字字段做的索引,也可以通过数字索引来做(SetIndexedPropertyHandler),原理是一样的。
10.8. ObjectTemplate内部字段的用途(SetInternalField)
这个字段保存ObjectTemplate生成的JavaScript对象和C++对象的关联关系,通常都设置为1(即保存一个JavaScript对象和C++对象的对应)前面的例子都是这样的用。当然也可以为>1的数,如下:
class Obj1
{
public:
Obj1(int x) : x_(x) { }
public:
static Handle<Value> getPropertyX(Local<String>property, constAccessorInfo& info)
{
// printf("Obj::getPropertyX\n");
return Integer::New(v8helper::Unwrap<Obj>(info.Holder(),0)->x_);
}
static void setPropertyX(Local<String> property,Local<Value>value, constAccessorInfo& info)
{
// printf("Obj::setPropertyX\n");
v8helper::Unwrap<Obj>(info.Holder(), 0)->x_= value->Int32Value();
}
public:
int x_;
};
class Obj2
{
public:
Obj2(int x) : x_(x) { }
public:
static Handle<Value> getPropertyX(Local<String>property, constAccessorInfo& info)
{
// printf("Obj::getPropertyX\n");
return Integer::New(v8helper::Unwrap<Obj>(info.Holder(),1)->x_);
}
static void setPropertyX(Local<String> property,Local<Value>value, constAccessorInfo& info)
{
// printf("Obj::setPropertyX\n");
v8helper::Unwrap<Obj>(info.Holder(), 1)->x_= value->Int32Value();
}
public:
int x_;
};
Obj1*g_o1 = new Obj1(13);
Obj2*g_o2 = new Obj2(17);
void test_object_internal_field(int argc, char* argv[])
{
HandleScope handle_scope;
Persistent<Context>context = Context::New();
Context::Scope context_scope(context);
//注册全局的函数
Local<Object>global_temp = Context::GetCurrent()->Global();
Local<FunctionTemplate>ft = FunctionTemplate::New(v8helper::Print);
global_temp->Set(String::New("print"), ft->GetFunction());
//准备JavaScript中的对象
Handle<ObjectTemplate>object_temp = ObjectTemplate::New();
object_temp->SetInternalFieldCount(2);
//创建JavaScript对象
Local<Object>obj1 = object_temp->NewInstance();
obj1->SetAccessor(String::New("x"), Obj1::getPropertyX, Obj1::setPropertyX);
//关联到c++对象
obj1->SetInternalField(0,External::New(g_o1));
//将JavaScript对象放入到执行环境中去
context->Global()->Set(String::New("obj1"),obj1, (PropertyAttribute)(v8::ReadOnly));
//创建JavaScript对象
Local<Object>obj2 = object_temp->NewInstance();
//关联到c++对象
obj2->SetInternalField(1,External::New(g_o2));
obj2->SetAccessor(String::New("x"), Obj2::getPropertyX, Obj2::setPropertyX);
context->Global()->Set(String::New("obj2"),obj2, (PropertyAttribute)(v8::ReadOnly));
{
HandleScope handle_scope;
//;print(obj.x);obj.x=15;print(obj.x);
//在JavaScript中运行
// Handle<Script>script = Script::Compile(String::New("print(obj1.x);"));
Handle<Script>script = Script::Compile(String::New("print(obj1.x+ ':' + obj2.x);"));
Handle<Value>result = script->Run();
}
context.Dispose();
}
以上例子中可以在ObjectTemplate上面绑定多个JavaScript对象和C++对象的映射,但是不知道这种方式和启用多个ObjectTemplate,每个ObjectTemplate new一个Object的方式有什么差别?难道内存消耗会少一些?
10.9. 创建FunctionTemplate和C++对象关联
以上例子中都是创建的JavaScript对象和C++对象关联,简单的说就是在JavaScript直接实用全局的JavaScript对象,这时C++的对象都是创建好的,在JavaScript直接使用。
如何在JavaScript调用FunctionTemplate呢?或者说如何在JavaScript中new一个C++的对象呢?
创建一个js框架,底层采用自己的C网络层,上层使用js来实现服务层,看起来困难不大。