Node.jsのネイティブ拡張を作ってみよう 〜NAN, 非同期処理, npm公開まで〜

Node.js では C++ を用いた拡張機能を作ることができます．
実際に作ってみたところ，意外と情報が少なく苦労したのでまとめておきます！

## 前準備
以下のコマンドで `node-gyp` をインストールします．
これは Node.js 拡張で用いるビルドツール的なものです．

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

`node-gyp` を動作させるためには以下のツールが必要です．

- Python **2.7** 系 （Python3は非対応！）
- コンパイル環境
  - `make` (Unix)
  - XCode (Mac OSX)
  - Visual Studio **2013** (Windows)

（より詳しい情報は[こちら（英語）](https://github.com/nodejs/node-gyp#installation)）

これで準備は完了です．

## `Hello, world` してみる
例として `"Hello, world"` という文字列を返すだけの関数を作ります．見慣れないクラスや単語が出てきますが，見て見ぬふりをしましょう！

```cpp:my_extension.cc
#include <node.h>

void HelloFunction(const v8::FunctionCallbackInfo<v8::Value>& args)
{
    v8::Isolate* isolate = args.GetIsolate();
    // 返り値を設定
    args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "hello, world"));
}

void init(v8::Local<v8::Object> exports)
{
    // hello 関数を外部に公開
    NODE_SET_METHOD(exports, "hello", HelloFunction);
}

NODE_MODULE(my_extension, init);
```

また，`node-gyp` 用のビルド設定ファイル `binding.gyp` を以下のような内容で作ります．

```json:binding.gyp
{
  "targets": [
    {
      "target_name": "my_extension",
      "sources": ["my_extension.cc"]
    }
  ]
}
```

この2つのファイルが準備出来たら以下のコマンドでプロジェクト作成→ビルドを行います．

```zsh
$ node-gyp configure
$ node-gyp build
```

以上のコマンドでエラーが出なければ，`build/` フォルダが作成されて `build/Release/my_extension.node` というファイルが作成されます．

これを Node.js から使ってみましょう．

```js:example.js
var myExtension = require('./build/Release/my_extension');

console.log(myExtension.hello()); // hello, world が出力される
```

```zsh:実行例
$ node example.js
hello, world
```

これで最低限の環境が整いました．
この時点でディレクトリ構造は以下のようになります．

```
.
├── binding.gyp
├── build/
├── example.js
└── my_extension.cc
```

## NANの導入
Node.jsは進化が早いのでバージョンアップによって短期間で仕様が変わってしまうことがあります．
どうやらネイティブ拡張のお作法も最近変わったらしく，その度に仕様を合わせるのは大変です．

そこでその差異を吸収してくれるのが [NAN](https://github.com/nodejs/nan)というライブラリです．

NANについては以下のページがわかりやすいです
[node.jsのnative addonを作るときはNANを使おう。 - from scratch](http://yosuke-furukawa.hatenablog.com/entry/2014/04/07/205935)

[NANのドキュメント](https://github.com/nodejs/nan/blob/master/README.md#usage) にしたがってインストールを行います．
npm で `nan` をインストールして，`binding.gyp` に1行付け足すだけでOKです．

```zsh
$ npm install --save nan
```

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

### hello, world をNANで書き換える
先ほど作ったhello, worldの例を NAN 仕様に書き換えてみます．
互換性が高くなっただけでなく，コードもシンプルになっていて嬉しいですね！

```cpp:my_extension.cc
#include <nan.h>

NAN_METHOD(hello)
{
    // 返り値を設定
    info.GetReturnValue().Set(Nan::New("hello, world").ToLocalChecked());
}

NAN_MODULE_INIT(init)
{
    // hello 関数を外部に公開
    NAN_EXPORT(target, hello);
}

NODE_MODULE(my_extension, init);
```

`NAN_` から始まるマクロが急に出てきて「？？？」となるかもしれませんが，これは [githubにある作成例](https://github.com/nodejs/nan/tree/master/examples/async_pi_estimate) などを参考に真似していくと良いとおもいます．

## コールバック型非同期関数を作る
次はNode.jsのライブラリによくある非同期処理の結果を引数のコールバックで受け取る方式を作ります．

例として3秒待ったあとに足し算の結果を返すだけの `addAsync` 関数をコールバック方式で作ります．

```js:example_addAsync.js
var myExtension = require('./build/Release/my_extension');

myExtension.addAsync(1, 2, function(err, result) {
    if (!err) {
        console.log(result);
    }
});
console.log("waiting...");
```

```zsh:実行例
$ node example_addAsync.js
waiting...
3 # <= 3秒後に表示される
```

以上の例のような動作をする `addAsync` を実装します．
NANの `AsyncWorker` というクラスを継承したものを使って実現できます．

```cpp:my_extension.cc
#include <nan.h>
#include <unistd.h>

class AddAsyncWorker : public Nan::AsyncWorker
{
public:
    AddAsyncWorker(int left, int right, Nan::Callback* callback)
        : Nan::AsyncWorker(callback), left(left), right(right)
    {}

    // 非同期処理の中身
    void Execute()
    {
        // 3秒待つ ※Windowsでは unistd.h の代わりに Windows.h の Sleep を使わないといけないかも
        sleep(3);
        result = left + right;
    }

    // 非同期処理が完了したとき呼び出される
    void HandleOKCallback()
    {
        v8::Local<v8::Value> callbackArgs[] = {
            Nan::Null(),
            Nan::New(result),
        };

        // コールバック呼び出し
        callback->Call(2, callbackArgs);
    }

private:
    int left;
    int right;
    int result;
};

NAN_METHOD(addAsync)
{
    // 非同期処理を開始
    auto left = info[0]->Int32Value();
    auto right = info[1]->Int32Value();
    auto callback = new Nan::Callback(info[2].As<v8::Function>());
    Nan::AsyncQueueWorker(new AddAsyncWorker(left, right, callback));
}

NAN_MODULE_INIT(init)
{
    // add 関数を外部に公開
    NAN_EXPORT(target, addAsync);
}

NODE_MODULE(my_extension, init);
```

コンストラクタで処理に必要な情報とコールバックを受け取り，関数の処理が始まると `Execute()` が呼ばれ，処理が完了すると `HandleOKCallback()` が呼ばれます．
値は クラスの private フィールドを使って受け渡しをすると楽です．

このクラスを `Nan::AsyncQueueWorker` に `new` で渡すと処理が始まるという仕組みです．

## npmに登録する
最後に，作成したネイティブ拡張をライブラリとして [npm](https://www.npmjs.com/) に登録してみます．

はじめにライブラリとしてrequire()した時のエントリポイントとなるファイル `index.js` を用意します．

```js:index.js
module.exports = require('bindings')('my_extension');
```

サンプルコードでは `require('./build/Release/my_extension')` という書き方をしていましたが，この `./build/Release/` を環境に応じて自動設定してくれる [bindings](https://github.com/TooTallNate/node-bindings) に置き換えます．

※ bindingsのインストールは以下のコマンドです．

```zsh
$ npm instlal --save bindings
```

### プロジェクトを初期化する
これはネイティブ関係なく，ライブラリを公開する場合は必要な作業です．

```zsh
$ npm init
```

## npmに公開
以下のコマンドでnpmに公開します．

```zsh
$ npm publish
```

これでNode.jsのネイティブ拡張を公開することができました！

## まとめ

- OSにかかわらず Node.js のネイティブ拡張は [node-gyp](https://github.com/nodejs/node-gyp) を用いて作成できる
- Node.js のバージョン間の差異を埋めてくれる [NAN](https://github.com/nodejs/nan) を使うと良い
- ライブラリとして公開するのも簡単