libuvを用いた非同期処理を含むNode.js Native Addon入門

# 概要
### Motivation
Node.jsのNative Extensionの解説記事はたくさんあるのですが、情報が古かったり、公式のドキュメントもやや使いづらかったりするので、今回の記事を執筆する運びとなりました。

### この記事が目標としていること
動くサンプルを通じて、Native Addonを作るための最低限の要素に触れ、C++の知識があれば大抵のものは作れるようになります（なるはずです）

最低限の要素とは以下の4つです。

- JavaScript - C++の橋渡し
- V8のAPIの基本的な操作（配列操作、オブジェクトの操作を含む）
- 内部にC++のオブジェクトを持ったJavaScriptのオブジェクトの作成
- 非同期処理

### ついでに実現できること
V8がどう動いているのかがほんの少しわかります。

### バージョン情報
- Mac OS X 10.12.6
- Node.js v9.3.0

### リポジトリ
https://github.com/kentrino/native-node-addon-samples

# 1. はじめの一歩
まずは、C++の関数を呼び出してNodeの文字列を作成し、JSの世界に返すだけのサンプルから入ります。ほぼ公式のドキュメントのままです。

### とりあえずビルドツールを準備する
npmとnodeはインストール済みであるとします。

```
$ npm install -g node-gyp
```

### はじめに何を作るか
これと等価なNodeモジュールを作成します。

```js
module.exports.hello = () => "world";
```

### 実装
##### その壱
```js
function Hello() {
  return "world";
}

```
まずは、上記JavaScriptの関数に対応するコードをC++で書いてみましょう。

```c++
void Hello(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
}
```

全体が型が`v8::FunctionCallbackInfo<v8::Value>`の`args`を引数にとる関数になっています。引数`args`からは、JS側で指定した関数の引数を受け取ることができ、また`args.GetReturnValue().Set` を呼び出してやると、関数の返り値を設定することができます。


##### その弐
```js
module.exports.hello = Hello
```

次に上記コードに対応するC++のコードを書いてみます。

```
void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Hello);
}
```

`NODE_SET_METHOD`というマクロのふりをした関数がよしなにやってくれます（昔はマクロだったらしい）。`v8::Local<v8::Object>`はV8オブジェクトへのポインタを意味し、適当なタイミングでGCの対象となります。

マクロの中身をちょっと覗いてやるとこんな感じになっています。`v8::HandleScope handle_scope(isolate)`を書いてやることによって、関数を抜ける時、`HandleScope`のデストラクタが呼ばれたタイミングで、`Local`型で宣言された変数がGCの対象となるようになります。

```c++
inline void NODE_SET_METHOD(v8::Local<v8::Object> recv,
                            const char* name,
                            v8::FunctionCallback callback) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate,
                                                                callback);
  v8::Local<v8::Function> fn = t->GetFunction();
  v8::Local<v8::String> fn_name = v8::String::NewFromUtf8(isolate, name);
  fn->SetName(fn_name);
  recv->Set(fn_name, fn);
}
```

上では大雑把な解説を書きましたが、実際には上のHello関数の型（`void Hello(const FunctionCallbackInfo<Value>& args)`）をもつ関数は、`v8::FunctionTemplate::New`の引数となることができて、`v8::Local<v8::FunctionTemplate>`の`GetFunction`を呼んでやると、V8上の関数になります。


##### その参
壱および弐がNodeモジュールとして機能するために最後の仕上げが必要です。

```
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
```
マクロです

##### その肆
C++の部分を全部をまとめるとこのようなコードになります。

```c++
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Hello(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", Hello);
}

// このマクロの後にはセミコロンは不要
NODE_MODULE(NODE_GYP_MODULE_NAME, init)

}  // namespace demo
```

##### その伍
Makefileに相当するものをまだ書いてないので、`node-gyp`がきちんと動くように、`binding.gyp`を書いてやります。

```json
{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "index.cc" ]
    }
  ]
}
```

以上で実装は完了です。`node-gyp configure`した後に`node-gyp build`を行うことで、以下のJavaScriptのコードが期待したとおりに動くようになります。

```
const addon = require('./build/Release/addon');
console.log(addon.hello());  // world
```


### 解説
##### Isolateについて
Isolateを用いることで、実行中のJavaScriptのスコープを得ることができます。後にも出てきますが、`HandleScope`がよしなにメモリ管理を行ってくれます。

##### Isolateについてもう少し詳しく
Isolateを経由して現在のスコープを取得することができますが、Isolate自体は独立したV8のランタイムを示すものです。スコープの制御の他に、ヒープ管理機構や、ガベージコレクタも含みます。同時に一つのスレッドしかIsoateにアクセスすることはできませんが、複数のスレッドから操作することができます。

