之前我们使用了nan来编写addon,但是基于nan方式的addon是类似封建版本的编译库,也就是说,在基于node v8版本之前,可以实现一次编写,处处编译。但是在node v8之后,还是需要再次编译,让宏定义再次匹配新的版本去编译出新的插件包,于是在node v8版本之后,nodejs提出了新的一套机制,也就是我们这次的主角-NAPI。
此模块包含header-only c++ wrapper class,这些类简化了使用C ++时由Node.js提供的基于C的N-API的使用。它以低开销提供了C ++对象模型和异常处理语义。
它有自己的头文件 napi.h , 包含了 N-API 的所有对 C++的封装, 并且跟 N-API 一样是由官方维护, 点这里查看仓库.因为他的使用相较于其他库的兼容和使用更加便利, 所以在进行 C++API 封装的时候优先选择该方法.

Example

下面通过简单的示例程序,了解其n-api api的使用;

hello world

通过js调用c++ addon,实现打印‘hello world’。

c++ code:

  1. #include <napi.h>
  2. Napi::String Hello(const Napi::CallbackInfo &info)
  3. {
  4. Napi::Env env = info.Env();
  5. return Napi::String::New(env, "world");
  6. };
  7. Napi::Object Init(Napi::Env env, Napi::Object exports)
  8. {
  9. exports.Set(Napi::String::New(env, "hello"), Napi::Function::New(env, Hello));
  10. return exports;
  11. };
  12. NODE_API_MODULE(hello, Init);

config building.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [
        "./src/node-addon-api/hello.cc"
      ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]      
    }
  ]
}

Test with js:

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

console.log(addon.hello());
// output: world

function auguments

通过js调用c++ addon实现带有参数的函数。
c++:

# addon.cpp

#include <napi.h>

Napi::Value Add(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  if (info.Length() < 2) {
    Napi::TypeError::New(env, "Wrong number of arguments")
        .ThrowAsJavaScriptException();
    return env.Null();
  }

  if (!info[0].IsNumber() || !info[1].IsNumber()) {
    Napi::TypeError::New(env, "Wrong arguments").ThrowAsJavaScriptException();
    return env.Null();
  }

  double arg0 = info[0].As<Napi::Number>().DoubleValue();
  double arg1 = info[1].As<Napi::Number>().DoubleValue();
  Napi::Number num = Napi::Number::New(env, arg0 + arg1);

  return num;
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports.Set(Napi::String::New(env, "add"), Napi::Function::New(env, Add));
  return exports;
}

NODE_API_MODULE(addon, Init)

c++

# addon.h

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <napi.h>

class MyObject : public Napi::ObjectWrap<MyObject> {
 public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
  MyObject(const Napi::CallbackInfo& info);

 private:
  static Napi::FunctionReference constructor;

  Napi::Value GetValue(const Napi::CallbackInfo& info);
  Napi::Value PlusOne(const Napi::CallbackInfo& info);
  Napi::Value Multiply(const Napi::CallbackInfo& info);

  double value_;
};

#endif

Test with js:

var addon = require('bindings')('addon.node')

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

function callbacks

通过c++ addon回调给js函数:
c++:

# addon.cpp

#include <napi.h>

void RunCallback(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::Function cb = info[0].As<Napi::Function>();
  cb.Call(env.Global(), {Napi::String::New(env, "hello world")});
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  return Napi::Function::New(env, RunCallback);
}

NODE_API_MODULE(addon, Init)

Test with js:

var addon = require('bindings')('addon');

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

object factory

通过c++函数创建js对象并返回值:

#include <napi.h>

Napi::Object CreateObject(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::Object obj = Napi::Object::New(env);
  obj.Set(Napi::String::New(env, "msg"), info[0].ToString());

  return obj;
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  return Napi::Function::New(env, CreateObject, "createObject");
}

NODE_API_MODULE(addon, Init)

Test with is:

var addon = require('bindings')('addon');

var obj1 = addon('hello');
var obj2 = addon('world');

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

function factory

通过c++函数创建js函数,并返回结果:

#include <napi.h>

Napi::String MyFunction(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  return Napi::String::New(env, "hello world");
}

Napi::Function CreateFunction(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::Function fn = Napi::Function::New(env, MyFunction, "theFunction");
  return fn;
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
  return Napi::Function::New(env, CreateFunction, "createObject");
}

NODE_API_MODULE(addon, Init)

Test with js:

var addon = require('bindings')('addon');

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

object wrap

通过c++创建js的class,并调用构造函数得到class上的结果:
addon.cc:

# addon.cc

#include <napi.h>
#include "myobject.h"

Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
  return MyObject::Init(env, exports);
}

