- assert - 断言
- Buffer - 缓冲器
- child_process - 子进程
- cluster - 集群
- console - 控制台
- crypto - 加密
- dgram - 数据报
- dns - 域名服务器
- Error - 异常
- events - 事件
- fs - 文件系统
- global - 全局变量
- http - HTTP
- https - HTTPS
- module - 模块
- net - 网络
- os - 操作系统
- path - 路径
- process - 进程
- querystring - 查询字符串
- readline - 逐行读取
- repl - 交互式解释器
- stream - 流
- string_decoder - 字符串解码器
- timer - 定时器
- tls - 安全传输层
- tty - 终端
- url - 网址
- util - 实用工具
- v8 - V8引擎
- vm - 虚拟机
- zlib - 压缩
Node.js v10.8.0 文档
目录
C++ 插件#
Node.js 插件是用 C++ 编写的动态链接共享对象,可以使用 require()
函数加载到 Node.js 中,且像普通的 Node.js 模块一样被使用。
它们主要用于为运行在 Node.js 中的 JavaScript 与 C/C++ 库之间提供接口。
目前用于实现插件的方法相当复杂,涉及多个组件和 API 的知识:
-
V8:Node.js 目前用于提供 JavaScript 实现的 C++ 库。 V8 提供了用于创建对象、调用函数等的机制。 V8 的 API 文档主要在
v8.h
头文件中(Node.js 源代码中的deps/v8/include/v8.h
),也可以在查看 V8 在线文档。 -
libuv:实现了 Node.js 的事件循环、工作线程、以及平台所有的的异步操作的 C 库。 它也是一个跨平台的抽象库,使所有主流操作系统中可以像 POSIX 一样访问常用的系统任务,比如与文件系统、socket、定时器、以及系统事件的交互。 libuv 还提供了一个类似 POSIX 多线程的线程抽象,可被用于强化更复杂的需要超越标准事件循环的异步插件。 建议插件开发者多思考如何通过在 libuv 的非阻塞系统操作、工作线程、或自定义的 libuv 线程中降低工作负载来避免在 I/O 或其他时间密集型任务中阻塞事件循环。
-
内置的 Node.js 库。Node.js 自身开放了一些插件可以使用的 C++ API。 其中最重要的是
node::ObjectWrap
类。 -
Node.js 包含一些其他的静态链接库,如 OpenSSL。 这些库位于 Node.js 源代码中的
deps/
目录。 只有 V8 和 OpenSSL 符号是被 Node.js 开放的,并且通过插件被用于不同的场景。 更多信息可查看 链接到 Node.js 自身的依赖。
以下例子可从 Node 插件示例 下载,作为学习插件开发的起点。
Hello world#
“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(NODE_GYP_MODULE_NAME, init)
} // namespace demo
注意,所有的 Node.js 插件必须导出一个如下模式的初始化函数:
void Initialize(Local<Object> exports);
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
NODE_MODULE
后面没有分号,因为它不是一个函数(详见 node.h
)。
module_name
必须匹配最终的二进制文件名(不包括 .node 后缀)。
在 hello.cc
示例中,初始化函数是 init
,插件模块名是 addon
。
Context-aware addons#
这是Node.js扩展可能需要在多个上下文中多次加载的环境。比如,Electron的运行时会在单个进程中运行多个Node.js实例。每个实例都有它自己的require()
缓存,因此当通过require()
进行加载时,每个实例需要一个原生的扩展才能正常运行。从插件程序的角度来看,这意味着它必须支持多次初始化。
可以使用 NODE_MODULE_INITIALIZER
宏来构建 context-aware 扩展,它扩展为Node.js函数的名字使得Node.js能在加载时找到。可以按如下例子初始化一个扩展:
using namespace v8;
extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
/* Perform addon initialization steps here. */
}
另一个选择是使用NODE_MODULE_INIT()
宏,这也可以用来构建context-aware扩展。但和NODE_MODULE()
不一样的是,它可以基于一个给定的函数来构建一个扩展,NODE_MODULE_INIT()
充当了这种跟有函数体的给定函数的声明。
可以在调用NODE_MODULE_INIT()
之后在函数体内部使用以下三个变量:
Local<Object> exports
,Local<Value> module
,Local<Context> context
这种构建context-aware 扩展的方式顺带了管理全局静态数据的职责。由于扩展可能被多次加载,可能甚至来自不同的线程,扩展中任何的全局静态数据必须加以保护,并且不能包含任何对JavaScript对象持久性的引用。因为JavaScript对象只在一个上下文中有效,从错误的上下文或者另外的线程中访问有可能会导致崩溃。
可以通过执行以下步骤来构造context-aware扩展以避免全局静态数据:
- 定义一个持有每个扩展实例数据的类。这样的类应该包含一个
v8::Persistent<v8::Object>
持有exports
对象的弱引用。与该弱引用关联的回调函数将会破坏该类的实例。 - 在扩展实例化过程中构造这个类的实例,把
v8::Persistent<v8::Object>
挂到exports
对象上去。 - 在v8::External中保存这个类的实例
- 通过将
v8::External
传递给v8::FunctionTemplate
构造函数,该函数会创建本地支持的JavaScript函数,把v8::External
传递给所有暴露给JavaScript的方法。v8::FunctionTemplate
构造函数的第三个参数接受v8::External
。
这确保了每个扩展实例数据到达每个能被JavaScript访问的绑定。每个扩展实例数据也必须通过其创建的任何异步回调函数。
下面的例子说明了一个context-aware 扩展的实现:
#include <node.h>
using namespace v8;
class AddonData {
public:
AddonData(Isolate* isolate, Local<Object> exports):
call_count(0) {
// 将次对象的实例挂到exports上
exports_.Reset(isolate, exports);
exports_.SetWeak(this, DeleteMe, WeakCallbackType::kParameter);
}
~AddonData() {
if (!exports_.IsEmpty()) {
// 重新设置引用以避免数据泄露
exports_.ClearWeak();
exports_.Reset();
}
}
// Per-addon 数据
int call_count;
private:
// exports即将被回收时调用的方法
static void DeleteMe(const WeakCallbackInfo<AddonData>& info) {
delete info.GetParameter();
}
// exports对象弱句柄。该类的实例将与其若绑定的`exports`对象一起销毁
v8::Persistent<v8::Object> exports_;
};
static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
// 恢复per-addon-instance数据
AddonData* data =
reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
data->call_count++;
info.GetReturnValue().Set((double)data->call_count);
}
// context-aware 初始化
NODE_MODULE_INIT(/* exports, module, context */) {
Isolate* isolate = context->GetIsolate();
// 为该扩展实例的AddonData创建一个新的实例
AddonData* data = new AddonData(isolate, exports);
// 在v8::External中包裹数据,这样我们就可以将它传递给我们暴露的方法
Local<External> external = External::New(isolate, data);
// 把"Method"方法暴露给JavaScript,并确保其接收我们通过把`external`作为FunctionTemplate构造函数第三个参数时创建的 per-addon-instance数据
exports->Set(context,
String::NewFromUtf8(isolate, "method", NewStringType::kNormal)
.ToLocalChecked(),
FunctionTemplate::New(isolate, Method, external)
->GetFunction(context).ToLocalChecked()).FromJust();
}
构建#
当源代码已被编写,它必须被编译成二进制 addon.node
文件。
要做到这点,需在项目的顶层创建一个名为 binding.gyp
的文件,它使用一个类似 JSON 的格式来描述模块的构建配置。
该文件会被 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
安装说明了解更多信息,包括平台特定的要求。
但 binding.gyp
文件已被创建,使用 node-gyp configure
为当前平台生成相应的项目构建文件。
这会在 build/
目录下生成一个 Makefile
文件(在 Unix 平台上)或 vcxproj
文件(在 Windows 上)。
下一步,调用 node-gyp build
命令生成编译后的 addon.node
的文件。
它会被放进 build/Release/
目录。
当使用 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。
所有的插件都需要链接到 V8,也可能链接到任何其他依赖。
通常情况下,只要简单地包含相应的 #include <...>
声明(如 #include <v8.h>
),则 node-gyp
会自动定位到相应的头文件。
但是也有一些注意事项需要注意:
-
当
node-gyp
运行时,它会检测指定的 Node.js 发行版本,并下载完整的源代码包或只是头文件。 如果下载了完整的源代码,则插件对全套的 Node.js 依赖有完全的访问权限。 如果只下载了 Node.js 的文件头,则只有 Node.js 导出的符号可用。 -
node-gyp
可使用指向一个本地 Node.js 源代码镜像的--nodedir
标志来运行。 如果使用该选项,则插件有全套依赖的访问权限。
使用 require() 加载插件#
编译后的二进制插件的文件扩展名是 .node
(而不是 .dll
或 .so
)。
require()
函数用于查找具有 .node
文件扩展名的文件,并初始化为动态链接库。
当调用 require()
时,.node
拓展名通常可被省略,Node.js 仍会找到并初始化该插件。
注意,Node.js 会优先尝试查找并加载同名的模块或 JavaScript 文件。
例如,如果与二进制的 addon.node
同一目录下有一个 addon.js
文件,则 require('addon')
会优先加载 addon.js
文件。
Node.js 的原生抽象#
文档中所示的每个例子都直接使用 Node.js 和 V8 的 API 来实现插件。 V8 的 API 可能并且已经与下一个 V8 的发行版本有显著的变化。 伴随着每次变化,插件为了能够继续工作,可能需要进行更新和重新编译。 Node.js 的发布计划会尽量减少这种变化的频率和影响,但 Node.js 目前可以确保 V8 API 的稳定性。
Node.js 的原生抽象(或称为 nan
)提供了一组工具,建议插件开发者使用这些工具来保持插件在过往与将来的 V8 和 Node.js 的版本之间的兼容性。
查看 nan
示例了解它是如何被使用的。
N-API#
N-API是用于构建本机插件的API。它独立于底层JavaScript运行时(例如V8),并作为Node.js本身的一部分进行维护。此API将是跨Node.js版本的应 Application Binary Interface(ABI)稳定版。它旨在将Addons与底层JavaScript引擎中的更改隔离开来,并允许为一个版本编译的模块在更高版本的Node.js上运行而无需重新编译。插件使用本文档中概述的相同方法/工具(node-gyp等)构建/打包。唯一的区别是原始代码使用的API集。不使用V8或Native Abstractions for Node.js API,而是使用N-API中可用的功能。
要在上面的“Hello world”示例中使用N-API,请使用以下内容替换hello.cc
的内容。所有其他说明保持不变。
// hello.cc using N-API
#include <node_api.h>
namespace demo {
napi_value Method(napi_env env, napi_callback_info args) {
napi_value greeting;
napi_status status;
status = napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &greeting);
if (status != napi_ok) return nullptr;
return greeting;
}
napi_value init(napi_env env, napi_value exports) {
napi_status status;
napi_value fn;
status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
if (status != napi_ok) return nullptr;
status = napi_set_named_property(env, exports, "hello", fn);
if (status != napi_ok) return nullptr;
return exports;
}
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
} // namespace demo
可用功能和使用文档在这里 C/C++ Addons - N-API.
插件示例#
以下是一些插件示例,用于帮助开发者入门。 这些例子使用了 V8 的 API。 查看在线的 V8 文档有助于了解各种 V8 调用,V8 的嵌入器指南解释了句柄、作用域和函数模板等的一些概念。
每个示例都使用以下的 binding.gyp
文件:
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ]
}
]
}
如果有一个以上的 .cc
文件,则只需添加额外的文件名到 sources
数组。
例如:
"sources": ["addon.cc", "myexample.cc"]
当 binding.gyp
文件准备就绪,则插件示例可以使用 node-gyp
进行配置和构建:
$ node-gyp configure build
函数的参数#
插件通常会开放一些对象和函数,供运行在 Node.js 中的 JavaScript 访问。 当从 JavaScript 调用函数时,输入参数和返回值必须与 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;
// 这是 "add" 方法的实现
// 输入参数使用 const FunctionCallbackInfo<Value>& args 结构传入
void Add(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
// 检查传入的参数的个数
if (args.Length() < 2) {
// 抛出一个错误并传回到 JavaScript
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "参数的数量错误")));
return;
}
// 检查参数的类型
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "参数错误")));
return;
}
// 执行操作
double value = args[0]->NumberValue() + args[1]->NumberValue();
Local<Number> num = Number::New(isolate, value);
// 设置返回值
args.GetReturnValue().Set(num);
}
void Init(Local<Object> exports) {
NODE_SET_METHOD(exports, "add", Add);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
} // namespace demo
但已被编译,示例插件就可以在 Node.js 中引入和使用:
// test.js
const addon = require('./build/Release/addon');
console.log('This should be eight:', addon.add(3, 5));
回调#
把 JavaScript 函数传入到一个 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(NODE_GYP_MODULE_NAME, Init)
} // namespace demo
注意,该例子使用了一个带有两个参数的 Init()
,它接收完整的 module
对象作为第二个参数。
这使得插件可以用一个单一的函数完全地重写 exports
,而不是添加函数作为 exports
的属性。
为了验证它,运行以下 JavaScript:
// test.js
const addon = require('./build/Release/addon');
addon((msg) => {
console.log(msg);
// 打印: 'hello world'
});
注意,在这个例子中,回调函数是被同步地调用。
对象工厂#
插件可从 C++ 函数中创建并返回新的对象,如以下例子所示。
一个带有 msg
属性的对象被创建并返回,该属性会输出传入 createObject()
的字符串:
// 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(NODE_GYP_MODULE_NAME, Init)
} // namespace demo
在 JavaScript 中测试它:
// test.js
const addon = require('./build/Release/addon');
const obj1 = addon('hello');
const obj2 = addon('world');
console.log(obj1.msg, obj2.msg);
// 打印: 'hello world'
函数工厂#
另一种常见情况是创建 JavaScript 函数来包装 C++ 函数,并返回到 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();
// 可以省略这步使它匿名
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(NODE_GYP_MODULE_NAME, Init)
} // namespace demo
测试它:
// test.js
const addon = require('./build/Release/addon');
const 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(NODE_GYP_MODULE_NAME, 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::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> MyObject::constructor;
MyObject::MyObject(double value) : value_(value) {
}
MyObject::~MyObject() {
}
void MyObject::Init(Local<Object> exports) {
Isolate* isolate = exports->GetIsolate();
// 准备构造函数模版
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
// 原型
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()) {
// 像构造函数一样调用:`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 {
// 像普通方法 `MyObject(...)` 一样调用,转为构造调用。
const int argc = 1;
Local<Value> argv[argc] = { args[0] };
Local<Context> context = isolate->GetCurrentContext();
Local<Function> cons = Local<Function>::New(isolate, constructor);
Local<Object> result =
cons->NewInstance(context, argc, argv).ToLocalChecked();
args.GetReturnValue().Set(result);
}
}
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"
]
}
]
}
测试:
// test.js
const addon = require('./build/Release/addon');
const obj = new addon.MyObject(10);
console.log(obj.plusOne());
// 打印: 11
console.log(obj.plusOne());
// 打印: 12
console.log(obj.plusOne());
// 打印: 13
包装对象的工厂#
也可以使用一个工厂模式,避免显式地使用 JavaScript 的 new
操作来创建对象实例:
const obj = addon.createObject();
// 而不是:
// const 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(NODE_GYP_MODULE_NAME, 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::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> MyObject::constructor;
MyObject::MyObject(double value) : value_(value) {
}
MyObject::~MyObject() {
}
void MyObject::Init(Isolate* isolate) {
// 准备构造函数模版
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
// 原型
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()) {
// 像构造函数一样调用:`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 {
// 像普通方法 `MyObject(...)` 一样调用,转为构造调用。
const int 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 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);
}
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"
]
}
]
}
测试:
// test.js
const createObject = require('./build/Release/addon');
const obj = createObject(10);
console.log(obj.plusOne());
// 打印: 11
console.log(obj.plusOne());
// 打印: 12
console.log(obj.plusOne());
// 打印: 13
const 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()
函数,它可以把两个 MyObject
对象作为输入参数:
// 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(NODE_GYP_MODULE_NAME, 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::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()) {
// 像构造函数一样调用:`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 {
// 像普通方法 `MyObject(...)` 一样调用,转为构造调用。
const int argc = 1;
Local<Value> argv[argc] = { args[0] };
Local<Context> context = isolate->GetCurrentContext();
Local<Function> cons = Local<Function>::New(isolate, constructor);
Local<Object> instance =
cons->NewInstance(context, argc, argv).ToLocalChecked();
args.GetReturnValue().Set(instance);
}
}
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
测试:
// test.js
const addon = require('./build/Release/addon');
const obj1 = addon.createObject(10);
const obj2 = addon.createObject(20);
const result = addon.add(obj1, obj2);
console.log(result);
// 打印: 30
AtExit 钩子#
“AtExit” 钩子是一个函数,它在 Node.js 事件循环结束后、但在 JavaScript 虚拟机被终止与 Node.js 关闭前被调用。
“AtExit” 钩子使用 node::AtExit
API 注册。
void AtExit(callback, args)#
callback
<void (*)(void*)> - 一个退出时调用的函数的指针。args
<void*> - 一个退出时传递给回调的指针。
注册的 AtExit 钩子会在事件循环结束之后但虚拟机被终止之前退出。
AtExit 有两个参数:一个退出时运行的回调函数的指针,和一个要传入回调的无类型的上下文数据的指针。
回调按照后进先出的顺序运行。
以下 addon.cc
实现了 AtExit:
// addon.cc
#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(at_exit_cb2, cookie);
AtExit(at_exit_cb2, cookie);
AtExit(at_exit_cb1, exports->GetIsolate());
AtExit(sanity_check);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
} // namespace demo
测试:
// test.js
require('./build/Release/addon');