From 051029c838f4df47ae1831606b284aea6ee15979 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 20 Apr 2018 22:57:33 -0400 Subject: [PATCH] n-api: initialize a module via a special symbol Much like regular modules, N-API modules can also benefit from having a special symbol which they can expose. Fixes: https://github.com/nodejs/node/issues/19845 --- doc/api/n-api.md | 26 ++++++++++++++++++++++++ src/node.cc | 9 ++++++++ src/node_api.cc | 15 +++++++++----- src/node_api.h | 21 ++++++++++++++++++- src/node_internals.h | 5 +++++ test/addons-napi/1_hello_world/binding.c | 4 +--- test/addons-napi/1_hello_world/test.js | 11 ++++++++-- 7 files changed, 80 insertions(+), 11 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 14ef277f535ebb..857d9aad31aa4e 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -982,6 +982,32 @@ napi_value Init(napi_env env, napi_value exports) { } ``` +If you expect that your module will be loaded multiple times during the lifetime +of the Node.js process, you can use the `NAPI_MODULE_INIT` macro to initialize +your module: + +```C +NAPI_MODULE_INIT() { + napi_value answer; + napi_status result; + + status = napi_create_int64(env, 42, &answer); + if (status != napi_ok) return NULL; + + status = napi_set_named_property(env, exports, "answer", answer); + if (status != napi_ok) return NULL; + + return exports; +} +``` + +This macro includes `NAPI_MODULE`, and declares an `Init` function with a +special name and with visibility beyond the addon. This will allow Node.js to +initialize the module even if it is loaded multiple times. + +The variables `env` and `exports` will be available inside the function body +following the macro invocation. + For more details on setting properties on objects, see the section on [Working with JavaScript Properties][]. diff --git a/src/node.cc b/src/node.cc index 6f946ab3ad1cc7..3f95714b695330 100644 --- a/src/node.cc +++ b/src/node.cc @@ -2225,6 +2225,13 @@ inline InitializerCallback GetInitializerCallback(DLib* dlib) { return reinterpret_cast(dlib->GetSymbolAddress(name)); } +inline napi_addon_register_func GetNapiInitializerCallback(DLib* dlib) { + const char* name = + STRINGIFY(NAPI_MODULE_INITIALIZER_BASE) STRINGIFY(NAPI_MODULE_VERSION); + return + reinterpret_cast(dlib->GetSymbolAddress(name)); +} + // DLOpen is process.dlopen(module, filename, flags). // Used to load 'module.node' dynamically shared objects. // @@ -2281,6 +2288,8 @@ static void DLOpen(const FunctionCallbackInfo& args) { if (mp == nullptr) { if (auto callback = GetInitializerCallback(&dlib)) { callback(exports, module, context); + } else if (auto napi_callback = GetNapiInitializerCallback(&dlib)) { + napi_module_register_by_symbol(exports, module, context, napi_callback); } else { dlib.Close(); env->ThrowError("Module did not self-register."); diff --git a/src/node_api.cc b/src/node_api.cc index 3a02e5effa7a4f..a77b08cfd57b95 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -860,16 +860,23 @@ void napi_module_register_cb(v8::Local exports, v8::Local module, v8::Local context, void* priv) { - napi_module* mod = static_cast(priv); + napi_module_register_by_symbol(exports, module, context, + static_cast(priv)->nm_register_func); +} + +} // end of anonymous namespace +void napi_module_register_by_symbol(v8::Local exports, + v8::Local module, + v8::Local context, + napi_addon_register_func init) { // Create a new napi_env for this module or reference one if a pre-existing // one is found. napi_env env = v8impl::GetEnv(context); napi_value _exports; NAPI_CALL_INTO_MODULE_THROW(env, - _exports = mod->nm_register_func(env, - v8impl::JsValueFromV8LocalValue(exports))); + _exports = init(env, v8impl::JsValueFromV8LocalValue(exports))); // If register function returned a non-null exports object different from // the exports object we passed it, set that as the "exports" property of @@ -881,8 +888,6 @@ void napi_module_register_cb(v8::Local exports, } } -} // end of anonymous namespace - // Registers a NAPI module. void napi_module_register(napi_module* mod) { node::node_module* nm = new node::node_module { diff --git a/src/node_api.h b/src/node_api.h index aaf002b7584449..b010d32db7b086 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -90,9 +90,28 @@ typedef struct { } \ EXTERN_C_END -#define NAPI_MODULE(modname, regfunc) \ +#define NAPI_MODULE(modname, regfunc) \ NAPI_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage) +#define NAPI_MODULE_INITIALIZER_BASE napi_register_module_v + +#define NAPI_MODULE_INITIALIZER_X(base, version) \ + NAPI_MODULE_INITIALIZER_X_HELPER(base, version) +#define NAPI_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#define NAPI_MODULE_INITIALIZER \ + NAPI_MODULE_INITIALIZER_X(NAPI_MODULE_INITIALIZER_BASE, \ + NAPI_MODULE_VERSION) + +#define NAPI_MODULE_INIT() \ + EXTERN_C_START \ + NAPI_MODULE_EXPORT napi_value \ + NAPI_MODULE_INITIALIZER(napi_env env, napi_value exports); \ + EXTERN_C_END \ + NAPI_MODULE(NODE_GYP_MODULE_NAME, NAPI_MODULE_INITIALIZER) \ + napi_value NAPI_MODULE_INITIALIZER(napi_env env, \ + napi_value exports) + #define NAPI_AUTO_LENGTH SIZE_MAX EXTERN_C_START diff --git a/src/node_internals.h b/src/node_internals.h index 8cc1aa1a8fec40..9ce158f765b450 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -33,6 +33,7 @@ #include "tracing/trace_event.h" #include "node_perf_common.h" #include "node_debug_options.h" +#include "node_api.h" #include #include @@ -833,6 +834,10 @@ static inline const char *errno_string(int errorno) { } // namespace node +void napi_module_register_by_symbol(v8::Local exports, + v8::Local module, + v8::Local context, + napi_addon_register_func init); #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/test/addons-napi/1_hello_world/binding.c b/test/addons-napi/1_hello_world/binding.c index 27d49079966d09..6efc14ee66e18f 100644 --- a/test/addons-napi/1_hello_world/binding.c +++ b/test/addons-napi/1_hello_world/binding.c @@ -10,10 +10,8 @@ napi_value Method(napi_env env, napi_callback_info info) { return world; } -napi_value Init(napi_env env, napi_value exports) { +NAPI_MODULE_INIT() { napi_property_descriptor desc = DECLARE_NAPI_PROPERTY("hello", Method); NAPI_CALL(env, napi_define_properties(env, exports, 1, &desc)); return exports; } - -NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/1_hello_world/test.js b/test/addons-napi/1_hello_world/test.js index c975c48a733f5c..d1d67d34f688c9 100644 --- a/test/addons-napi/1_hello_world/test.js +++ b/test/addons-napi/1_hello_world/test.js @@ -1,6 +1,13 @@ 'use strict'; const common = require('../../common'); const assert = require('assert'); -const addon = require(`./build/${common.buildType}/binding`); +const bindingPath = require.resolve(`./build/${common.buildType}/binding`); +const binding = require(bindingPath); +assert.strictEqual(binding.hello(), 'world'); +console.log('binding.hello() =', binding.hello()); -assert.strictEqual(addon.hello(), 'world'); +// Test multiple loading of the same module. +delete require.cache[bindingPath]; +const rebinding = require(bindingPath); +assert.strictEqual(rebinding.hello(), 'world'); +assert.notStrictEqual(binding.hello, rebinding.hello);