Node.jsのネイティブ
拡張を作ってみる
常田 裕士
Node.js
 Chromeで使っているJavaScriptのエンジン(v8)を、コマンドベースで実行
できるようにしたもの。
 サーバーサイドJavaScript。ブラウザ上で使わない。
 Scratch3.0もNode.jsで動いてる。
ネイティブ拡張
 スクリプト言語からC言語で書かれたライブラリを呼ぶ機能。
 だいたいのスクリプト言語には備わっている。
 それぞれの言語のマナーに合わせて、「いい感じの」インターフェースを
を作るのがキモ。
SWIG
 Ruby, Python, Javaなんかで使われる、ネイティブ拡張生成のシステム。
 独自のインターフェース定義言語から、自動的にGlueCodeを作る。
 わりかしいい具合にやってくれるが、複雑なのはやっぱりしんどい。
 JavaScriptはコールバック関数に対応していないなど、まだイマイチ
N-API
 Node.jsのネイティブ拡張用API
 中身のv8エンジンとは独立(というか、v8エンジンがコロコロ仕様変わる
から、ネイティブ拡張に対して安定的なAPIを提供する目的で作られた。)
 さらに、このN-APIをC++でラップしたnode-addon-apiが使われている。
開発環境もコマンドひとつでセットアップできるので便利。
 今回はこのnode-addon-apiを使う。
今回のネタ)IoTivity-Lite
 IoTの通信のハンドシェイク規格のIoTivityのコンパクト版
 IoTivityはTizenとかで作っていた(Intel, Sumsungなど)
 Tizenがあまりイケてなくて、開発が停滞。
 IoT向けに絞ったIotivity-Liteが現在のメイン。
 とはいえ、これも誰が使ってるんだかよくわからない…
 IEEE802.15.4で動いてるOpenThread関連だと、IoTivity-LiteとGoogleさんの
OpenWeaveあたりがハンドシェイクとかペアリングの仕組みで使われている
 IoTivty-Liteの方が簡単そうなので、これを試してみる。
 やはりスクリプト系を使いたいところが多いのでライブラリを作る。
 まだ作りかけ。
