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

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' }

参考文献

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
ユーザーは見つかりませんでした