NAN(Native Abstractions for Node.js)でnode.jsのアドオン



# Node.jsのアドオン

Node.jsでは、C/C++で書いたネイティブのコードをアドオンとして組み込むことができます。C/C++で書かれた共有オブジェクトをnode.jsでロードすることで実現します。
下記がそのドキュメントです。
https://nodejs.org/dist/latest-v6.x/docs/api/addons.html


# NAN

Nodeのアドオン(NativeのコードをNode.jsで利用するしくみ)はV8に依存しており、困ったことにバージョンアップごとにV8の変更に影響されてしまします。そのため後方互換性が保たれないので、場合によってはバージョンアップすると大きく変更しなければならいことがあります。
そこで、NANというモジュールをかませることによって、Nodeのアドオンがnode.jsのバージョンに依存しないようになり、node.jsのバージョンに依存しないコードが書けるようになります。

Node.jsのアドオンはこちらを利用して書くのが良いと思います。

# NANでのアドオン実装

## 準備

```shell-session:
$ npm install -g node-gyp
$ npm install --save nan
```

アドオンのビルドにはnode-gypが必要です。
またNANを使う場合はnpmでモジュールをダウンロードしてください。


## よくある"world"という文字列を返す hello() というモジュールの実装

ビルドに必要なgypファイルです。
NANを使うにはinclude_dirsが必要です。

```json:binding.gyp
{
  "targets": [
    {
      "target_name": "addon",
      "sources": ["addon.cc" ],
       "include_dirs" : [
        "<!(node -e \"require('nan')\")"
      ]
    }
  ]
}
```

アドオンの実装は以下になります。
NAN_METHODマクロでメソッドの実装、
NAN_MODULE_INITでは初期化としてメソッドの登録、
NODE_MODULE()で初期化関数を指定します。

Methodメソッドで"world"という文字列を作って返却してます。

```c++:addon.cc
#include <nan.h>

namespace demo {
  NAN_METHOD(Method) {
    info.GetReturnValue().Set(Nan::New("world").ToLocalChecked());
  }

  NAN_MODULE_INIT(init) {
    Nan::SetMethod(target, "hello", Method);
  }

  NODE_MODULE(addon, init)
}
```

これをビルドするには

```shell-session:
$ node-gyp configure build
```

とします。
上記により、buildフォルダのRelease以下にaddon.nodeができあがります。
JavaScriptで呼び出すには、

```javascript:hello.js
var addon = require('./build/Release/addon');
var ret = addon.hello();
console.log(ret);
```

のようにします。


## 足し算を実装してみる(引数が必要なケースの実装)

足し算をするメソッド add(x, y) を実装します。
足し算なので、Number型の引数が2つ必要になります。

```C++:addon.cc
#include <nan.h>

namespace demo {
  NAN_METHOD(Add) {
    if (info.Length() < 2) {
      Nan::ThrowTypeError("Wrong number of arguments");
      return;
    }

    if (!info[0]->IsNumber() || !info[1]->IsNumber()) {
      Nan::ThrowTypeError("Wrong arguments");
      return;
    }
    
    double value = info[0]->NumberValue() + info[1]->NumberValue();
    info.GetReturnValue().Set(Nan::New(value));
  }

  NAN_MODULE_INIT(init) {
    Nan::SetMethod(target, "add", Add);
  }

  NODE_MODULE(addon, init)
}
```

引数の情報はinfoに配列として渡っており、引数はinfo[0], info[1]のような形で取り出せます。
Numberの場合 info[0]->NumberValue()の形でとれます。
引数がNumberじゃないと足し算できないため、型チェクをinfo[0]->IsNumber()という形で行ってます。

テストコード

```javascript:hello.js
var addon = require('./build/Release/addon');
var ret = addon.add(1, 2);
console.log(ret);
try {
  addon.add(1);
} catch (e) {
  console.log(e);
}

try {
  addon.add(1, "hello");
} catch (e) {
  console.log(e);
}
```

を実行すると

```shell-session:
3
[TypeError: Wrong number of arguments]
[TypeError: Wrong arguments]
```

となります。

## コールバック関数を使ってみる

上記の足し算をコールバック関数で結果を返すようにしてみます。
第3引数にコールバック関数を渡すようにして、そこに結果を返します。

```C++:addon.cc
#include <nan.h>

namespace demo {
  NAN_METHOD(Call) {
    if (info.Length() < 3) {
      Nan::ThrowTypeError("Wrong number of arguments");
      return;
    }

    if (!info[0]->IsNumber() || !info[1]->IsNumber() || !info[2]->IsFunction()) {
      Nan::ThrowTypeError("Wrong arguments");
      return;
    }

    Nan::Callback *callback = new Nan::Callback(info[2].As<v8::Function>());
    double value = info[0]->NumberValue() + info[1]->NumberValue();
    const int argc = 1;
    v8::Local<v8::Value> argv[argc] = { Nan::New(value) };
    callback->Call(argc, argv);
  }

  NAN_MODULE_INIT(init) {
    Nan::SetMethod(target, "call", Call);
  }

  NODE_MODULE(addon, init)
}
```

