全部追いかけてないですが、io.jsのなんやかんやとかNANとかのおかげでnative addonの作り方がだいぶ変わったらしく、そもそもそれ以前の作り方もだいぶ記憶喪失に近いので、改めて公式ドキュメント見ながらまとめてみました。
まとめはじめたら長くなってしまったので、分割して掲載したいと思います。
とりあえずaddonを作る&動かす
C++のソースコードを用意し、node-gypでコンパイルするという大まかな流れは変わっていないようです。
node-gyp のインストール
Node.js本体はインストール済みの&npmが使える状態のものとします。node-gyp
がコンパイルなどをよしなにやってくれるツールなのでこれをインストールします。
$ npm install -g node-gyp
インストール先ディレクトリのPermission問題が起きる場合は sudo
をつけて実行すると良いでしょう。
必要なファイルの用意
同一ディレクトリに hello.cc
, binding.gyp
, test.js
を用意します。
- hello.cc (ほぼ公式のパクリ)
#include <node.h>
namespace demo {
void BlueMethod(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "Yes, Yes, Yes!"));
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "hello", BlueMethod);
}
NODE_MODULE(addon, init)
} // namespace demo
- binding.gyp
{
"targets": [
{
"target_name": "addon",
"sources": [ "hello.cc" ]
}
]
}
- test.js
var addon = require('./build/Release/addon');
console.log(addon.hello());
コンパイル&実行
コンパイルは node-gyp で実施します。
$ node-gyp configure build
二回目以降は build
だけでいいはずです。
$ node-gyp build
test.jsを使って実行します。
$ node test.js
Yes, Yes, Yes!
引数の処理
数値の合計をだす sum
という関数をサンプルにします。const v8::FunctionCallbackInfo<v8::Value>& args
が可変長の引数情報を保持しているので、実行時に都度引数の数や引数のデータ型をチェックする必要があります。
ソースコード
- hello.cc
#include <node.h>
namespace demo {
// const v8::FunctionCallbackInfo<v8::Value>& args が引数になる
void Sum(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
double v = 0;
for (int i = 0; i < args.Length(); i++) {
// 実行時まで引数の型がわからないのでチェック
if (!args[i]->IsNumber()) {
// 数値以外だったら例外を投げる
isolate->ThrowException(v8::Exception::TypeError(v8::String::NewFromUtf8(isolate, "Wrong arguments")));
// そしてreturnで抜ける
return;
}
// 数値データとしてi番目の引数を取り出す
v += args[i]->NumberValue();
}
// JavaScript界の数値データ形式(v8::Number)に変換する
v8::Local<v8::Value> num = v8::Number::New(isolate, v);
// 返り値をセットする
args.GetReturnValue().Set(num);
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "sum", Sum);
}
NODE_MODULE(addon, init)
} // namespace demo
- test.js
var addon = require('./build/Release/addon');
console.log(addon.sum(1, 2, 3));
console.log(addon.sum(1, 2, 3, 4));
実行
$ node test.js
6
10
コールバック関数を引数に持つ関数の処理
処理が完了した時にコールバック関数に処理を返すタイプの関数の扱い方。ただしこのやり方だと非同期ではなく、コールバック関数が呼ばれるまではNode.js上の他の全ての処理(例えばsetIntervalで回しているものとか)が止まるので注意。
ソースコード
- hello.cc
#include <node.h>
namespace demo {
// args[0] にコールバック関数が渡される想定
void RunCallback(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
// 引数の数と型のチェック
if (args.Length() != 1 || !args[0]->IsFunction()) {
isolate->ThrowException(v8::Exception::TypeError(v8::String::NewFromUtf8(isolate, "Wrong arguments")));
return;
}
// 関数型にキャストする
v8::Local<v8::Function> cb = v8::Local<v8::Function>::Cast(args[0]);
// コールバックに渡す引数を用意する
const unsigned argc = 1;
v8::Local<v8::Value> argv[argc] = {
v8::String::NewFromUtf8(isolate, "done!")
};
// 呼ぶ
cb->Call(Null(isolate), argc, argv);
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "run", RunCallback);
}
NODE_MODULE(addon, init)
} // namespace demo
- test.js
var addon = require('./build/Release/addon');
addon.run(function(result) {
console.log(result);
});
実行
$ node test.js
done!
オブジェクトを生成して返り値にする
キーと値を格納できるオブジェクト型(Object)を生成して返します。この例では一層だけですが、値の部分に新たなオブジェクトを格納させることで、階層化されたオブジェクトを格納できます。
ちなみにオブジェクト型の v8::Object とは別に v8::Array 型がありますが、これは v8::Object を継承したクラスになっています。
ソースコード
- hello.cc
#include <node.h>
namespace demo {
// 与えられた文字列引数をオブジェクトに格納する
void GetObj(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
// 引数の数と型のチェック
if (args.Length() != 1 || !args[0]->IsString()) {
isolate->ThrowException(v8::Exception::TypeError(v8::String::NewFromUtf8(isolate, "Wrong arguments")));
return;
}
// 新しいObjectを作成する
v8::Local<v8::Object> obj = v8::Object::New(isolate);
// キーに "blue"、値に渡された引数を指定する
obj->Set(v8::String::NewFromUtf8(isolate, "blue"),
args[0]->ToString());
// 返り値にセットする
args.GetReturnValue().Set(obj);
}
void init(v8::Local<v8::Object> exports) {
NODE_SET_METHOD(exports, "get_obj", GetObj);
}
NODE_MODULE(addon, init)
} // namespace demo
- test.js
var addon = require('./build/Release/addon');
var obj = addon.get_obj("orange");
console.log(obj);
実行
$ node test.js
{ blue: 'orange' }