NODE_API_MODULE(addon, InitAll)

myObject.cc:

#include "myobject.h"

Napi::FunctionReference MyObject::constructor;

Napi::Object MyObject::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func =
      DefineClass(env,
                  "MyObject",
                  {InstanceMethod("plusOne", &MyObject::PlusOne),
                   InstanceMethod("value", &MyObject::GetValue),
                   InstanceMethod("multiply", &MyObject::Multiply)});

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("MyObject", func);
  return exports;
}

MyObject::MyObject(const Napi::CallbackInfo& info)
    : Napi::ObjectWrap<MyObject>(info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);

  int length = info.Length();

  if (length <= 0 || !info[0].IsNumber()) {
    Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
    return;
  }

  Napi::Number value = info[0].As<Napi::Number>();
  this->value_ = value.DoubleValue();
}

Napi::Value MyObject::GetValue(const Napi::CallbackInfo& info) {
  double num = this->value_;

  return Napi::Number::New(info.Env(), num);
}

Napi::Value MyObject::PlusOne(const Napi::CallbackInfo& info) {
  this->value_ = this->value_ + 1;

  return MyObject::GetValue(info);
}

Napi::Value MyObject::Multiply(const Napi::CallbackInfo& info) {
  Napi::Number multiple;
  if (info.Length() <= 0 || !info[0].IsNumber()) {
    multiple = Napi::Number::New(info.Env(), 1);
  } else {
    multiple = info[0].As<Napi::Number>();
  }

  Napi::Object obj = constructor.New(
      {Napi::Number::New(info.Env(), this->value_ * multiple.DoubleValue())});

  return obj;
}

myObject.h:

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <napi.h>

class MyObject : public Napi::ObjectWrap<MyObject> {
 public:
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
  MyObject(const Napi::CallbackInfo& info);

 private:
  static Napi::FunctionReference constructor;

  Napi::Value GetValue(const Napi::CallbackInfo& info);
  Napi::Value PlusOne(const Napi::CallbackInfo& info);
  Napi::Value Multiply(const Napi::CallbackInfo& info);

  double value_;
};

#endif

Test with js:

var addon = require('bindings')('addon');

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

console.log( obj.multiply().value() ); // 13
console.log( obj.multiply(10).value() ); // 130

var newobj = obj.multiply(-1);
console.log( newobj.value() ); // -13
console.log( obj === newobj ); // false

passing wrapped

通过js创建c++ addon对象,并分别调用对象的结果值相加返回:
addon.cc:

#include <napi.h>
#include "myobject.h"

using namespace Napi;

Napi::Object CreateObject(const Napi::CallbackInfo& info) {
  return MyObject::NewInstance(info[0]);
}

Napi::Number Add(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  MyObject* obj1 =
      Napi::ObjectWrap<MyObject>::Unwrap(info[0].As<Napi::Object>());
  MyObject* obj2 =
      Napi::ObjectWrap<MyObject>::Unwrap(info[1].As<Napi::Object>());
  double sum = obj1->Val() + obj2->Val();
  return Napi::Number::New(env, sum);
}

Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
  MyObject::Init(env, exports);

  exports.Set(Napi::String::New(env, "createObject"),
              Napi::Function::New(env, CreateObject));

  exports.Set(Napi::String::New(env, "add"), Napi::Function::New(env, Add));

  return exports;
}

NODE_API_MODULE(addon, InitAll)

myObject.cc:

#include "myobject.h"
#include <napi.h>
#include <uv.h>

MyObject::MyObject(const Napi::CallbackInfo& info)
    : Napi::ObjectWrap<MyObject>(info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);

  this->val_ = info[0].As<Napi::Number>().DoubleValue();
};

Napi::FunctionReference MyObject::constructor;

void MyObject::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func = DefineClass(env, "MyObject", {});

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("MyObject", func);
}

Napi::Object MyObject::NewInstance(Napi::Value arg) {
  Napi::Object obj = constructor.New({arg});
  return obj;
}

myObject.h:

#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <napi.h>

class MyObject : public Napi::ObjectWrap<MyObject> {
 public:
  static void Init(Napi::Env env, Napi::Object exports);
  static Napi::Object NewInstance(Napi::Value arg);
  double Val() const { return val_; }
  MyObject(const Napi::CallbackInfo& info);

 private:
  static Napi::FunctionReference constructor;
  double val_;
};

#endif

Test with js:

var addon = require('bindings')('addon');

//  创建c++ object
var obj1 = addon.createObject(10);
//  创建c++ object2
var obj2 = addon.createObject(20);
// 通过c++ function进行对象值的累加求和
var result = addon.add(obj1, obj2);