ところが、IsolateだけではV8のランタイムとしては成立せず、Contextというものも準備する必要があります。Contextはルートオブジェクトを用意してくれます。たとえば、iframeの内部などはContextを分けて制御しています。

また、iframeの例からもわかるように、Isolateの内部には複数のContextが存在することができ、かつ、Isolateが排他ロック制御を行ってくれるおかげで、別々のContextに含まれるObjectは簡単にかつ安全に共有することができます。

##### さらにIsolateについて
https://github.com/v8/v8/wiki/Embedder's-Guide に書いてあります。


V8をゼロからまるっと動かすサンプルもあります。
https://github.com/v8/v8/blob/master/samples/hello-world.cc


### 完全なサンプル
https://github.com/kentrino/native-node-addon-samples の`./hello_world` 以下にあります


# 2. つぎ
JavaScriptの世界から値を受け取って、C++側で利用してみましょう。

### 何を作るか
```js
function printEverything() {
  if (typeof arguments.length < 2) {
    throw new TypeError("Wrong number of arguments.");
  }
  if (typeof arguments[0] !== "number") {
    throw new TypeError("Argument #1 must be a number.");
  }

  if (typeof arguments[1] !== "string") {
    throw new TypeError("Argument #2 must be a string.");
  }
  console.log(`${arguments[0]}, ${arguments[1]}`);
}

module.exports.printEverything = printEverything;
```
とだいたい等価なモジュールを作ってみましょう（実際は配列とオブジェクトも引数にとり、それらを表示する関数になっています）

- 引数の数、型のチェックがあること
- 例外を投げているところ

が新しい要素です。先程扱っていないところだけを紹介します。

##### 引数の数、型チェックを行う

```c++
  if (args.Length() < 2) {
    //
  }

  if (!args[0]->IsNumber()) {
    //
  }

  if (!args[1]->IsString()) {
    //
  }
```
このようにして、引数の数をみたり、型チェックを行ったりすることができます。

##### V8の例外を投げる

```c++
    isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Argument #2 must be string.")));
```

##### V8の値からC++で扱いやすい形に変換する

数値の場合

```c++
  double number = args[0]->NumberValue();
```

文字列の場合

```c++
  String::Utf8Value string(args[1]);
  std::string cpp_string = std::string(*string);
```

配列の場合

```c++
  Local<Array> array = Local<Array>::Cast(args[2]);
  std::vector<double> vec;
  unsigned int length = array->Length();
  for (unsigned int i = 0; i < length; i++) {
    if (!array->Get(i)->IsNumber()) {
      isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Argument #3 must consist only of number.")));
    }
    vec.push_back((double)Local<Number>::Cast(array->Get(i))->NumberValue());
  }
```


オブジェクトの場合

```c++
  std::map<std::string, std::string> mp;

  Local<Context> context = isolate->GetCurrentContext();
  Local<Array> props = object->GetOwnPropertyNames(context).ToLocalChecked();
  unsigned int length = props->Length();
  for(unsigned int i = 0; i < length; ++i) {
    Local<Value> local_key = props->Get(i);
    Local<Value> local_val = object->Get(context, local_key).ToLocalChecked();
    if (!local_key->IsString() || !local_val->IsString()) {
      isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Argument #4 must have type of { [key: string]: string; }.")));      
    }
    std::string key = *String::Utf8Value(local_key);
    std::string val = *String::Utf8Value(local_val);
    mp[key] = val;
  }
```

### 完全なサンプル
https://github.com/kentrino/native-node-addon-samples の`./print_everything` 以下にあります。

# 3. さらにつぎ
今度は、C++のオブジェクトを内部に含んだJavaScriptのオブジェクトを作ってみましょう。実用的なNative Addonを作るために必ず必要となってくるテクニックです。

### 何を作るか
helloプロパティを呼ぶとworldが返ってくるフレンドリーなスタッククラスを作ってみます。

```
const h = new HelloStack();
h.hello;   // "world"
h.push(3);
h.pop();   // 3
```

### 実装
##### 自前のV8オブジェクトを作るクラスの概要
実際にはV8オブジェクトの内部に収まるクラスを作成します。
クラス定義では、`node::ObjectWrap`を継承します。

