昨日のエラーの原因はまだつかめていない。しかし追求している途中でNODE_MODULE
マクロが何をしているのかを知ることができた。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); \ | |
} \ | |
} |
NODE_MODULE
マクロは_module
変数を定義し、_module
変数を引数にDLLロード時にnode_module_register
関数を呼び出す関数を定義する。これによりio.js側にモジュールの情報をロード時に伝えることができる。
通常DLLをロードしたとき、このような初期化作業を行うのはGetProcAddress
を使って関数名から初期化関数のアドレスをもらって呼び出すのがふつうだ。おそらくそれでも同じことはできそうに思うが何か理由があるのだろう。他のOSと互換性のある実装がやりにくいからだろうか。このあたりもう少し理由を追究してみたいような気もするが、とりあえずは置いておくことにする。
それで_module
変数だが、これはnode
名前空間のnode_module
構造体である。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
}; |
この構造体はアドオンのメタ情報を定義するものである。1つ1つ説明すると
nm_version
はモジュールのバージョンを示す。nm_flags
はフラグをセットする。今のところフラグの値としてはNM_F_BUILTIN
とNM_F_LINKED
の2種類である。nm_dso_handle
はDLLをロードしたとき得られるハンドルが入る。これはio.js側でDLOpen
関数内でセットされる。nm_filename
はモジュールのファイル名が入る。nm_register_func
はモジュールを初期化するための関数ポインタが入る。これはモジュール内でfunc(Handle<Object> exports)
で定義される関数である。nm_context_register_func
はモジュールを初期化するための関数ポインタが入る。しかしこの関数ポインタの意味はまだ理解していない。nm_modname
はモジュール名が入る。これはrequire
で指定するときのモジュール名である。nm_priv
は不明。nm_link
はnm_module
構造体はそれ自体簡単なリンクリストとなっており、DLOpen
関数でモジュールをロードしたときに1つ前のモジュールのアドレスがここに入る。
この構造体でアドオン・モジュールの情報を引き渡し、DLOpen
関数で初期化処理が行われる。コードを読んだ結果DLOpen
関数は以下のことを行っていた。
uv_dlopen
関数でアドオン・モジュールをロードする。ロードが実装されたときにアドオン・モジュールの初期化関数が呼ばれ、結果node_module_register
関数が呼ばれる。node_module_register
関数内では以下のことが行われる。mp
にm
が代入される。m
はアドオン・モジュールの_module
である。mp->nm_flags
にNM_F_BUILTIN
ビットがセットされていた場合、mp->nm_link
メンバにmodlist_builtin
変数の内容が代入され、modlist_builtin
変数の内容にmp
変数が代入される。これはおそらくビルトイン・モジュールだけの特別な操作であり、別途ビルトイン・モジュール用のリンク・リストがあり、そこに読み込まれたモジュールが挿入される操作である。- この関数が呼ばれたときにio.js本体がまだ初期化されていない場合は、
mp->nm_flags
にNM_F_LINKED
が代入され、mp->nm_link
にmodlist_linked
変数の内容が代入される。そしてmodlist_linked
にmp
が代入される。 - 3.のいずれにも該当しない場合
modpending
にmpが代入される。require
で読み込まれるモジュールはほぼこの変数に代入される。
- 3.のいずれにも該当しない場合
- ローカル変数
mp
(node_module_register関数内のmp
ではない)にmodpending
の値を代入する。 uv_dlopen
がロードに失敗したときは例外をスローする。mp
の値がnullptr
の場合"Module did not self-register."エラーをスローする。mp->nm_version
の値がNODE_MODULE_VERSION
に一致しないときは"Module version mismatch."エラーをスローする。mp->nm_flags
のNM_F_BUILTIN
ビットが立っていた場合、"Built-in module self-registered."エラーをスローする。mp->nm_dso_handle
にDLLのハンドルをセットする。mp->nm_link
にmod_list_addon
をセットする。そしてmod_list_addon
にmp
をセットする。これはmod_list_addon
を先頭としたリンクリストにアドオン・モジュールのアドレスを追加する操作となる。mp->nm_context_register_func
もしくはmp->nm_register_func
を呼び出す。
stlコンテナ等を使わずリンクリストを使っているあたりちょっと原始的でベタな実装だと思ったが、これも理由があってのことかもしれない。
次はmp->nm_register_func
あたりの理解を深めたいね。