C言語との接続の設計
 簡単な例
 const char* hello() { return “hello”; }
 これなら、だいたいの言語で文字列をマッピングする機能がある。
 Napi::String Function(const Napi::CallbackInfo& info) {
 Napi::Env env = info.Env();
 return Napi::String::New(env, hello() );
 }
 Napi::Object Init(Napi::Env env, Napi::Object exports) {
 exports.Set(Napi::String::New(env, “function"),
 Napi::Function::New(env, Function));
 return exports;
 }
 Node.js側にhello()をラップした関数オブジェクトを渡すことで、Node.jsからラッパ関数を呼べる。
 ラッパ経由でhello()を呼べる
Node.jsから呼ぶ
 var addon = require('native-extension');
 console.log(addon.function());
function() Function() hello()
Napi::ObjectWrap
 Napi::ObjectWrapは、ネイティブ機能をラップするオブジェクトを作る。
 Node.js側からはネイティブのメソッドを持ったオブジェクトに見える
 ObjectWrapとは言ってるが、ラップの部分は面倒見てくれない。
 自分で、ラップ
 class MyObject : public Napi::ObjectWrap<MyObject> {…}
Napi::ObjectWrap
 Napi::ObjectWrap::Init()をオーバーライドして、コンストラクタを定義す
る。
 Napi::Function X::Init(Napi::Env env, Napi::Ojbect exports) {
 Napi::Function func = DefineClass(env, “MyObject”,
 {InstanceMethod("plusOne", &MyObject::PlusOne) });
 exports.Set(“MyObject", func);
 return exports;
 }
Node.jsから呼び出す
 var obj = new addon.MyObject();
 console.log( obj.plusOne() );
new
MyObject()
Constructor
obj.
plusOne()
PlusOne()
ちょっと困ること
 console.dir()でネイティブのメソッド名は表示されない!
Napi::External
 Napi::Externalの機能では、ネイティブのオブジェクトをハンドラを作ってNode.jsに渡せる。
 Node.js側からは単なるブラックボックスのオブジェクト。メソッドなし、フィールドなし。
 Napi::Object FunctionExt(const Napi::CallbackInfo& info) {
 Napi::Env env = info.Env();
 void* mem = malloc(100);
 return Napi::External::New(env, mem);
 }
 Napi::Object Init(Napi::Env env, Napi::Object exports) {
 exports.Set(Napi::String::New(env, “function_ext"),
 Napi::Function::New(env, FunctionExt));
 return exports;
 }
Node.jsから呼び出す
 var addon = require('native-extension');
 console.log(addon.function_ext());
function_ext() FunctionExt
External mem
Externalは単なるプレースホルダ 中身はみせられない
困るやつ(ネストされたclass,struct)
 struct inner_t {int x; int y}
 struct outer_t { struct inner_t inner_x; int z; }
 Int func(struct inner_t* sx) {…}
 struct Outer の中身のstruct Innerを取り出して使うパターン。
 ラッパオブジェクト Inner のコンストラクタを呼べるようにしておく。
 Externalでやると、中身が操作できないので困る。
 Napi::Function Inner::GetClass(Napi::Env env) {
 Napi::Function func = DefineClass(…);
 constructor = Napi::Persistent(func);
 constructor.SuppressDestruct();
 }
 Napi::Object Outer::get_inner_x(…) {
 return Inner::constructor.new({});
 }
 Outerのラッパでinner_xを参照したときに、Innerのラッパオブジェクトを作
る。
 var obj = new addon.MyObject();
 console.log( obj.plusOne() );
new
MyObject()
Outer
obj.
Inner
get_inner
Inner.x Inner::get_x
Inner
Constructor
続・困るやつ(ネストされた
class,struct)
 ネストされたstructがある場合、ネストされた中身は、別のstructの一部で
ある場合もあれば、独立したインスタンスである場合もある。
 独立したインスタンスは、それ自身のライフサイクルでメモリ開放すれば
いいが、他のstructの一部である場合には、メモリを解放しない。
 ラッパクラスは両方のパターンに対応する必要がある。
 スマートポインタ的対応が必要
ライフサイクル
 Node.jsのオブジェクトはGCで無くなる。
 Node.js内で誰かがオブジェクトを参照していたら、GCされない
 Node.jsのオブジェクトのライフサイクルと同期するネイティブ層は、GC
からの通知でメモリを空ければよい。
 Node.jsのオブジェクトのライフサイクルで動いているメモリをネイティ
ブで握る場合はGCされないようにする必要がある。
 Node.jsの仕組みの外で確保したメモリは自身で消す必要がある。
 var obj = new addon.MyObject();
 console.log( obj.inner );
 console.log( new Inner() );
new
MyObject()
Constructor
obj.
Inner
get_inner
Inner
Constructor
New
Inner()
Inner
Constructor
多分これでいいはず…?
 //コンストラクタの引数ありなしでメモリを使い分ける
 Inner::Inner(const Napi::CallbackInfo& info) : ObjectWrap(info)
 {
 if (info.Length() == 0) {
 /* 自力で作るパターン。自前でメモリ*/
 m_shptr = make_shared<struct inner_t>();
 }
 else if (info.Length() == 1 && info[0].IsExternal() ) {
 /* 外からもらったshared_ptrを弱参照する (使わなくなったら消える)。*/
 m_shptr = info[0].As<Napi::External< shared _ptr<struct inner_t> >();
 }
 else { /* 例外 */ }
 }
 var non_deletable_inner = obj.inner_x; //objがいる限り中身は消さない
 var deletable_x = new Inner(); // gcする
 ネイティブ内部でもうまくライフサイクルの管理が必要
 だいたいC++のshared_ptrでいけるはず。

Node native ext