```c++
#include <node_object_wrap.h>

class HelloStack : public node::ObjectWrap {
 public:
  // コンストラクタをヒープにロードして、呼び出せるようにする
  // プロトタイプの関数のセット、インスタンスが内部にC++のフィールドを持っていることはここで宣言する
  static void LoadConstructor(v8::Isolate* isolate);

  // インスタンスを作成する。下記のConstructorを直接JavaScript側から叩くことができないので、この関数で仲介する
  static void CreateNewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);

  // プロトタイプ関数の実装
  static void Push(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void Pop(const v8::FunctionCallbackInfo<v8::Value>& args);

  // スタックの内部実装で使うvector
  std::vector<double> stack_;

 private:
  explicit HelloStack();
  ~HelloStack();

  // Constructorの実装。プロパティhelloの作成はここで行う
  static void Constructor(const v8::FunctionCallbackInfo<v8::Value>& args);

  // V8ヒープにのっているコンストラクタ
  static v8::Persistent<v8::Function> constructor;
};
```


##### モジュール作成部分
Init時に`HelloStack::LoadConstructor`を呼んでいます。

```c++
void CreateNewInstance(const FunctionCallbackInfo<Value>& args) {
  HelloStack::CreateNewInstance(args);
}

void Init(Local<Object> exports, Local<Object> module) {
  HelloStack::LoadConstructor(exports->GetIsolate());

  NODE_SET_METHOD(module, "exports", CreateNewInstance);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Init);

}  // namespace demo
```

##### 個々の実装
`InstanceTemplate` はインスタンス化されたときの形状を指定します。

```c++

void HelloStack::LoadConstructor(Isolate* isolate) {
  HandleScope scope(isolate);
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, Constructor);
  tpl->SetClassName(String::NewFromUtf8(isolate, "HelloStack"));
  // V8のオブジェクトには、JavaScriptから見えない内部フィールドを持たせることができ、今回の場合、
  // C++のオブジェクトをラップしたV8オブジェクトを作るので1以上である必要がある *1
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  // プロトタイプの関数を定義する
  NODE_SET_PROTOTYPE_METHOD(tpl, "push", Push);
  NODE_SET_PROTOTYPE_METHOD(tpl, "pop", Pop);

  HelloStack::constructor.Reset(isolate, tpl->GetFunction());
}
```

*1 `Assertion failed: (handle->InternalFieldCount() > 0), function Wrap, file .../include/node/node_object_wrap.h, line 77.` が起こる


```c++
void HelloStack::Constructor(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  HandleScope scope(isolate);

  // new演算子 or Local<Function>#NewInstanceによる呼び出しの場合
  if (args.IsConstructCall()) {
    HelloStack* obj = new HelloStack();
    Local<Object> that = args.This();

    that->Set(String::NewFromUtf8(isolate, "hello"), String::NewFromUtf8(isolate, "world"));

    obj->Wrap(that);
    args.GetReturnValue().Set(that);
    return;
  }
}
```

### 解説
#####　マクロとかの中身

`NODE_SET_PROTOTYPE_METHOD` の実装です。先程の`NODE_SET_METHOD`とほぼ同じことをしていますね（レシーバーが適切かどうかを判定するための`Signature`を設定していますが、どのように機能するのかは調べきれていません）。`PrototypeTemplate`は`SomeObject.prototype` と同義です。

```c++
inline void NODE_SET_PROTOTYPE_METHOD(v8::Local<v8::FunctionTemplate> recv, const char* name, v8::FunctionCallback callback) {
  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::HandleScope handle_scope(isolate);
  v8::Local<v8::Signature> s = v8::Signature::New(isolate, recv);
  v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate, callback, v8::Local<v8::Value>(), s);
  v8::Local<v8::String> fn_name = v8::String::NewFromUtf8(isolate, name);
  t->SetClassName(fn_name);
  recv->PrototypeTemplate()->Set(fn_name, t);
}
```

`obj->Wrap` の中身です。きちんと`MakeWeak()`されているので、V8オブジェクトの破棄時にV8が`HelloStack`をGCしてくれます。

```c++
  inline void Wrap(v8::Local<v8::Object> handle) {
    assert(persistent().IsEmpty());
    assert(handle->InternalFieldCount() > 0);
    handle->SetAlignedPointerInInternalField(0, this);
    persistent().Reset(v8::Isolate::GetCurrent(), handle);
    MakeWeak();
  }
```

##### Persistentなオブジェクトについて
`Local`のオブジェクトは`HandleScope`のデストラクタが呼ばれるタイミングで（= C++の関数を抜けるタイミングで）GCの対象となりますが、`Persistent`のオブジェクトは明示的に`.Reset()` を呼ばない限りGCされることはありません

### 完全なサンプル
https://github.com/kentrino/native-node-addon-samples の`./hello_stack` 以下にあります

