Node.js And C++__6.V8和Node接口抽象

本文介绍了在Node.js addon开发中,由于V8 API的不稳定性,需要使用NAN(Native Abstraction for Node.js)来提供跨版本的稳定接口。NAN的目标是提供一个在所有Node.js版本上保持稳定的API,简化与V8和Node.js的交互,特别是处理异步addon开发。文章详细对比了NAN与原始V8 API的区别,包括导出函数、数值处理、弱类型转换、字符串和对象操作,以及回调和异步处理。NAN通过宏和工具简化了这些操作,减少了与V8版本兼容性的担忧。此外,还介绍了如何利用NAN进行对象包装,以确保对象在不同Node.js版本间的兼容性。
摘要由CSDN通过智能技术生成

在这本书的前五章中,我们一直在假设我们正在创建的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来实现。注意,如果值实际上是空的࿰

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值