插件

Node.js 的插件是基于 C/C++ 编写的动态链接共享对象(dynamically-linked shared objects),可以像普通的 Node.js 模块一样通过 require() 函数加载到 Node.js 的开发环境中。Node.js 插件主要用于支持 Node.js 中 JavaScript 调用 C/C++ 库的接口。

目前,创建 Node.js 插件的方法比较复杂,需要了解以下几个组件和 API:

  • V8:基于 C++ 函数库,当前 Node.js 的 JavaScript 引擎。V8 实现了 JavaScript 创建对象、调用函数等诸多底层机制。V8 的 API 主要集中在 v8.h 头文件(详见 Node.js 项目中的 deps/v8/include/v8.h)中,更多信息请参考线上地址 https://v8docs.nodesource.com/。
  • libuv:基于 C 的函数库,实现了 Node.js 的事件循环机制、worker 线程机制以及 Node.js 上所有与异步相关的机制。同时,它是一个跨平台的抽象库,提供了简洁、类 POSIX 的接口,方便开发者调用主流操作系统的常规系统任务,比如与文件系统、网络套接字、定时器以及系统事件的交互。此外,libuv 实现了一个类 pthreads 的线程机制,便于开发者在标准事件循环机制之上构建更强大的异步插件。libuv 鼓励开发者思考如何避免 I/O、任务加载失败等因素对系统操作、worker 线程以及自定义 libuv 线程的阻塞行为。
  • Node.js 内建库:Node.js 本身内建了一系列 C/C++ API 供插件使用,其中最重要的就是 node::ObjectWrap 类。
  • Node.js 内置了一系列静态链接库,比如 OpenSSL,其中大部分位于项目的 deps/ 目录内,只有 V8 和 OpenSSL 刻意被 Node.js 进行了重定向输出,便于诸多插件的调用,更多信息请参考Linking to Node.js's own dependencies。

以下所有示例都可以在 https://github.com/nodejs/node-addon-examples 下载到。如果你想要开发 Node.js 的插件,也可以使用这些示例作为初始模板。

Hello World

该示例是使用 C++ 开发插件的简单示例,等同于下面这段 JavaScript 代码:

module.exports.hello = () => 'world';

首先,创建 hello.cc 文件:

// hello.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
}

void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(addon, init)

}  // namespace demo

值得注意的是,开发 Node.js 插件的开发时,必须输出一个初始化函数,模式如下:

void Initialize(Local<Object> exports);
NODE_MODULE(module_name, Initialize)

在 NODE_MODULE 的行末没有分号,因为它并不是一次函数调用(详见 node.h)。module_name 必须二进制文件名相匹配(不包含文件名的 .node 后缀)。通过上述代码,我们在 hello.cc 文件中声明了初始化函数是 init,插件名称是 addon。

构建

在上面的源代码编写完成后,需要将其编译为二进制文件 addon.node。首先,在项目的根目录创建一个 binding.gyp 文件,使用类 JSON 格式配置构建信息。该文件创建完成后会被 node-gyp 用来编译模块,node-gyp 是一个专门用于编译 Node.js 插件的工具。

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "hello.cc" ]
    }
  ]
}

注意:Node.js 中捆绑了一个特殊版本的 node-gyp 工具集,它是 npm 的一部分,用于支持 npm install 命令编译和安装插件,但是不能直接被开发者使用。如果开发者想直接使用 node-gyp,需要使用 npm install -g node-gyp 命令安装 node-gyp,更多信息请参考 node-gyp 的安装指南,其中包括了对特定平台的需求信息。

创建完 bindings.gyp 文件之后,即可使用 node-gyp configure 命令生成适用于当前平台的构建文件,同时会在 build 目录下生成一个适用于 UNIX 平台的 Makefile 或者适用于 Windows 平台的 vcxproj 文件。

接下来,调用 node-gyp build 命令在 build/Release/ 目录下编译生成 addon.node 文件。