# 4. ラスト
いよいよlibuvを用いた非同期処理を含んだ、Native Addonの作成にうつります。今回はフィボナッチ数列を計算してくれる、こんな感じに動作するモジュールを作ります。

```js
computeFibonacci(10, console.log); // 55
```

### 実装
わかりやすいところから始めます。

##### フィボナッチ数列を計算するところ
```c++
std::string ComputeFibonacci(int number) {
  int i;
  long long a, b, c;
  a = 1;
  b = 1;
  if (number == 1) return std::to_string(a);
  if (number == 2) return std::to_string(b);
  for (i = 0; i < number - 2; ++i) {
    c = a + b;
    a = b;
    b = c;
  }
  return std::to_string(c);
}
```

##### まずはデータから

```c++
class AsyncWork {
  public:
    // フィボナッチ数列の計算が終わったあと、JavaScriptの世界へ渡す処理
    // PersistentにしておかないとGCされてしまう
    v8::Persistent<v8::Function> callback;

    // フィボナッチ数列の何項目か
    int number;
    
    // フィボナッチ数列の計算結果を詰める
    std::string result;

    // あとでメモリを解放するために突っ込んである
    uv_work_t uv_request;
};
```

データ型をみると、ある程度処理の様子が想像できると思います。

##### 非同期処理のキューに詰めるところ
`uv_work_t`型の`&work->uv_request`をこのように指定してやります。`uv_work_t`は内部のフィールドに`data`を保持しており、ここに処理するべき内容を突っ込んでやります。
`DoWork`がキューのイベントの非同期処理を行う関数で、`AfterWork`では非同期処理終了後の処理を渡しています。

```c++
uv_queue_work(uv_default_loop(), &work->uv_request, (uv_work_cb)DoWork, (uv_after_work_cb)AfterWork);
```

他の部分と合わせるとこんな感じになります。

```c++
void ComputeFibonacciAsync(const FunctionCallbackInfo<Value> &args) {
  Isolate *isolate = Isolate::GetCurrent();
  HandleScope scope(isolate);

  int number = (args[0]->NumberValue());
  // JavaScriptのコールバック関数
  Local<Function> cb_local = Local<Function>::Cast(args[1]);

  AsyncWork *work = new AsyncWork;
  work->uv_request.data = work;
  work->callback.Reset(isolate, cb_local);
  work->number = number;

  uv_queue_work(uv_default_loop(), &work->uv_request, (uv_work_cb)DoWork, (uv_after_work_cb)AfterWork);
}
```

##### DoWork
キューのイベントを処理するところです。簡単ですね。

```c++
static void DoWork(uv_work_t *req) {
  AsyncWork *work = static_cast<AsyncWork *>(req->data);
  std::string result = ComputeFibonacci(work->number);
  work->result = result;
}
```


##### AfterWork
非同期処理終了後の処理になります。`work` から、`result` を取り出し、その結果をJavaScriptのコールバックに渡してやります。

```c++
static void AfterWork(uv_work_t *req, int status) {
  Isolate *isolate = Isolate::GetCurrent();
  HandleScope scope(isolate);

  AsyncWork *work = static_cast<AsyncWork *>(req->data);
  Local<String> result = String::NewFromUtf8(isolate, work->result.c_str());

  const unsigned argc = 1;
  Local<Value> argv[argc] = {result};
  Local<Function> cb = Local<Function>::New(isolate, work->callback);
  cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
  work->callback.Reset();

  delete work;
}
```

### 完全なサンプル
https://github.com/kentrino/native-node-addon-samples の`./async_fibonacci` 以下にあります

### GMPを使ったバージョン
上のサンプルだと`long long`の範囲でしか計算できないので、GMPを使った任意精度版も用意しました。

https://github.com/kentrino/native-node-addon-samples の`./gmp_async_fibonacci` 以下にあります

##### GMPのインストール
```
$ brew install gmp
```

# その他
NAN(https://github.com/nodejs/nan) を使ったものだが優れたサンプルが豊富にある。

https://github.com/freezer333/nodecpp-demo


# 参考
- https://github.com/nikhilm/uvbook
- https://github.com/v8/v8/wiki/Embedder's%20Guide
- https://nodejs.org/api/addons.html
- https://mattn.kaoriya.net/software/lang/c/20110325015245.htm
- https://stackoverflow.com/questions/19383724/what-exactly-is-the-difference-between-v8isolate-and-v8context
- https://nodeaddons.com/how-not-to-access-node-js-from-c-worker-threads/

# 質問・マサカリ
http://twitter.com/_kentrino

まで気軽にお声掛けください。