Native Addon を作る - NODE_MODULEマクロやDLOpen関数など

公開:2015-04-15 19:55
更新:2020-02-15 04:37
カテゴリ:native addon,io.js,c++,javascript

昨日のエラーの原因はまだつかめていない。しかし追求している途中でNODE_MODULEマクロが何をしているのかを知ることができた。

#define NODE_MODULE_X(modname, regfunc, priv, flags) \
extern "C" { \
static node::node_module _module = \
{ \
NODE_MODULE_VERSION, \
flags, \
NULL, \
__FILE__, \
(node::addon_register_func) (regfunc), \
NULL, \
NODE_STRINGIFY(modname), \
priv, \
NULL \
}; \
NODE_C_CTOR(_register_ ## modname) { \
node_module_register(&_module); \
} \
}
view raw gistfile1.cpp hosted with ❤ by GitHub

NODE_MODULEマクロは_module変数を定義し、_module変数を引数にDLLロード時にnode_module_register関数を呼び出す関数を定義する。これによりio.js側にモジュールの情報をロード時に伝えることができる。

通常DLLをロードしたとき、このような初期化作業を行うのはGetProcAddressを使って関数名から初期化関数のアドレスをもらって呼び出すのがふつうだ。おそらくそれでも同じことはできそうに思うが何か理由があるのだろう。他のOSと互換性のある実装がやりにくいからだろうか。このあたりもう少し理由を追究してみたいような気もするが、とりあえずは置いておくことにする。

それで_module変数だが、これはnode名前空間のnode_module構造体である。

typedef void (*addon_register_func)(
v8::Handle<v8::Object> exports,
v8::Handle<v8::Value> module,
void* priv);
typedef void (*addon_context_register_func)(
v8::Handle<v8::Object> exports,
v8::Handle<v8::Value> module,
v8::Handle<v8::Context> context,
void* priv);
#define NM_F_BUILTIN 0x01
#define NM_F_LINKED 0x02
struct node_module {
int nm_version;
unsigned int nm_flags;
void* nm_dso_handle;
const char* nm_filename;
node::addon_register_func nm_register_func;
node::addon_context_register_func nm_context_register_func;
const char* nm_modname;
void* nm_priv;
struct node_module* nm_link;
};
view raw gistfile1.cpp hosted with ❤ by GitHub

この構造体はアドオンのメタ情報を定義するものである。1つ1つ説明すると

この構造体でアドオン・モジュールの情報を引き渡し、DLOpen関数で初期化処理が行われる。コードを読んだ結果DLOpen関数は以下のことを行っていた。

  1. uv_dlopen関数でアドオン・モジュールをロードする。ロードが実装されたときにアドオン・モジュールの初期化関数が呼ばれ、結果node_module_register関数が呼ばれる。node_module_register関数内では以下のことが行われる。
    1. mpmが代入される。mはアドオン・モジュールの_moduleである。
    2. mp->nm_flagsNM_F_BUILTINビットがセットされていた場合、mp->nm_linkメンバにmodlist_builtin変数の内容が代入され、modlist_builtin変数の内容にmp変数が代入される。これはおそらくビルトイン・モジュールだけの特別な操作であり、別途ビルトイン・モジュール用のリンク・リストがあり、そこに読み込まれたモジュールが挿入される操作である。
    3. この関数が呼ばれたときにio.js本体がまだ初期化されていない場合は、mp->nm_flagsNM_F_LINKEDが代入され、mp->nm_linkmodlist_linked変数の内容が代入される。そしてmodlist_linkedmpが代入される。
      1. 3.のいずれにも該当しない場合modpendingにmpが代入される。requireで読み込まれるモジュールはほぼこの変数に代入される。
  2. ローカル変数mp(node_module_register関数内のmpではない)にmodpendingの値を代入する。
  3. uv_dlopenがロードに失敗したときは例外をスローする。
  4. mpの値がnullptrの場合"Module did not self-register."エラーをスローする。
  5. mp->nm_versionの値がNODE_MODULE_VERSIONに一致しないときは"Module version mismatch."エラーをスローする。
  6. mp->nm_flagsNM_F_BUILTINビットが立っていた場合、"Built-in module self-registered."エラーをスローする。
  7. mp->nm_dso_handleにDLLのハンドルをセットする。
  8. mp->nm_linkmod_list_addonをセットする。そしてmod_list_addonmpをセットする。これはmod_list_addonを先頭としたリンクリストにアドオン・モジュールのアドレスを追加する操作となる。
  9. mp->nm_context_register_funcもしくはmp->nm_register_funcを呼び出す。

stlコンテナ等を使わずリンクリストを使っているあたりちょっと原始的でベタな実装だと思ったが、これも理由があってのことかもしれない。

次はmp->nm_register_funcあたりの理解を深めたいね。