Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

前準備

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

$ npm install -g node-gyp

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

  • Python 2.7 系 (Python3は非対応!)
  • コンパイル環境
    • make (Unix)
    • XCode (Mac OSX)
    • Visual Studio 2013 (Windows)

(より詳しい情報はこちら(英語)

これで準備は完了です.

Hello, world してみる

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

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 を以下のような内容で作ります.

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

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

$ node-gyp configure
$ node-gyp build

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

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

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

console.log(myExtension.hello()); // hello, world が出力される
実行例
$ node example.js
hello, world

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

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

NANの導入

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

そこでその差異を吸収してくれるのが NANというライブラリです.

NANについては以下のページがわかりやすいです
node.jsのnative addonを作るときはNANを使おう。 - from scratch

NANのドキュメント にしたがってインストールを行います.
npm で nan をインストールして,binding.gyp に1行付け足すだけでOKです.

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

hello, world をNANで書き換える

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

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にある作成例 などを参考に真似していくと良いとおもいます.

コールバック型非同期関数を作る

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

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

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...");
実行例
$ node example_addAsync.js
waiting...
3 # <= 3秒後に表示される

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

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::AsyncQueueWorkernew で渡すと処理が始まるという仕組みです.

npmに登録する

最後に,作成したネイティブ拡張をライブラリとして npm に登録してみます.

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

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

サンプルコードでは require('./build/Release/my_extension') という書き方をしていましたが,この ./build/Release/ を環境に応じて自動設定してくれる bindings に置き換えます.

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

$ npm instlal --save bindings

プロジェクトを初期化する

これはネイティブ関係なく,ライブラリを公開する場合は必要な作業です.

$ npm init

npmに公開

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

$ npm publish

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

まとめ

  • OSにかかわらず Node.js のネイティブ拡張は node-gyp を用いて作成できる
  • Node.js のバージョン間の差異を埋めてくれる NAN を使うと良い
  • ライブラリとして公開するのも簡単
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした