Node の C++ アドオン開発で必要なこと

この記事は JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース)の8日目の記事です。

今日は、Node の C++ アドオン開発をする上で必要な心構えや Tips について書きたいと思います。

1. 情報が少なくても泣かない

C++ アドオンについての公式ドキュメント に記載されているコードが以下のような同期処理のコードであるため、(おそらく殆どの人にとって)本命である非同期処理の参考になりません。

#include <node.h>
#include <v8.h>

using namespace v8;

Handle<Value> Method(const Arguments& args) {
  HandleScope scope;
  return scope.Close(String::New("world"));
}

void init(Handle<Object> target) {
  NODE_SET_METHOD(target, "hello", Method);
}
NODE_MODULE(hello, init)

また、例として挙げられている node_postgresは v0.5.2 で削除された node::EventEmitter を継承しているため v0.5.2 以降では動作しません。
公式ドキュメントがアテにならないため、C++ Addonを作成する場合は Node 自体のソースコードを読んだり、既存の Addon のソースコードを読むなどする必要があります。
なお、Node v0.6 以降向けの C++ アドオンの情報としては node-sqlite3 及び Writing node.js extensions with C++ and V8 が参考になると思います。

このように情報がなくても諦めずに探しだす根気が必要になります。もっとも、C++ アドオン開発に挑むような人はフロンティアスピリットに溢れているような人のはずなので、このような障害はむしろご褒美かもしれません。

2. API が変わっても絶望しない

Node のような若いプロダクトの宿命のようなものですが、互換性のない API の変更がそれなりにあります。

まず、大きいものは前述の node::EventEmitter の削除です。この変更により C++ で EventEmitter を使用していたアドオンは動作しなくなっています。

また、v0.5.4で EIO スレッドで処理を実行するための eio_custom() のプロトタイプが変更されています。これに対応するため、eio_custom()の第一引数に渡す関数のプロトタイプを変更する必要があります。

Node v0.5.3まで:

typedef int (*eio_cb)(eio_req *req);
...
eio_req *eio_custom(eio_cb execute, int pri, eio_cb cb, void *data);

Node v0.5.4以降:

typedef int (*eio_cb)(eio_req *req);
...
eio_req *eio_custom(void (*execute)(eio_req *), int pri, eio_cb cb, void *data);

この様な非互換な変更があるため、v0.4.x 〜 v0.6.x までサポートする場合は以下の様にプリプロセッサと NODE_VERSION_AT_LEASTマクロを活用します。

#include <node.h>
#include <node_version.h>

#if NODE_VERSION_AT_LEAST(0,5,4)
    //Node v0.5.4以上の場合
static void EIO_foo(eio_req *req) {
#else
    //Node v0.5.4未満の場合
static int EIO_foo(eio_req *req) {
#endif
    ...
}

ちなみに、v0.6 以降のみをサポートする場合は素直に libuv の API を使用しましょう。

この様に、Node の API が変わっても絶望せず、追従していく覚悟が必要となります。
(APIの変化に追従できず放置されたアドオンをどうするかなどは、これからエコシステムを進化させる過程で議論されることになると思います。)

3. package.json のバージョン指定はしっかりしよう

2.とも関連しますが、C++ アドオンはバージョンの違いの影響がシビアなので、package.json でのエンジンのバージョン指定はできるだけ正確にしましょう。

特にバージョン指定されてないけれど、バージョン違いでビルドが通らない場合はインストールの失敗の原因が利用者にわかりづらくなります。

4 複数バージョンでテストしよう

C++ アドオンに限った話ではないですが、特に C++ アドオンは内部的な API の変更の影響を受けやすいので、複数バージョンでテストしましょう。

手軽にやる場合は、Travis CIを活用するのも手です。
Node で Travis CI を使う方法については、Travis CIでNodeのテスト - hokaccha memoが参考になります。

手元で複数バージョンのテストをするなら下記のようなスクリプトを使えばいいです。

これだけだとシーケンシャルに実行するだけなので、マルチプロセスで並列化したり、出力の形式など工夫の余地はたくさんあるのでいろいろやってみると良いでしょう。

おわりに

Node の C++ アドオン開発は情報が少なかったりで大変なことも多いですが、得られるものも多いと思いますので、興味がある人は是非挑戦してみてください。