Nan::Callbackで受け取ったCallbackからNan：：Callbackオブジェクトをつくり、そをにCallメソッドで呼び出します。
コールバック関数には引数情報(argc,argv)を渡して上げる必要があります。

```javascript:hello.js
var addon = require('./build/Release/addon');
addon.call(1, 2, function(value){
  console.log(value);
});

try {
  addon.call(1, 2);
} catch (e) {
  console.log(e);
}

try {
  addon.call(1, 2, "hello");
} catch (e) {
  console.log(e);
}
```

```shell-session:
3
[TypeError: Wrong number of arguments]
[TypeError: Wrong arguments]
```

## 非同期に処理をする

上記は同期的にコールバックを読んでしまいましたが、
非同期にコールバックを呼び出すにはAsyncWorkerというしくみが必要です。

### AsyncWorker

非同期処理にはNan::AsyncWorker を継承したクラスを定義し、それを実装します。
AsyncWorkerはExecute()で非同期処理の内容を実行し、結果をどうするかをHandleOKCallback()で実行します。

サンプルは以下です。

```C++:addon.cc
#include <nan.h>

namespace demo {
  class AddWorker : public Nan::AsyncWorker {
  public:
    AddWorker(Nan::Callback *callback, double value1, double value2) :
      Nan::AsyncWorker(callback), value1(value1), value2(value2), result(0) {}
    ~AddWorker() {}

    void Execute() {
      result = value1 + value2;
    }

    void HandleOKCallback() {
      Nan::HandleScope scope;
      const int argc = 1;
      v8::Local<v8::Value> argv[argc] = { Nan::New(result) };
      callback->Call(argc, argv);
    }
  private:
    double value1, value2, result;
  };

  NAN_METHOD(Call) {
    if (info.Length() < 3) {
      Nan::ThrowTypeError("Wrong number of arguments");
      return;
    }

    if (!info[0]->IsNumber() || !info[1]->IsNumber() || !info[2]->IsFunction()) {
      Nan::ThrowTypeError("Wrong arguments");
      return;
    }

    Nan::Callback *callback = new Nan::Callback(info[2].As<v8::Function>());
    Nan::AsyncQueueWorker(new AddWorker(callback, info[0]->NumberValue(), info[1]->NumberValue()));
  }

  NAN_MODULE_INIT(init) {
    Nan::SetMethod(target, "call", Call);
  }

  NODE_MODULE(addon, init)
}
```

上記はExecuteで足し算を行い、HandleOKCallbackで結果を渡すコールバックを呼び出しています。

## ObjectWrap

C++ のクラスをJavaScriptからnewでインスタンス化するためにはオブジェクトをWrapして上げる必要があり、
これは、ObjectWrapで継承したクラスにより実現できます。

NANの場合は、Nan::ObjectWrapを継承させます。
ObjectWrapを継承したMyObjectを用意します。

```C++:myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <nan.h>

namespace demo {
  class MyObject: public Nan::ObjectWrap {
  public:
    static NAN_MODULE_INIT(Init);

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

    static NAN_METHOD(New);
    static NAN_METHOD(GetValue);
    static Nan::Persistent<v8::Function> constructor;
    double value_;

  };
}
#endif
```

```C++:myobject.cc
#include "myobject.h"

namespace demo {
  Nan::Persistent<v8::Function> MyObject::constructor;

  NAN_MODULE_INIT(MyObject::Init) {

    //コンストラクターテンプレートを準備する
    v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
    tpl->SetClassName(Nan::New("MyObject").ToLocalChecked());
    tpl->InstanceTemplate()->SetInternalFieldCount(1);

    //プロトタイプ
    SetPrototypeMethod(tpl, "getValue", GetValue);

    constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked());
    Nan::Set(target, Nan::New("MyObject").ToLocalChecked(),
      Nan::GetFunction(tpl).ToLocalChecked());
  }

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

  NAN_METHOD(MyObject::New) {
    if (info.IsConstructCall()) {
      double value = info[0]->IsUndefined() ? 0 : Nan::To<double>(info[0]).FromJust();
      MyObject* obj = new MyObject(value);
      obj->Wrap(info.This());
      info.GetReturnValue().Set(info.This());
    } else {
      const int argc = 1;
      v8::Local<v8::Value> argv[argc] = {info[0]};
      v8::Local<v8::Function> cons = Nan::New(constructor);
      info.GetReturnValue().Set(cons->NewInstance(argc, argv));
    }
  }

  NAN_METHOD(MyObject::GetValue) {
    MyObject* obj = Nan::ObjectWrap::Unwrap<MyObject>(info.Holder());
    info.GetReturnValue().Set(obj->value_);
  }
}

```

