在Node-Addon系列4文章中,主要学习Node Addons的函数式的和对象式的扩展编写流程,并且熟悉了Node.js的常用对象 Isolate FunctionCallbackInfo ObjectWrap Function FunctionTemplate String等对象,以及关键的成员函数绑定 NODE_SET_PROTOTYPE_METHOD ,初始化函数绑定 NODE_MODULE。而本节主要介绍WrapObject的另外的两种用法,以及退出钩子的编写。


对象工厂(Factory of wrapped objects)

使用工厂模式来避免使用javascript的 new 操作符创建一个对象。

下面的使用New关键字创建的示例,写起代码不那么简洁呢!

// test.js
var object = require("./build/Release/wrap_oop.node");
var obj = new object.CustomObject(10);
console.log(obj.plusOne()); // 11
console.log(obj.plusOne()); // 12
console.log(obj.plusOne()); // 13
// 使用工厂模式创建对象的用例
// test.js
const createObject = require('./build/Release/custom_add.node');

var obj = createObject();
console.log(obj.plusOne()); // 11
console.log(obj.plusOne()); // 12
console.log(obj.plusOne()); // 13

var obj2 = createObject();
console.log(obj2.plusOne()); // 21
console.log(obj2.plusOne()); // 22
console.log(obj2.plusOne()); // 23
  • 从JS代码中,可以看出,加载custom_add文件后,获取到的是一个函数对象,可以直接使用。也就是说,外部的函数与对象中的创建方法绑定了。每次调用外部函数,就相当于调用 Object.new()Object.NewInstance();,这便是核心思想。具体示例如下:
//
// Created by goerver on 16-6-30.
// add_bind.cpp
//

#include <node/v8.h>
#include "Add.h"

namespace CustomAdd{
    using namespace v8;
    void CreateObject(const FunctionCallbackInfo<Value>& args){
        Add::NewInstance(args);
    }

    void InitAll(Local<Object> exports,Local<Object> module){
        Add::Init(exports->GetIsolate());
        NODE_SET_METHOD(module,"exports",CreateObject);
    }

    NODE_MODULE(Add,InitAll);
}
//
// Created by goerver on 16-6-30.
//

#ifndef NODEADDONE_LESSION4_ADD_H
#define NODEADDONE_LESSION4_ADD_H
//Add.h
#include <node/node_object_wrap.h>
#include <node/node.h>
namespace CustomAdd{
    class Add: public node::ObjectWrap {
    public:
        static void Init(v8::Isolate* isolate);
        static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);

    private:
        explicit Add(double value=0);
        ~Add();
        static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
        static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
        static v8::Persistent<v8::Function> constructor;
        double value_;

    };
}
#endif //NODEADDONE_LESSION4_ADD_H

//
// Created by goerver on 16-6-30.
// Add.cpp
//

#include "Add.h"

namespace CustomAdd {
    using v8::Context;
    using v8::Function;
    using v8::FunctionCallbackInfo;
    using v8::FunctionTemplate;
    using v8::Isolate;
    using v8::Local;
    using v8::Number;
    using v8::Object;
    using v8::Persistent;
    using v8::String;
    using v8::Value;

    Persistent<Function> Add::constructor;

    Add::Add(double value) : value_(value) { }

    Add::~Add() { }

    void Add::Init(v8::Isolate *isolate) {
        Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
        tpl->SetClassName(String::NewFromUtf8(isolate, "Add"));
        tpl->InstanceTemplate()->SetInternalFieldCount(1);

        NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

        constructor.Reset(isolate, tpl->GetFunction());
    }

    void Add::New(const v8::FunctionCallbackInfo<v8::Value> &args) {
        Isolate *isolate = args.GetIsolate();

        if (args.IsConstructCall()) {
            double value = args[0]->IsUndefined()?0:args[0]->NumberValue();
            Add* add = new Add(value);
            add->Wrap(args.This());
            args.GetReturnValue().Set(args.This());
        } else {
           //调用内部方法,避免重复实现
            Add::NewInstance(args);
        }
    }