当使用 npm install 命令安装 Node.js 的插件时,npm 会使用自身捆绑的 node-gyp 再次编译和生成适用于用户平台的相关文件。

构建完成后,就可以在 Node.js 的开发中使用 require() 加载 addon.node 模块了:

// hello.js
const addon = require('./build/Release/addon');

console.log(addon.hello()); // 'world'

更多信息请参考下面的几个示例,或者查看用于生产环境的示例 https://github.com/arturadib/node-qt。

因为插件编译后二进制文件路径(有时候可能是 ./build/Debug/)由编译方式确定,所以建议使用 bindings 包加载编译后的模块。

值得注意的是,bindings 包定位插件模块的方式非常类似于 try-catch:

try {
  return require('./build/Release/addon.node');
} catch (err) {
  return require('./build/Debug/addon.node');
}

链接到 Node.js 自身的依赖

Node.js 自身用到了许多静态链接库,比如 V8、libuv 以及 OpenSSL。所有的插件都可以链接到这些静态链接库,链接方式非常简单,只需要声明 #include <...>(比如:#include <v8.h>)语句,node-gyp 就会自动定位到合适的头文件。不过,开发时需要注意以下几点:

  • 运行 node-gyp 时,它会检测 Node.js 的特定版本并下载源码或者头文件信息。如果下载的是源码,那么插件就可以完整的使用 Node.js 的依赖;如果下载的只是头文件信息,那么只能使用 Node.js 输出的特定依赖。
  • 使用 node-gyp 命令时,可以添加 --nodedir 标志,用于指定本地的 Node.js 源数据。开启这个可选参数之后,插件可以使用 Node.js 的全部依赖。

使用 require() 加载插件

插件编译后的二进制文件使用 .node 作为后缀名,通过 require() 函数可以查找以 .node 为扩展名的文件并将它们初始化为动态链接库。

调用 require() 时,.node 扩展名虽然可以被 Node.js 查找到并初始化为动态链接库,但是有一个问题值得注意,那就是如果插件名字和其他文件重名了, Node.js 会优先加载 Node.js 模块和 JavaScript 文件。 举例来说,如果在同一目录下有 addon.js 和 addon.node 两个文件,那么 require('addon') 函数会优先加载 addon.js 文件。

Node.js 的本地抽象

本文档中的每个示例都直接使用了 Node.js 和 V8 的 API 开发插件,所以有一点非常值得注意,那就是 V8 API 随着版本升级仍在快速迭代之中。对于 V8 和 Node.js 的版本升级,插件需要重新修改和编译以适应新的 API。当前 Node.js 在发布计划中尽量减少 API 的变动对插件所带来的影响,但是这无法确保 V8 API 的稳定性。

Native Abstrations for Node.js(简称 NAN)提供了一系列工具帮助插件开发者在新旧版本的 V8 和 Node.js 之间保持插件的一致性。更多有多 NAN 的使用信息请参考 NAN 开发实例。

插件实例

以下内容是用于帮助开发者快读上手的插件实例,它们都在开发过程中调用了 V8 API。通过在线的 V8 指南 可以了解更多有关 V8 调用的信息,也可以参考 Embedder 的使用指南 了解 V8 的核心概念,比如句柄、作用域、函数模板等等。

每一个示例都使用了如下的 binding.gyp 文件:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "addon.cc" ]
    }
  ]
}

如果插件涉及多个 .cc 文件,可以使用将其依次添加入 source 字段所引用的数组中:

"sources": ["addon.cc", "myexample.cc"]

binding.gyp 创建完成后,最后使用 node-gyp 生成配置文件和编译成二进制文件:

$ node-gyp configure build

函数参数

插件通常都会暴漏某些对象和函数给 Node.js 中的 JavaScript 调用。当 JavaScript 调用函数时,也必须将将传入的参数映射给 C/C++ 代码,在函数调用完成后,还要映射 C/C++ 传回的返回值。

