昨日のエラーの原因はまだつかめていない。しかし追求している途中でNODE_MODULEマクロが何をしているのかを知ることができた。
NODE_MODULEマクロは_module変数を定義し、_module変数を引数にDLLロード時にnode_module_register関数を呼び出す関数を定義する。これによりio.js側にモジュールの情報をロード時に伝えることができる。
通常DLLをロードしたとき、このような初期化作業を行うのはGetProcAddressを使って関数名から初期化関数のアドレスをもらって呼び出すのがふつうだ。おそらくそれでも同じことはできそうに思うが何か理由があるのだろう。他のOSと互換性のある実装がやりにくいからだろうか。このあたりもう少し理由を追究してみたいような気もするが、とりあえずは置いておくことにする。
それで_module変数だが、これはnode名前空間のnode_module構造体である。
この構造体はアドオンのメタ情報を定義するものである。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あたりの理解を深めたいね。