在这本书的前五章中,我们一直在假设我们正在创建的addon使用的Node.js版卫0.12到6.0。这是一个相当宽泛的版本,但是有较早版本的节点。我们的addons不会编译/工作的js。在使用云托管服务时,这可能是一个很重要的问题,因为它可能会使用早期版本的node.js。当你在npm上发布你的插件时,它也会带来问题——因为你的addon不能被分配给所有用户。
为什么我们的addons不能在早期版本中工作?我们为什么要担心Node.js的未来版本能破坏我们的插件吗?答案是,它不是Node.js而是V8,V8定义了我们与JavaScript交互的API!V8的API随着时间的推移而发生了变化,并不能保证它不会再次发生。而V8开发者试图保持API的稳定,经常会有一些(可能小)打破了每一个新的变化,新的发行包。当新的V8版本被植入到Node.js中,我们运行addons有被修改的风险。
考虑到Node.js的发行速度(许多应用程序仍然运行在版本为 v0.10、v0.12、v4.0等的Node.js上),确实需要某种形式的抽象,这样我们就可以将目标锁定在更稳定的(并且干净的)API上。对于Node(NAN)的原生抽象就是这样。
NAN由io.js工作组管理,目标是提供一组宏和实用工具,实现两个目标:
1.在所有Node.js 版本上维护一个稳定的API;
2.提供一组实用工具,以帮助简化该Node.js和V8 API最困难的部分(例如异步的addon开发)。
如果您打算是Addon都能得到Node.js版本为v0.10-4.0的支持,那么只需要使用NAN来开发。最好熟悉它,并在任何你希望在很长一段时间内拥有大量用户的项目中使用它。
应该注意的是,NAN并不是一个高级API。虽然很少有实用程序可以简化异步的addon开发,但是大多数的NAN只是为标准的V8操作提供了替换的宏调用,比如在JavaScript堆中创建新的原始/对象、定义导出的函数和Node.js对象包装。到目前为止,我们已经了解到的V8编程的基本规则仍然适用,NAN只是提供了一种稍微不同的API来做我们以前做过的事情。
在这一章中,我将快速地展示相应的“NAN方法”来完成我们在第1-5章所完成的事情。在后面的章节中,我们将使用NAN,因为它对复杂的addon开发非常有帮助,并且在向npm公开发布addons时非常有意义。
非抽象导出函数
为了演示简单的功能,让我们来回顾一下第2章的addon示例,特别是在我们从JavaScript和C++中传递原语和对象/数组的地方。下面是我们在addon中创建的函数:
// adds 42 to the number passed and returns result
NODE_SET_METHOD(exports, "pass_number", PassNumber);
// adds 42 to the integer passed and returns result
NODE_SET_METHOD(exports, "pass_integer", PassInteger);
// reverses the string passed and returns the result
NODE_SET_METHOD(exports, "pass_string", PassString);
// nots the boolean passed and returns the result
NODE_SET_METHOD(exports, "pass_boolean", PassBoolean);
// extracts the x/y numeric properties in object
// passed to it and return a new object with sum
// and product
NODE_SET_METHOD(exports, "pass_object", PassObject);
// increments each numeric value in the input array, then returns
// an array a with a[0] equal to input[0]+1, a[1] equal to the
// "none_index" property defined on the input array, and a[2] equal
// to input[2]+1.
NODE_SET_METHOD(exports, "increment_array", IncrementArray);
这些示例函数是基本的,但是它们允许我们看到V8变量的所有典型用例。现在,让我们创建一个新的addon,具有相同的功能,只使用NAN。我们将逐一检查每一个,这样我们就可以清楚地了解原始V8 API和NAN之间的区别。
依赖设置
在开始之前,我们需要配置我们的addon以使用NAN。当使用NAN创建addon时,NAN将成为您的模块的依赖项。因此,在包中。json文件必须将NAN声明为一个依赖项:
$ npm install –save nan
不过,与大多数使用npm安装的模块不同,安装并没有安装JavaScript包,它只是简单地下载了NAN的C++(头文件)的分布。您不需要从JavaScript中引用NAN,但是您需要从您的C++代码中引用它,特别是通过包含nan.h。
为了将依赖项添加到您的addon中,我们在绑定中添加了节点node-gyp文件:
"include_dirs" : [
"<!(node -e \"require('nan')\")"
]
当我们编译C++addon时,这个指令将导致在构建路径上的nan模块的头文件。相当于添加了一个包含头文件的配置。
导出函数
让我们从实现示例中pass_number最简单的函数开始。我们将创建一个包含addon的文件夹,创建一个package.json,通过执行npm安装NAN:npm install nan –save,并配置下面的 binding.gyp 文件:
{
"targets": [
{
"target_name": "basic_nan",
"sources": [ "basic_nan.cpp" ],
"include_dirs" : [
"<!(node -e \"require('nan')\")"
]
}
]
}
现在我们将创建basic_nan.cpp并设置我们的addon。
#include <nan.h>
using namespace Nan;
using namespace v8;
NAN_METHOD(PassNumber) {
// do nothing ... for now.
}
NAN_MODULE_INIT(Init) {
Nan::Set(target, New<String>("pass_number").ToLocalChecked(),
GetFunction(New<FunctionTemplate>(PassNumber)).ToLocalChecked());
}
NODE_MODULE(basic_nan, Init)
对比标准V8 API:
#include <node.h>
using namespace v8;
void PassNumber(const FunctionCallbackInfo<Value>& args) {
// do nothing ... for now.
}
void Init(Local<Object> exports) {
NODE_SET_METHOD(exports, "pass_number", PassNumber);
}
NODE_MODULE(basic_nan, Init)
让我们从最底部开始——注意NODE_MODULE 命令是完全相同的。NODE_MODULE 已经是一个宏了—在不同版本的 Node.js 之间保持兼容性。与NAN的模式一样,作者没有发明新的方法来做事情,除非有理由相信v8/node的API改变会产生问题。
用我们的方法,我们有Init方法。在纯V8方法中,我们为node模块中使用的初始化函数有一个特定的调用签名。它必须具有返回类型的void,并接受导出(以及可选的模块)。在NAN中,提供了一个名为NAN_MODULE_INIT的宏。宏是参数化的,因此我们仍然可以命名初始化例程(Init),但是宏使我们避免了与调用签名的任何不一致。
在Init内部,我们会看到一些主要的变化,奇怪的是,NAN实际上会变得稍微复杂一些!我们不再使用 NODE_SET_METHOD 宏,而是在NAN中使用Set函数。注意,在Init的NAN版本中有一个“神奇”变量,称为target。这是由NAN_MODULE_INIT宏提供的,实际上是我们已经习惯使用的导出对象。为了在任何使用NAN的对象上设置一个值,我们必须使用实际的JavaScript字符串和函数—因此额外的代将“PassNumber”转换为V8字符串和 PassNumber作为JavaScript函数。我们将在稍后介绍这些转换的详细信息,因为它们使用了NAN非常依赖的标准的New 和ToLocalChecked方法。
再往上看,我们看一下PassNumber函数。在原始的V8代码中,我们必须有一个特定的调用签名——在这里返回void,我们接受一个包含调用信息的FunctionCallbackInfo对象。这个签名尤其在V8版本中发生了巨大的变化,因此,NAN为我们提供了一个宏来解决这个问题也就不足为奇了。NAN_METHOD为Node.js不同的版本创建一个适当的函数签名。稍后我们将看到,它还创建了一个info参数,该参数允许我们以V8版本的未知方式获取函数参数和holder(this)。
在这两代码的顶部,您会注意到我们现在使用了一个额外的名称空间Nan。v8的命名空间仍然被使用,还有node.h已经包含在nan.h中。
数值
让我们从PassNumber。在最简单的形式中,没有错误检查,原始的V8实现看起来如下:
void PassNumber(const FunctionCallbackInfo<Value>& args) {
Isolate * isolate = args.GetIsolate();
double value = args[0]->NumberValue();
Local<Number> retval = Number::New(isolate, value + 42);
args.GetReturnValue().Set(retval);
}
从典型用法转换到使用NAN是不容易的。主要变化:
1.不需要获得Isolate;
2.我们将使用内置的信息参数,由NAN_METHOD宏提供,而不是直接使用FunctionCallbackInfo;
3.我们将使用Nan::New来创建一个新数字,而不是V8::number,这就是我们不需要Isolate的原因。
NAN_METHOD(PassNumber) {
double value = info[0]->NumberValue();
Local<Number> retval = Nan::New(value + 42);
info.GetReturnValue().Set(retval);
}
执行错误检查是很简单的,因为info对象的功能与FunctionCallbackInfo args功能相同,在最初的V8示例中有很多相同的功能:
if ( info.Length() < 1 ) {
info.GetReturnValue().Set(Nan::New(0));
return;
}
if ( !info[0]->IsNumber()) {
info.GetReturnValue().Set(Nan::New(-1));
return;
}
这些函数的整型和布尔型变化遵循相同的模式 Nan::New方法重载来创建适当的值,而info具有IntegerValue和BooleanValue。
NAN_METHOD(PassInteger) {
if ( info.Length() < 1 ) {
return;
}
if ( !info[0]->IsInt32()) {
return;
}
int value = info[0]->IntegerValue();
Local<Integer> retval = Nan::New(value + 42);
info.GetReturnValue().Set(retval);
}
NAN_METHOD(PassBoolean) {
if ( info.Length() < 1 ) {
return;
}
if ( !info[0]->IsBoolean()) {
return;
}
bool value = info[0]->BooleanValue();
Local<Boolean> retval = Nan::New(!value);
info.GetReturnValue().Set(retval);
}
弱类型 and ToLocalChecked
如果您阅读了NAN文档,您很快就会注意到,它非常强调Maybe type(弱类型)的概念,这是V8的一个相对较新的特性。同样地,当我们的例子以简洁的格式处理参数和新原语时,大部分的NAN例子都是在创建JavaScript原语和对象(包括函数)时使用ToLocalChecked。实际上,我们在Init方法中看到了这种风格。让我们来看看这些概念是什么意思,以及为什么要使用它们。
让我们想象一下,我们的PassNumber方法是导出的,然后用JavaScript代码调用它:
console.log( addon.pass_number("xyz") );
在我们的原始代码中,info[0]是一个Local,并且在传递给add的参数时获取 “correct” 的值是很简单的,如果参数不是有效的数字,那么info[0]->NumberValue() 将返回NaN。还有一种使用Nan::to和ToLocalChecked的附加方式来执行转换到数字。
NAN_METHOD(PassNumber) {
Nan::MaybeLocal<Number> value = Nan::To<Number>(info[0]);
// will crash if value is empty
Local<Number> checked = value.ToLocalChecked();
Local<Number> retval = Nan::New(checked->Value() + 42);
info.GetReturnValue().Set(retval);
}
在这里,我们看到了Maybe types的(简单的)使用,它可能或可能不包含值。Nan::To返回可Maybe types,并转换为真正的Local类型参数,通过调用ToLocalChecked来实现。注意,如果值实际上是空的