ちょっとコンストラクタを作るのにFunctionTemplateなるものを用意したり、おまじない的なコードが多いのですが、
GetValueでNewで設定した値を取得できます。

後は、

```C++:addon.cc
#include <nan.h>
#include "myobject.h"

namespace demo {
  NAN_MODULE_INIT(init) {
    MyObject::Init(target);
  }

  NODE_MODULE(addon, init)
}
```

```json:binding.gyp
{
  "targets": [
    {
      "target_name": "addon",
      "sources": ["addon.cc", "myobject.cc" ],
       "include_dirs" : [
        "<!(node -e \"require('nan')\")"
      ]
    }
  ]
}
```

と記述してビルドすれば、

```javascript:hello.js
var addon = require('./build/Release/addon');
var myobject = new addon.MyObject(10);
console.log(myobject.getValue());  //10を出力

```

でObjectが作れるのを確認できます。

## イベントを登録・発火する

最後にイベントを登録する方法です。
ObjectWrapを継承したクラスにコールバックをもたせるようにして、
イベントを発火させるときはそのコールバックをイベント名と主にコールします。
下記はplusOne()というメソッドで値を+1したら、"result"イベントを発火してそこで結果を返すようにします。


```C++:myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <nan.h>

namespace demo {
  class MyObject: public Nan::ObjectWrap {
  public:
    static NAN_MODULE_INIT(Init);

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

    static NAN_METHOD(New);
    static NAN_METHOD(GetValue);
    static NAN_METHOD(PlusOne);
    static Nan::Persistent<v8::Function> constructor;
    double value_;
    Nan::Callback *emit = nullptr;
  };
}
#endif

```



```C++:myobject.cc
#include "myobject.h"

namespace demo {
  Nan::Persistent<v8::Function> MyObject::constructor;

  NAN_MODULE_INIT(MyObject::Init) {

    //コンストラクターテンプレートを準備する
    v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
    tpl->SetClassName(Nan::New("MyObject").ToLocalChecked());
    tpl->InstanceTemplate()->SetInternalFieldCount(1);

    //プロトタイプ
    SetPrototypeMethod(tpl, "getValue", GetValue);
    SetPrototypeMethod(tpl, "plusOne", PlusOne);

    constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked());
    Nan::Set(target, Nan::New("MyObject").ToLocalChecked(),
      Nan::GetFunction(tpl).ToLocalChecked());
  }

  MyObject::MyObject(double value) : value_(value) {}
  MyObject::~MyObject() {
    if (emit != nullptr) {
      delete emit;
    }
  }

  NAN_METHOD(MyObject::New) {
    if (info.IsConstructCall()) {
      double value = info[0]->IsUndefined() ? 0 : Nan::To<double>(info[0]).FromJust();
      MyObject* obj = new MyObject(value);
      obj->Wrap(info.This());

      //イベントを登録できるようにする
      obj->emit = new Nan::Callback(
        v8::Local<v8::Function>::Cast(obj->handle()->Get(Nan::New("emit").ToLocalChecked()))
      );

      info.GetReturnValue().Set(info.This());
    } else {
      const int argc = 1;
      v8::Local<v8::Value> argv[argc] = {info[0]};
      v8::Local<v8::Function> cons = Nan::New(constructor);
      info.GetReturnValue().Set(cons->NewInstance(argc, argv));
    }
  }

  NAN_METHOD(MyObject::GetValue) {
    MyObject* obj = Nan::ObjectWrap::Unwrap<MyObject>(info.Holder());
    info.GetReturnValue().Set(obj->value_);
  }

  NAN_METHOD(MyObject::PlusOne) {
    MyObject* obj = Nan::ObjectWrap::Unwrap<MyObject>(info.Holder());
    obj->value_ += 1;

    //resultイベントを発火して結果を渡す
    v8::Local<v8::Value> argv[] = { Nan::New("result").ToLocalChecked(), Nan::New(obj->value_) };
    obj->emit->Call(obj->handle(), 2, argv);
  }
}

```

あとは、onでイベントが登録できるようにEventEmitterのプロトタイプを継承するようにします。
下記のようにします。


```javascript:hello.js
var EventEmitter = require('events').EventEmitter;
var addon = require('./build/Release/addon');

addon.MyObject.prototype.__proto__ = EventEmitter.prototype;

var myobject = new addon.MyObject(10);
myobject.on('result', function(ret) {
  console.log(ret);
});
myobject.plusOne();  //11を出力
myobject.plusOne();  //12を出力
```

以上です。
NANをつかって楽しいNode.jsのaddonライフを!