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"
という文字列を返すだけの関数を作ります.見慣れないクラスや単語が出てきますが,見て見ぬふりをしましょう!
#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
を以下のような内容で作ります.
{
"targets": [
{
"target_name": "my_extension",
"sources": ["my_extension.cc"]
}
]
}
この2つのファイルが準備出来たら以下のコマンドでプロジェクト作成→ビルドを行います.
$ node-gyp configure
$ node-gyp build
以上のコマンドでエラーが出なければ,build/
フォルダが作成されて build/Release/my_extension.node
というファイルが作成されます.
これを Node.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
{
"targets": [
{
"target_name": "my_extension",
"sources": ["my_extension.cc"],
+ "include_dirs": ["<!(node -e \"require('nan')\")"]
}
]
}
hello, world をNANで書き換える
先ほど作ったhello, worldの例を NAN 仕様に書き換えてみます.
互換性が高くなっただけでなく,コードもシンプルになっていて嬉しいですね!
#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
関数をコールバック方式で作ります.
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
というクラスを継承したものを使って実現できます.
#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 に登録してみます.
はじめにライブラリとしてrequire()した時のエントリポイントとなるファイル 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のネイティブ拡張を公開することができました!