C++
Node.js

node.js v5.0.0でC++のnative addonを作る(基礎編)

More than 3 years have passed since last update.

全部追いかけてないですが、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' }


参考文献