下面代码演示了如何读取 JavaScript 传递来的函数以及如何传输返回值:

// addon.cc
#include <node.h>

namespace demo {

using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

// This is the implementation of the "add" method
// Input arguments are passed using the
// const FunctionCallbackInfo<Value>& args struct
void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  // Check the number of arguments passed.
  if (args.Length() < 2) {
    // Throw an Error that is passed back to JavaScript
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate, "Wrong number of arguments")));
    return;
  }

  // Check the argument types
  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate, "Wrong arguments")));
    return;
  }

  // Perform the operation
  double value = args[0]->NumberValue() + args[1]->NumberValue();
  Local<Number> num = Number::New(isolate, value);

  // Set the return value (using the passed in
  // FunctionCallbackInfo<Value>&)
  args.GetReturnValue().Set(num);
}

void Init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(addon, Init)

}  // namespace demo

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

console.log('This should be eight:', addon.add(3,5));

回调函数

在 Node.js 开发环境中使用 JavaScript 通过插件向 C++ 函数传递并执行一个回调函数是非常常见的操作,下面代码演示了如何在 C++ 中执行回调函数:

// addon.cc
#include <node.h>

namespace demo {

using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Value;

void RunCallback(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Function> cb = Local<Function>::Cast(args[0]);
  const unsigned argc = 1;
  Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };
  cb->Call(Null(isolate), argc, argv);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", RunCallback);
}

NODE_MODULE(addon, Init)

}  // namespace demo

值得注意的是,在上面的示例中 Init 函数使用了两个参数,其中第二个参数用于接收完整的 module 对象。这种做法便于插件通过简单的函数重写 exports,而无需将函数挂载在 exports 之下。

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg+' '+obj2.msg); // 'hello world'
// test.js
const addon = require('./build/Release/addon');

addon((msg) => {
  console.log(msg); // 'hello world'
});

在上面的示例中,回调函数是以同步方式调用的。

对象工厂

在下面的代码中演示了如何使用 C++ 函数创建和返回一个新对象,新对象有一个 msg 属性:

// addon.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

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

  Local<Object> obj = Object::New(isolate);
  obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());

  args.GetReturnValue().Set(obj);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(addon, Init)

}  // namespace demo

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg+' '+obj2.msg); // 'hello world'

函数工厂

另一个常见操作就是通过 C++ 函数创建 Javascript 函数并将其返回到 JavaScript 开发环境中:

// addon.cc
#include <node.h>

namespace demo {

using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void MyFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));
}

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

  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  Local<Function> fn = tpl->GetFunction();

  // omit this to make it anonymous
  fn->SetName(String::NewFromUtf8(isolate, "theFunction"));

  args.GetReturnValue().Set(fn);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateFunction);
}

NODE_MODULE(addon, Init)

}  // namespace demo

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

var fn = addon();
console.log(fn()); // 'hello world'

包装 C++ 对象

在某些情况下,可以通过包装 C++ 对象或类让 JavaScript 使用 new 操作符创建新的实例:

// addon.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::Local;
using v8::Object;

void InitAll(Local<Object> exports) {
  MyObject::Init(exports);
}

NODE_MODULE(addon, InitAll)

}  // namespace demo

然后,在 myobject.h 头文件中,包装类继承 node::ObjectWrap:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Local<v8::Object> exports);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  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_;
};

}  // namespace demo

#endif

在 myobject.cc 文件中,具体实现需要暴漏出来的代码逻辑。在下面的代码中,通过将 plusOne() 方法挂载在构造器的原型上,将其暴漏给了外部:

// myobject.cc
#include "myobject.h"