    void Add::NewInstance(const v8::FunctionCallbackInfo<v8::Value> &args) {
        Isolate* isolate = args.GetIsolate();
        const unsigned argc =1;
        Local<Value> argv[argc] = {args[0]};
        Local<Function> cons = Local<Function>::New(isolate,constructor);
        Local<Context> context = isolate->GetCurrentContext();
        Local<Object>  instance = cons->NewInstance(context,argc,argv).ToLocalChecked();
        args.GetReturnValue().Set(instance);
    }

    void Add::PlusOne(const v8::FunctionCallbackInfo<v8::Value> &args) {
        Isolate* isolate = args.GetIsolate();
        Add* add;
        add = node::ObjectWrap::Unwrap<Add>(args.Holder());
        ++add->value_;
        args.GetReturnValue().Set(Number::New(isolate,add->value_));
    }
}

之后的binding.gyp,将不再重复显示,编写的方式可以参考之前的文章。但需要注意的是,当有头文件时,并不需要将头文件写入sources中,只需要将.cc或.cpp的所有文件写入数组即可,如"sources": ["addon.cc","myobject.cc"]

Passing wrapped objects around

通过这种方式的使用,使用的示例是这样的:

const addon = require('./build/Release/function_add_by_obj.node');
var obj1 = addon.createObject(10);
var obj2 = addon.createObject(20);
var result = addon.add(obj1, obj2);
console.log(result);

In addition to wrapping and returning C++ objects, it is possible to pass wrapped objects around by unwrapping them with the Node.js helper function node::ObjectWrap::Unwrap. The following examples shows a function add() that can take two MyObject objects as input arguments* :

英文最后一句是本小节的重点,函数调用C++对象的成员函数获取内部变量,实现操作C++对象实现某一个功能。而C++对象的创建根据函数传递arguments传递参数的。该实现方式的思想与前一个例子是完全相同的。需要记住的相这个方法。参数如何通过封装再封装传递给对象的。

MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
                args[0]->ToObject());
MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
                args[1]->ToObject());

还有一点是,当一个 exports 对象挂载多个函数时,这时函数的绑定便与之前的例子不同了,比较如下:

//挂载多个函数的方式
void InitAll(Local<Object> exports) {
        MyObject::Init(exports->GetIsolate());
        NODE_SET_METHOD(exports, "createObject", CreateObject);
        NODE_SET_METHOD(exports, "add", Add);
    }

具体的示例如下:

// Created by goerver on 16-7-1.
// AddObj.h
#ifndef NODEADDONE_LESSION4_ADDOBJ_H
#define NODEADDONE_LESSION4_ADDOBJ_H

#include <node/node.h>
#include <node/node_object_wrap.h>
namespace demo {

    class MyObject : public node::ObjectWrap {
    public:
        static void Init(v8::Isolate* isolate);
        static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
        inline double value() const { return value_; }
    private:
        explicit MyObject(double value = 0);
        ~MyObject();
        static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
        static v8::Persistent<v8::Function> constructor;
        double value_;
    };

}  // namespace demo
#endif //NODEADDONE_LESSION4_ADDOBJ_H
//
// Created by goerver on 16-7-1.
// AddObj.cpp
//
#include <node.h>
#include "AddObj.h"
namespace demo {
    using v8::Context;
    using v8::Function;
    using v8::FunctionCallbackInfo;
    using v8::FunctionTemplate;
    using v8::Isolate;
    using v8::Local;
    using v8::Object;
    using v8::Persistent;
    using v8::String;
    using v8::Value;

    Persistent<Function> MyObject::constructor;
    MyObject::MyObject(double value) : value_(value) {
    }

    MyObject::~MyObject() {
    }

    void MyObject::Init(Isolate* isolate) {
        // Prepare constructor template
        Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
        tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
        tpl->InstanceTemplate()->SetInternalFieldCount(1);

        constructor.Reset(isolate, tpl->GetFunction());
    }