console.log(result); // 30

array buffer to native

通过js创建array buffer,传递给c++ addon并做处理;

#include <napi.h>
#include <stdio.h>

static void ArrayConsumer(const int32_t* array, size_t length) {
  for (size_t index = 0; index < length; index++) {
    fprintf(stderr, "array[%lu] = %d\n", index, array[index]);
  }
}

static Napi::Value AcceptArrayBuffer(const Napi::CallbackInfo& info) {
  if (info.Length() != 1) {
    Napi::Error::New(info.Env(), "Expected exactly one argument")
        .ThrowAsJavaScriptException();
    return info.Env().Undefined();
  }
  if (!info[0].IsArrayBuffer()) {
    Napi::Error::New(info.Env(), "Expected an ArrayBuffer")
        .ThrowAsJavaScriptException();
    return info.Env().Undefined();
  }

  Napi::ArrayBuffer buf = info[0].As<Napi::ArrayBuffer>();

  ArrayConsumer(reinterpret_cast<int32_t*>(buf.Data()),
                buf.ByteLength() / sizeof(int32_t));

  return info.Env().Undefined();
}

static Napi::Object Init(Napi::Env env, Napi::Object exports) {
  exports["AcceptArrayBuffer"] = Napi::Function::New(env, AcceptArrayBuffer);
  return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

Test with js:

const binding = require('bindings')('array_buffer_to_native');
const array = new Int32Array(10);

array[0] = 19;
array[1] = -41;
array[2] = 98;
array[3] = -922;
array[4] = 587;
array[5] = 12;
array[6] = 221;
array[7] = 49;
array[8] = -96;
array[9] = -1;

binding.AcceptArrayBuffer(array.buffer);

function reference demo

通过c++持久化callback函数,并通过c++代理执行函数;

#include <napi.h>
#include "native-addon.h"

Napi::Object Init(Napi::Env env, Napi::Object exports) {
    NativeAddon::Init(env, exports);
    return exports;
}

NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

native-addon.cc:

#include "native-addon.h"
#include <iostream>

Napi::FunctionReference NativeAddon::constructor;

Napi::Object NativeAddon::Init(Napi::Env env, Napi::Object exports) {
  Napi::HandleScope scope(env);

  Napi::Function func = DefineClass(env, "NativeAddon", {
    InstanceMethod("tryCallByStoredReference", &NativeAddon::TryCallByStoredReference),
    InstanceMethod("tryCallByStoredFunction", &NativeAddon::TryCallByStoredFunction)
  });

  constructor = Napi::Persistent(func);
  constructor.SuppressDestruct();

  exports.Set("NativeAddon", func);
  return exports;
}

NativeAddon::NativeAddon(const Napi::CallbackInfo& info) 
: Napi::ObjectWrap<NativeAddon>(info)  {
  jsFnRef = Napi::Persistent(info[0].As<Napi::Function>());
  jsFn = info[1].As<Napi::Function>();
}

void NativeAddon::TryCallByStoredReference(const Napi::CallbackInfo& info) {
    // Napi::Env env = info.Env();
    jsFnRef.Call({});
}

void NativeAddon::TryCallByStoredFunction(const Napi::CallbackInfo& info) {
    // Napi::Env env = info.Env();
    jsFn.Call({});
}

native-addon.h:

#include <napi.h>

class NativeAddon : public Napi::ObjectWrap<NativeAddon> {
    public:
        static Napi::Object Init(Napi::Env env, Napi::Object exports);
        NativeAddon(const Napi::CallbackInfo& info);

    private:
        static Napi::FunctionReference constructor;
        Napi::FunctionReference jsFnRef;
        Napi::Function jsFn;

        void TryCallByStoredReference(const Napi::CallbackInfo& info);
        void TryCallByStoredFunction(const Napi::CallbackInfo& info);
};

Test with js:

'use strict'

const { NativeAddon } = require('bindings')('addon')

function JSFnRef() {
    console.log('Hi! I\'m a JS function reference!')
}

function JSFn() {
    console.log('Hi! I\'m a JS function!')
}

const ITERATIONS = 5

const addon = new NativeAddon(JSFnRef, JSFn)

for (let i = 0; i < ITERATIONS; i++) {
    addon.tryCallByStoredReference()
}

try {
    addon.tryCallByStoredFunction()
} catch (err) {
    console.error('This code crashes because JSFn is valid only inside the constructor function.')
    console.error('The lifespan of the JSFn function is limited to the execution of the constructor function.')
    console.error('After that, the function stored in JSFn is ready to be garbage collected and it cannot be used anymore.')
}