namespace demo {

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> MyObject::constructor;

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

MyObject::~MyObject() {
}

void MyObject::Init(Local<Object> exports) {
  Isolate* isolate = exports->GetIsolate();

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

  // Prototype
  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

  constructor.Reset(isolate, tpl->GetFunction());
  exports->Set(String::NewFromUtf8(isolate, "MyObject"),
               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.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    args.GetReturnValue().Set(cons->NewInstance(argc, argv));
  }
}

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

  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value_ += 1;

  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
}

}  // namespace demo

构建该示例之前,需要将 myobject.cc 文件名添加到 binding.gyp 文件中:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [
        "addon.cc",
        "myobject.cc"
      ]
    }
  ]
}

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

var obj = new addon.MyObject(10);
console.log( obj.plusOne() ); // 11
console.log( obj.plusOne() ); // 12
console.log( obj.plusOne() ); // 13

包装对象工厂方法

此外,可以使用工厂模式在 JavaScript 中隐式创建对象实例:

var obj = addon.createObject();
// instead of:
// var obj = new addon.Object();

首先,需要在 addon.cc 文件中实现 createObject() 方法的逻辑:

// addon.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

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

void InitAll(Local<Object> exports, Local<Object> module) {
  MyObject::Init(exports->GetIsolate());

  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(addon, InitAll)

}  // namespace demo

在 myobject.h 头文件中,添加静态方法 NewInstance() 用于实例化对象,该方法用于替换 JavaScript 中的 new 操作:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <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);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  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_;
};

}  // namespace demo

#endif

myobject.cc 文件的具体实现与前例代码非常类似:

// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

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> 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);

  // Prototype
  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

  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.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    args.GetReturnValue().Set(cons->NewInstance(argc, argv));
  }
}

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<Object> instance = cons->NewInstance(argc, argv);

  args.GetReturnValue().Set(instance);
}

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

  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value_ += 1;

  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
}

}  // namespace demo

构建示例之前,先将 myobject.cc 文件名添加到 binding.gyp 配置文件中:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [
        "addon.cc",
        "myobject.cc"
      ]
    }
  ]
}

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const createObject = require('./build/Release/addon');

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

var obj2 = createObject(20);
console.log( obj2.plusOne() ); // 21
console.log( obj2.plusOne() ); // 22
console.log( obj2.plusOne() ); // 23

传递包装对象

在包装盒返回 C++ 对象之外,还可以使用 Node.js 中的辅助函数 node::ObjectWrap::Unwrap 解包包装对象。在下面的示例中演示了函数 add() 如何解析传入的两个对象参数:

// addon.cc
#include <node.h>
#include <node_object_wrap.h>
#include "myobject.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

在 myobject.h 头文件中,添加一个新的公共方法,用于调用对象解包后的私有属性:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <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

myobject.cc 的具体实现和前面的示例类似:

// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

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.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    args.GetReturnValue().Set(cons->NewInstance(argc, argv));
  }
}

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<Object> instance = cons->NewInstance(argc, argv);

  args.GetReturnValue().Set(instance);
}

}  // namespace demo

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

var obj1 = addon.createObject(10);
var obj2 = addon.createObject(20);
var result = addon.add(obj1, obj2);

console.log(result); // 30

AtExit 钩子函数

"AtExit" 钩子函数在 Node.js 事件循环结束之后、JavaScript VM 终止以及 Node.js 退出之前调用。需要使用 "node::AtExit" 接口注册 "AtExit" 钩子函数。

void AtExit(callback, args)

  • callback,void (*)(vodi*),一个指向函数的指针,函数结束时调用
  • args,void*,函数结束是传递给 callback 的指针

注册 AtExit 钩子函数需要在事件循环之后和 VM 退出之前。callback 按照后入先出的顺序执行。下面 addon.cc 文件中的代码实现了 AtExit 钩子函数:

// addon.cc
#undef NDEBUG
#include <assert.h>
#include <stdlib.h>
#include <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) {
  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

编译成功之后,在 Node.js 环境中使用 require() 函数加载插件:

// test.js
const addon = require('./build/Release/addon');

results matching ""

    No results matching ""