夜间模式暗黑模式
字体
阴影
滤镜
圆角
JavaScript&C++ Interaction Based on V8 Engine

V8也是必须得过却不容易过的一道坎啊。

Data & Template

V8中实现了JavaScript和C++之间数据和函数的相互调用。但是应当想到原生C++的数据类型和JavaScript中的数据类型还是很不一样的。这里V8相当于一个桥梁作用,提供了Value Class,在JavaScript和C++进行相互调用数据的时候就通过Value Class进行,比如说:

Handle<Value> Add(const Arguments& args) {
    int a = args[0]->Uint32Value();
    int b = args[1]->Uint32Value();

    return Integer::New(a + b);
}

对于JavaScript对象和函数来说,在V8中是通过两个模板类Template进行定义(并不是C++里的模板,别搞混了)。一个是对象模板ObjectTemplate,另一个是FunctionTemplate函数模板,分别定义JavaScript的对象和函数。通过这两个模板类也可以将C++中的对象和函数暴露给JavaScript脚本环境。

Interaction

对于变量相互调用来说,变量首先可以参照这样的方式来定义getter/setter:

static char sname[512] = {0};

static Handle<Value> NameGetter(Local<String> name, const AccessorInfo& info) {
    return String::New((char*)&sname, strlen((char*)&sname));
}

static void NameSetter(Local<String> name, Local<Value> value, const AccessorInfo& info) {
    Local<String> str = value->ToString();
    str->WriteAscii((char*)&sname);
}

然后要把它们注册到Global上:

Handle<ObjectTemplate> global = ObjectTemplate::New();
// 为全局对象创建一个template
global->SetAccessor(String::New("name"), NameGetter, NameSetter);
// 把getter和setter注册到global中

对于函数来说也可以照猫画虎:

// 在C++中 定义原型函数
Handle<Value> func(const Argument& args){ return xxx; }
// 注册到global 公开给脚本环境
global->Set(String::New("func"), FunctionTemplate::New(func));

C++的类是个一言难尽的东西,相较于JavaScript这种动态性比较强的语言来说,实在是显得不太“方便灵活”。可以认为V8实现了一个任务,就是把复杂的C++对象封装好来给JavaScript使用。从这个角度来说,可以不用太过纠结于C++的繁杂语法,但也能享受到“系统语言级的效率”,同时也能继续享受JavaScript的灵活多变:

// Example C++ class
class Person {
    private:
        unsigned int age;
        char name[512];
    public:
        Person(unsigned int age, char *name) {
            this->age = age;
            strncpy(this->name, name, sizeof(this->name));
        }
        // getter
        unsigned int getAge() {
            return this->age;
        }
        // setter
        void setAge(unsigned int nage) {
            this->age = nage;
        }
        // getter
        char *getName() {
            return this->name;
        }
        // setter
        void setName(char *nname) {
            strncpy(this->name, nname, sizeof(this->name));
        }
}

上面就是一个简单的Person类实现了对应的getter和setter方法,对于包装来说,不仅需要对getter和setter进行包装,也要对构造函数进行包装:

Handle<Value> PersonConstructor(const Argument& args){
    Handle<Object> object = args.This();
    HandleScope handle_scope;
    int age = args[0]->Uint32Value();

    String::Utf8Value str(args[1]);
    char* name = ToCString(str);

    Person *person = new Person(age, name);
    object->SetInternalField(0, External::New(Person));
    return object;
}

实际上构造函数其实和一般的函数,从V8包装角度看来没有多大区别,函数原型都是一样的。从args获取函数后需要转化成相对应的类型,设置在object中,并根据此参数来调用Person类实际的构造函数。接下来是getter和setter的包装:

Handle<Value> PersonGetAge(const Arguments& args){
    Local<Object> self = args.Holder();
    Local<External> wrap - Local<External>::Cast(self->GetInternalField(0));

    void *ptr = wrap->Value();

    return Integer::New(static_cast<Person*>(ptr)->getAge());
}

Handle<Value> PersonSetAge(const Arguments& args) {
    Local<Object> self = args.Holder();
    Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));

    void* ptr = wrap->Value();

    static_cast<Person*>(ptr)->setAge(args[0]->Uint32Value());
    return Undefined();
}

然后需要把Person暴露给脚本环境,并定义原型模板和设置实例模板:

Handle<FunctionTemplate> person_template = FunctionTemplate::New(PersonConstructor); 
person_template->SetClassName(String::New("Person")); 
global->Set(String::New("Person"), person_template);

Handle<ObjectTemplate> person_proto = person_template->PrototypeTemplate(); 

person_proto->Set("getAge", FunctionTemplate::New(PersonGetAge)); 
person_proto->Set("setAge", FunctionTemplate::New(PersonSetAge)); 

person_proto->Set("getName", FunctionTemplate::New(PersonGetName)); 
person_proto->Set("setName", FunctionTemplate::New(PersonSetName));

对于C++调用JavaScript中的函数,可以直接看一个计数器timer的例子:V8编译执行了timer.js,构造了Timer对象。

static void OnTimeout(uv_timer_t* handle) {
   TimerWrap* wrap = static_cast<TimerWrap*>(handle->data);
   Environment* env = wrap->env();
   HandleScope handle_scope(env->isolate());
   Context::Scope context_scope(env->context());
   wrap->MakeCallback(kOnTimeout, 0, nullptr);
}

inline v8::Local<v8::Value> AsyncWrap::MakeCallback(uint32_t index, int argc, v8::Local<v8::Value>* argv) {
   v8::Local<v8::Value> cb_v = object()->Get(index);
   CHECK(cb_v->IsFunction());
   return MakeCallback(cb_v.As<v8::Function>(), argc, argv);
}

TimerWrap 对象通过数组的索引寻址,找到 Timer 对象索引 0 的对象,而对其赋值的是在 lib/timer.js 里面的 list._timer[kOnTimeout] = listOnTimeout; 。这边找到的对象是个 Function, 后面忽略 domains 异常处理等,就是简单的调用 Function 对象的 Call 方法, 并且传人上文提到的 Context 和参数。

Local<Value> ret = callback->Call(recv, argc, argv);

这就实现了 C++ 对 JS 函数的调用。

暂无评论

发送评论 编辑评论


				
上一篇