このエラーについて調べてみた。
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行目でエラーが発生している。
// Given a file name, pass it to the proper extension handler. | |
Module.prototype.load = function(filename) { | |
debug('load ' + JSON.stringify(filename) + | |
' for module ' + JSON.stringify(this.id)); | |
assert(!this.loaded); | |
this.filename = filename; | |
this.paths = Module._nodeModulePaths(path.dirname(filename)); | |
var extension = path.extname(filename) || '.js'; | |
if (!Module._extensions[extension]) extension = '.js'; | |
Module._extensions[extension](this, filename);// <<<<< エラー発生個所 | |
this.loaded = true; | |
}; |
拡張子によってthis,filenameを引数に取る関数を呼び分けるようになっている。node拡張子の時はprocess.dlopen()を呼び出すようになっていた。
//Native extension for .node | |
Module._extensions['.node'] = process.dlopen; |
続いてdlopen関数を調べる。dlopen関数はnode.ccファイルの中にDLOpen関数として定義されていた。つまりネイティブ・メソッドである。ここからはC/C++の世界である。
// DLOpen is process.dlopen(module, filename). | |
// Used to load 'module.node' dynamically shared objects. | |
// | |
// FIXME(bnoordhuis) Not multi-context ready. TBD how to resolve the conflict | |
// when two contexts try to load the same shared object. Maybe have a shadow | |
// cache that's a plain C list or hash table that's shared across contexts? | |
void DLOpen(const FunctionCallbackInfo<Value>& args) { | |
Environment* env = Environment::GetCurrent(args); | |
uv_lib_t lib; | |
CHECK_EQ(modpending, nullptr); | |
if (args.Length() != 2) { | |
env->ThrowError("process.dlopen takes exactly 2 arguments."); | |
return; | |
} | |
Local<Object> module = args[0]->ToObject(env->isolate()); // Cast | |
node::Utf8Value filename(env->isolate(), args[1]); // Cast | |
const bool is_dlopen_error = uv_dlopen(*filename, &lib); | |
// Objects containing v14 or later modules will have registered themselves | |
// on the pending list. Activate all of them now. At present, only one | |
// module per object is supported. | |
node_module* const mp = modpending; | |
modpending = nullptr; | |
if (is_dlopen_error) { | |
Local<String> errmsg = OneByteString(env->isolate(), uv_dlerror(&lib)); | |
#ifdef _WIN32 | |
// Windows needs to add the filename into the error message | |
errmsg = String::Concat(errmsg, args[1]->ToString(env->isolate())); | |
#endif // _WIN32 | |
env->isolate()->ThrowException(Exception::Error(errmsg)); | |
return; | |
} | |
if (mp == nullptr) { | |
env->ThrowError("Module did not self-register."); | |
return; | |
} | |
if (mp->nm_version != NODE_MODULE_VERSION) { | |
char errmsg[1024]; | |
snprintf(errmsg, | |
sizeof(errmsg), | |
"Module version mismatch. Expected %d, got %d.", | |
NODE_MODULE_VERSION, mp->nm_version); | |
env->ThrowError(errmsg); | |
return; | |
} | |
if (mp->nm_flags & NM_F_BUILTIN) { | |
env->ThrowError("Built-in module self-registered."); | |
return; | |
} | |
mp->nm_dso_handle = lib.handle; | |
mp->nm_link = modlist_addon; | |
modlist_addon = mp; | |
Local<String> exports_string = env->exports_string(); | |
Local<Object> exports = module->Get(exports_string)->ToObject(env->isolate()); | |
if (mp->nm_context_register_func != nullptr) { | |
mp->nm_context_register_func(exports, module, env->context(), mp->nm_priv); | |
} else if (mp->nm_register_func != nullptr) { | |
mp->nm_register_func(exports, module, mp->nm_priv); | |
} else { | |
env->ThrowError("Module has no declared entry point."); | |
return; | |
} | |
// Tell coverity that 'handle' should not be freed when we return. | |
// coverity[leaked_storage] | |
} |
38-41行目でmpがnullptrの時に「Module did not self-register.」エラーが発生するようである。このmpはnode_module構造体へのポインタである。25行目でmpにmodpendingの内容を代入している。このmodpendingというのはstaticグローバル変数である。
static node_module* modpending; |
先ほどのDLOpen関数に戻ると11行目でmodpendingがnullptrかどうかをテストしている。それから25行目でmpにmodpendingを代入しているから、その間にmodpendingに値がセットされるはずである。怪しいのは20行目のuv_dlopen関数である。これを調べる。
このuv_dlopen関数は以下の通りであった。私はWindowsしか知らないのでwin版のdl.c中のを参照した。
int uv_dlopen(const char* filename, uv_lib_t* lib) { | |
WCHAR filename_w[32768]; | |
lib->handle = NULL; | |
lib->errmsg = NULL; | |
if (!uv_utf8_to_utf16(filename, filename_w, ARRAY_SIZE(filename_w))) { | |
return uv__dlerror(lib, GetLastError()); | |
} | |
lib->handle = LoadLibraryExW(filename_w, NULL, LOAD_WITH_ALTERED_SEARCH_PATH); | |
if (lib->handle == NULL) { | |
return uv__dlerror(lib, GetLastError()); | |
} | |
return 0; | |
} |
ソースを読むとこの関数はdllを読み出しているだけである。ん、どこでmodpendingに値をセットしているのか。よくわからないのでio.jsのソースコードをmodpendingでgrepするとnode_module_register関数をnode.cc中で見つけた。
extern "C" void node_module_register(void* m) { | |
struct node_module* mp = reinterpret_cast<struct node_module*>(m); | |
if (mp->nm_flags & NM_F_BUILTIN) { | |
mp->nm_link = modlist_builtin; | |
modlist_builtin = mp; | |
} else if (!node_is_initialized) { | |
// "Linked" modules are included as part of the node project. | |
// Like builtins they are registered *before* node::Init runs. | |
mp->nm_flags = NM_F_LINKED; | |
mp->nm_link = modlist_linked; | |
modlist_linked = mp; | |
} else { | |
modpending = mp; | |
} | |
} |
この関数の15行目でmodpendingに値をセットしている。とするとこの関数がどこかで呼ばれ、modpendingに値をセットするのだということがわかる。で、今度はnode_module_registerでソースコードをgrepする。すると以下のようなマクロがnode.h中で見つかる。
#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); \ | |
} \ | |
} |
このマクロ中の16行目、NODE_C_CTORマクロ中でnode_module_register関数は呼ばれている。ちなみにこのNODE_MODULE_Xマクロは何かというと、ネイティブ・アドオンモジュールを定義するときに使うマクロNODE_MODULEのベースとなるマクロである。NODE_MODULE_Xの引数を省略したものがNODE_MODULEである。
#define NODE_MODULE(modname, regfunc) \ | |
NODE_MODULE_X(modname, regfunc, NULL, 0) |
カギとなりそうなのはNODE_C_CTORマクロである。このマクロ定義をのぞいてみる。
#if defined(_MSC_VER) | |
#pragma section(".CRT$XCU", read) | |
#define NODE_C_CTOR(fn) \ | |
static void __cdecl fn(void); \ | |
__declspec(dllexport, allocate(".CRT$XCU")) \ | |
void (__cdecl*fn ## _)(void) = fn; \ | |
static void __cdecl fn(void) | |
#else | |
#define NODE_C_CTOR(fn) \ | |
static void fn(void) __attribute__((constructor)); \ | |
static void fn(void) | |
#endif |
#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のときにそうなってしまうのかはよくわからない。というかそうなるという裏付けも取れていないのだけれども。もう少し調べてみようかなと思っている。