    void MyObject::New(const FunctionCallbackInfo<Value>& args) {
        Isolate* isolate = args.GetIsolate();

        if (args.IsConstructCall()) {
            // Invoked as constructor: `new MyObject(...)`
            double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
            MyObject* obj = new MyObject(value);
            obj->Wrap(args.This());
            args.GetReturnValue().Set(args.This());
        } else {
            // Invoked as plain function `MyObject(...)`, turn into construct call.
            MyObject::NewInstance(args);
        }
    }

    void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
        Isolate* isolate = args.GetIsolate();

        const unsigned argc = 1;
        Local<Value> argv[argc] = { args[0] };
        Local<Function> cons = Local<Function>::New(isolate, constructor);
        Local<Context> context = isolate->GetCurrentContext();
        Local<Object> instance =
                cons->NewInstance(context, argc, argv).ToLocalChecked();
        args.GetReturnValue().Set(instance);
    }

}  // namespace demo
// Created by goerver on 16-7-1.
// bind_add_obj.cpp
#include "AddObj.h"
namespace demo {
    using v8::FunctionCallbackInfo;
    using v8::Isolate;
    using v8::Local;
    using v8::Number;
    using v8::Object;
    using v8::String;
    using v8::Value;

    void CreateObject(const FunctionCallbackInfo<Value>& args) {
        MyObject::NewInstance(args);
    }

    void Add(const FunctionCallbackInfo<Value>& args) {
        Isolate* isolate = args.GetIsolate();

        MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
                args[0]->ToObject());
        MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
                args[1]->ToObject());

        double sum = obj1->value() + obj2->value();
        args.GetReturnValue().Set(Number::New(isolate, sum));
    }

    void InitAll(Local<Object> exports) {
        MyObject::Init(exports->GetIsolate());
        NODE_SET_METHOD(exports, "createObject", CreateObject);
        NODE_SET_METHOD(exports, "add", Add);
    }
    NODE_MODULE(addon, InitAll)
}  // namespace demo

退出钩出(AtExit hooks)

An “AtExit” hook is a function that is invoked after the Node.js event loop has ended by before the JavaScript VM is terminated and Node.js shuts down. “AtExit” hooks are registered using the node::AtExit API.

主要描述的是AtExit是在Node.js事件循环结束,在JS VM停止前,执行该回调函数

void AtExit(callback,args);

  • callback:void (*) - A pointer to the function to call at exit.
  • args:void* - A pointer to pass to the callback at exit.
  • Registers exit hooks that run after the event loop has ended but before the VM is killed. AtExit takes two parameters: a pointer to a callback function to run at exit, and a pointer to untyped context data to be passed to that callback.

Callbacks are run in last-in first-out order.

AtExit 该函数使用示例如下

// test.js
const addon = require('./build/Release/addon');
#undef NDEBUG
#include <assert.h>
#include <stdlib.h>
#include <node/node.h>

namespace demo {

using node::AtExit;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;

static char cookie[] = "yum yum";
static int at_exit_cb1_called = 0;
static int at_exit_cb2_called = 0;

static void at_exit_cb1(void* arg) {
  // void* 空指针,调用 static_cast强制
  Isolate* isolate = static_cast<Isolate*>(arg);
  HandleScope scope(isolate);
  Local<Object> obj = Object::New(isolate);
  assert(!obj.IsEmpty()); // assert VM is still alive
  assert(obj->IsObject());
  at_exit_cb1_called++;

}

static void at_exit_cb2(void* arg) {
  assert(arg == static_cast<void*>(cookie));
  at_exit_cb2_called++;

}

static void sanity_check(void*) {
  assert(at_exit_cb1_called == 1);
  assert(at_exit_cb2_called == 2);
}

void init(Local<Object> exports) {
  //无函数参数的使用
  AtExit(sanity_check);
  AtExit(at_exit_cb2, cookie);
  AtExit(at_exit_cb2, cookie);
  AtExit(at_exit_cb1, exports->GetIsolate());
}
NODE_MODULE(addon, init)

}  // namespace demo

NodeJS 下个系例准备中:

  • [ ] v8 API 相关常用API示例
  • [ ] 项目需要实例编写Node Addon
  • [ ] 组织分析-相关Node-Addon学习资料
  • [ ] NodeJS-源码结构分析

作者 @flyfishonline 2016 年 07月 1日