最近由于忙着解决个人单身的问题,时隔这么久才更新第五章。
上一章主要讲了Google V8的Context概念。那么其实Google V8的基本概念还有FunctionTemplate, ObjectTemplate等比较重要的基本概念,这些概念将在后续章节中进行渗透。
本章主要来讲讲如何通过V8来实现JS调用C++。JS调用C++,分为JS调用C++函数(全局),和调用C++类。
JS调用C++函数
JS调用C++函数,就是通过FunctionTemplate和ObjectTemplate进行扩展的。
FunctionTemplate,ObjectTemplate可以理解为JS function和C++ 函数之间的binding。FunctionTemplate实现了JS函数和C++函数的绑定,当然这种绑定是单向的,只能实现JS调用C++的函数。说的更直白一点,FunctionTemplate和ObjectTemplate就相当于JS的function和object。
基本原理就是先将C++ 函数通过FunctionTemplate实现绑定,然后将这个FunctionTemplate注册到JS的global上去,这样,JS就可以调用C++函数了。
代码如下:
上面这段代码实现了在JS调用C++ Yell()函数。
基本步骤分为A, B , C三步:
#include "v8.h" #include <string.h> #include <stdio.h> using namespace v8; using namespace std; Handle<Value> Yell(const Arguments& args) { HandleScope handle_scope; char buffer[4096]; memset(buffer, 0, sizeof(buffer)); Handle<String> str = args[0]->ToString(); str->WriteAscii(buffer); printf("Yell: %s\n", buffer); return Undefined(); } int main(int argc, char** argv) { HandleScope handle_scope; //A Handle<FunctionTemplate> fun = FunctionTemplate::New(Yell); //B Handle<ObjectTemplate> global = ObjectTemplate::New(); global->Set(String::New("yell"), fun); //C Persistent<Context> cxt = Context::New(NULL, global); Context::Scope context_scope(cxt); Handle<String> source = String::New("yell('Google V8!')"); Handle<Script> script = Script::Compile(source); Handle<Value> result = script->Run(); cxt.Dispose(); }
第一步,定义一个FunctionTempte并与C++函数绑定:
Handle<FunctionTemplate> fun = FunctionTemplate::New(Yell);
第二部,定义一个ObectTemplate,并向该对象注册一个FunctionTemplate
Handle<ObjectTemplate> global = ObjectTemplate::New(); global->Set(String::New("yell"), fun);
第三部,将该对象注册到JS的global中去:
Persistent<Context> cxt = Context::New(NULL, global);
JS调用C++类
JS其实是无法直接使用C++类的,当JS中new一个对象的时候,需要手动将C++产生的对象同JS的对象进行绑定。从而就造成了JS使用C++类的假象:
var cloudapp = new CloudApp(); cloudapp.xxInterface();这一点V8做的不够强大,而Qt的QML(类JS脚本语言)就能实现自动绑定。
InternalField
当JS new一个对象的时候,C++中也会同步的new一个对象并将该指针保存在C++内部,并维护这个指针list,这就是V8 InternalField的作用。所有需要跟JS绑定的C++指针都存在这个InternalField中,其实就是一个list,一个V8 Object可以拥有任意数量的InternalField。如果需要使用保存在InterField中的C++指针,直接Get出来即可:
将C++指针封装到InternalField中:
//.... void* ptr = ... object->SetInternalField(0, External::New(ptr));上面这段代码将一个C++指针ptr保存在InternalField的index 0处。然后将来的某个时候如果需要获取这个指针,只需使用index 0来获取该指针。
将C++指针从InternalField中获取出来:
Local<External> wrap = Local<External>::Cast(object->GetInternalField(0)); void* ptr = wrap->Value();object->GetInternalField(0)就是从InternalField取出index=0处的C++指针。
External
既然说到C++指针的绑定,就必须说一下V8的External了。V8的External就是专门用来封装(Wrap)和解封(UnWrap)C++指针的。V8的External 实现如下:
Local<Value> External::Wrap(void* value) { return External::New(value); } void* External::Unwrap(Handle<v8::Value> obj) { return External::Cast(*obj)->Value(); }External其实就是C++指针的载体。这也就解释了前面在InternalField中设置和获取InternalField中的C++指针的时候,使用了External::New和wrap->Value()的原因了。External::Value()返回的就是C++指针。
下面开始上代码,看看究竟是如何实现JS调用C++类的:
//C++Externtion #include "v8.h" #include "utils.h" #include <iostream> #include <string> using namespace std; using namespace v8; enum AppState{ IDEL = 0, LOADED, STOP }; class CloudApp { public: CloudApp(int id) { state = IDEL; appId = id; } void start() { cout << "CloudApp been Loaded id = " << appId << endl; state = LOADED; }; int getState() { return state;} int getAppId() { return appId;} private: AppState state; int appId; }; //向MakeWeak注册的callback. void CloudAppWeakReferenceCallback(Persistent<Value> object , void * param) { if (CloudApp* cloudapp = static_cast<CloudApp*>(param)) { delete cloudapp; } } //将C++指针通过External保存为Persistent对象,避免的指针被析构 Handle<External> MakeWeakCloudApp(void* parameter) { Persistent<External> persistentCloudApp = Persistent<External>::New(External::New(parameter)); //MakeWeak非常重要,当JS世界new一个CloudApp对象之后 //C++也必须new一个对应的指针。 //JS对象析构之后必须想办法去析构C++的指针,可以通过MakeWeak来实现, //MakeWeak的主要目的是为了检测Persistent Handle除了当前Persistent //的唯一引用外,没有其他的引用,就可以析构这个Persistent Handle了, //同时调用MakeWeak的callback。这是我们可以再这个callback中delete //C++指针 persistentCloudApp.MakeWeak(parameter, CloudAppWeakReferenceCallback); return persistentCloudApp; } //将JS传进来的参数解析之后,创建C++对象 CloudApp* NewCloudApp(const Arguments& args) { CloudApp* cloudApp = NULL; if (args.Length() == 1) { cloudApp = new CloudApp(args[0]->ToInt32()->Value()); } else { v8::ThrowException(String::New("Too many parameters for NewCloudApp")); } return cloudApp; } //相当于JS对应的构造函数,当JS中使用new CloudApp的时候,这个callback将自动被调用 Handle<Value> CloudAppConstructCallback(const Arguments& args) { if (!args.IsConstructCall()) return Undefined(); CloudApp* cloudapp = NewCloudApp(args); Handle<Object> object = args.This(); object->SetInternalField(0, MakeWeakCloudApp(cloudapp)); return Undefined(); } Handle<Value> GetState(const Arguments& args) { Handle<Object> self = args.Holder(); Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); void* ptr = wrap->Value(); CloudApp* cloudapp = static_cast<CloudApp*>(ptr); return Integer::New(cloudapp->getState()); } Handle<Value> GetAppId(const Arguments& args) { Handle<Object> self = args.Holder(); Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); void* ptr = wrap->Value(); CloudApp* cloudapp = static_cast<CloudApp*>(ptr); return Integer::New(cloudapp->getAppId()); } Handle<Value> Start(const Arguments& args) { Handle<Object> self = args.Holder(); Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); void* ptr = wrap->Value(); CloudApp* cloudapp = static_cast<CloudApp*>(ptr); cloudapp->start(); return Undefined(); } void SetupCloudAppInterface(Handle<ObjectTemplate> global) { Handle<FunctionTemplate> cloudapp_template = FunctionTemplate::New(CloudAppConstructCallback); cloudapp_template->SetClassName(String::New("CloudApp")); Handle<ObjectTemplate> cloudapp_proto = cloudapp_template->PrototypeTemplate(); //这一步,完全可以使用cloudapp_inst->Set(....) //使用prototype更符合JS编程 cloudapp_proto->Set(String::New("start"), FunctionTemplate::New(Start)); cloudapp_proto->Set(String::New("state"), FunctionTemplate::New(GetState)); cloudapp_proto->Set(String::New("appid"), FunctionTemplate::New(GetAppId)); //******很重要!!! Handle<ObjectTemplate> cloudapp_inst = cloudapp_template->InstanceTemplate(); cloudapp_inst->SetInternalFieldCount(1); //向JS世界注册一个函数,其本质就是向JS世界的global注册一个类。 //所以,也是通过向global注入CloudApp类。 global->Set(String::New("CloudApp"), cloudapp_template); } void InitialnilizeInterface(Handle<ObjectTemplate> global) { SetupCloudAppInterface(global); } void LoadJsAndRun() { Handle<String> source = ReadJS("script.js"); Handle<Script> script = Script::Compile(source); Handle<Value> result = script->Run(); printValue(result); } void Regist2JsContext(Handle<ObjectTemplate>& object , Persistent<Context>& context) { context = Context::New(NULL, object); } int main(int argc, char** argv) { HandleScope handle_scope; Handle<ObjectTemplate> global = ObjectTemplate::New(); Persistent<Context> context; InitialnilizeInterface(global); Regist2JsContext(global, context); Context::Scope context_scope(context); LoadJsAndRun(); context.Dispose(); return 0; }
JS代码如下:
//script.js var cloudapp = new CloudApp(24); cloudapp.start(); var result;上面的代码基本可以从函数名称和注释中明白是什么意思。最后再讲一点SetInternalFieldCount:
Handle<ObjectTemplate> cloudapp_inst = cloudapp_template->InstanceTemplate(); cloudapp_inst->SetInternalFieldCount(1);
在其他的操作都就绪之后还必须SetInsternalFieldCount(),这一点是为了告诉V8,我们有几个InternalField,这里是只有1个。否则,在JS和C++指针交互过程中,V8在查找InternalField的时候会越界的。
版权申明:
转载文章请注明原文出处,任何用于商业目的,请联系本人:hyman_tan@126.com