io.jsでネイティブ・アドオンが動かない原因を調べる - エラーが何を示しているのかを調べる。

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

このエラーについて調べてみた。

H:\pj\gyptest>node.exe np.js
module.js:335
  Module._extensionsextension;
                               ^
Error: Module did not self-register.
    at Error (native)
    at Module.load (module.js:335:32)
    at Function.Module._load (module.js:290:12)
    at Module.require (module.js:345:17)
    at require (module.js:364:17)
    at Object.<anonymous> (H:\pj\gyptest\np.js:1:77)
    at Module._compile (module.js:410:26)
    at Object.Module._extensions..js (module.js:428:10)
    at Module.load (module.js:335:32)
    at Function.Module._load (module.js:290:12)
    

エラーによれば、module.jsの335行目でエラーが発生している。

拡張子によってthis,filenameを引数に取る関数を呼び分けるようになっている。node拡張子の時はprocess.dlopen()を呼び出すようになっていた。

続いてdlopen関数を調べる。dlopen関数はnode.ccファイルの中にDLOpen関数として定義されていた。つまりネイティブ・メソッドである。ここからはC/C++の世界である。

38-41行目でmpがnullptrの時に「Module did not self-register.」エラーが発生するようである。このmpはnode_module構造体へのポインタである。25行目でmpにmodpendingの内容を代入している。このmodpendingというのはstaticグローバル変数である。

先ほどのDLOpen関数に戻ると11行目でmodpendingがnullptrかどうかをテストしている。それから25行目でmpにmodpendingを代入しているから、その間にmodpendingに値がセットされるはずである。怪しいのは20行目のuv_dlopen関数である。これを調べる。

このuv_dlopen関数は以下の通りであった。私はWindowsしか知らないのでwin版のdl.c中のを参照した。

ソースを読むとこの関数はdllを読み出しているだけである。ん、どこでmodpendingに値をセットしているのか。よくわからないのでio.jsのソースコードをmodpendingでgrepするとnode_module_register関数をnode.cc中で見つけた。

この関数の15行目でmodpendingに値をセットしている。とするとこの関数がどこかで呼ばれ、modpendingに値をセットするのだということがわかる。で、今度はnode_module_registerでソースコードをgrepする。すると以下のようなマクロがnode.h中で見つかる。

このマクロ中の16行目、NODE_C_CTORマクロ中でnode_module_register関数は呼ばれている。ちなみにこのNODE_MODULE_Xマクロは何かというと、ネイティブ・アドオンモジュールを定義するときに使うマクロNODE_MODULEのベースとなるマクロである。NODE_MODULE_Xの引数を省略したものがNODE_MODULEである。

カギとなりそうなのはNODE_C_CTORマクロである。このマクロ定義をのぞいてみる。

#if defined(_MSC_VER)で囲まれている部分を見るとこれは巧妙なハックであることがわかる。これはmain関数が呼ばれる前に初期化関数を呼び出す手法である。つまりはこのマクロはユーザー初期化セクション(.CRT$XCU)に初期化関数を定義しつつセットするのである。そうすることでモジュールがロードされたときにこの関数が呼び出されるのではないかと思う。この部分はDLLがロードされたときの動きがどうなるのかの正確な情報を得ていないので推測ではあるが。

まとめると、uv_dlopen()関数を呼び出したときにdllがロードされ初期化関数が実行されることでnode_module_register関数が呼ばれmodpendingに値がセットされるのである。

つまり「Module did not self-register.」エラーはmpがnullptrであった場合、つまりmodpendingに何らかの原因で値がセットされなかったときに発生するようだ。これが発生するケースとしてはこの初期化関数がdllロード時に呼ばれなかった場合が考えられるけれども、なぜnode.exeのときにそうなってしまうのかはよくわからない。というかそうなるという裏付けも取れていないのだけれども。もう少し調べてみようかなと思っている。