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
PR-URL: https://github.com/nodejs/node/pull/20161
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
Gabriel Schulhof 2018-04-20 22:57:33 -04:00
parent 3bcd8576fc
commit 0f8caf23be
7 changed files with 80 additions and 11 deletions

View File

@ -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 For more details on setting properties on objects, see the section on
[Working with JavaScript Properties][]. [Working with JavaScript Properties][].

View File

@ -2229,6 +2229,13 @@ inline InitializerCallback GetInitializerCallback(DLib* dlib) {
return reinterpret_cast<InitializerCallback>(dlib->GetSymbolAddress(name)); return reinterpret_cast<InitializerCallback>(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<napi_addon_register_func>(dlib->GetSymbolAddress(name));
}
// DLOpen is process.dlopen(module, filename, flags). // DLOpen is process.dlopen(module, filename, flags).
// Used to load 'module.node' dynamically shared objects. // Used to load 'module.node' dynamically shared objects.
// //
@ -2285,6 +2292,8 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
if (mp == nullptr) { if (mp == nullptr) {
if (auto callback = GetInitializerCallback(&dlib)) { if (auto callback = GetInitializerCallback(&dlib)) {
callback(exports, module, context); callback(exports, module, context);
} else if (auto napi_callback = GetNapiInitializerCallback(&dlib)) {
napi_module_register_by_symbol(exports, module, context, napi_callback);
} else { } else {
dlib.Close(); dlib.Close();
env->ThrowError("Module did not self-register."); env->ThrowError("Module did not self-register.");

View File

@ -858,16 +858,23 @@ void napi_module_register_cb(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module, v8::Local<v8::Value> module,
v8::Local<v8::Context> context, v8::Local<v8::Context> context,
void* priv) { void* priv) {
napi_module* mod = static_cast<napi_module*>(priv); napi_module_register_by_symbol(exports, module, context,
static_cast<napi_module*>(priv)->nm_register_func);
}
} // end of anonymous namespace
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context,
napi_addon_register_func init) {
// Create a new napi_env for this module or reference one if a pre-existing // Create a new napi_env for this module or reference one if a pre-existing
// one is found. // one is found.
napi_env env = v8impl::GetEnv(context); napi_env env = v8impl::GetEnv(context);
napi_value _exports; napi_value _exports;
NAPI_CALL_INTO_MODULE_THROW(env, NAPI_CALL_INTO_MODULE_THROW(env,
_exports = mod->nm_register_func(env, _exports = init(env, v8impl::JsValueFromV8LocalValue(exports)));
v8impl::JsValueFromV8LocalValue(exports)));
// If register function returned a non-null exports object different from // If register function returned a non-null exports object different from
// the exports object we passed it, set that as the "exports" property of // the exports object we passed it, set that as the "exports" property of
@ -879,8 +886,6 @@ void napi_module_register_cb(v8::Local<v8::Object> exports,
} }
} }
} // end of anonymous namespace
// Registers a NAPI module. // Registers a NAPI module.
void napi_module_register(napi_module* mod) { void napi_module_register(napi_module* mod) {
node::node_module* nm = new node::node_module { node::node_module* nm = new node::node_module {

View File

@ -93,6 +93,25 @@ typedef struct {
#define NAPI_MODULE(modname, regfunc) \ #define NAPI_MODULE(modname, regfunc) \
NAPI_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage) 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 #define NAPI_AUTO_LENGTH SIZE_MAX
EXTERN_C_START EXTERN_C_START

View File

@ -33,6 +33,7 @@
#include "tracing/trace_event.h" #include "tracing/trace_event.h"
#include "node_perf_common.h" #include "node_perf_common.h"
#include "node_debug_options.h" #include "node_debug_options.h"
#include "node_api.h"
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
@ -840,6 +841,10 @@ static inline const char *errno_string(int errorno) {
} // namespace node } // namespace node
void napi_module_register_by_symbol(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context,
napi_addon_register_func init);
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

View File

@ -10,10 +10,8 @@ napi_value Method(napi_env env, napi_callback_info info) {
return world; return world;
} }
napi_value Init(napi_env env, napi_value exports) { NAPI_MODULE_INIT() {
napi_property_descriptor desc = DECLARE_NAPI_PROPERTY("hello", Method); napi_property_descriptor desc = DECLARE_NAPI_PROPERTY("hello", Method);
NAPI_CALL(env, napi_define_properties(env, exports, 1, &desc)); NAPI_CALL(env, napi_define_properties(env, exports, 1, &desc));
return exports; return exports;
} }
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

View File

@ -1,6 +1,13 @@
'use strict'; 'use strict';
const common = require('../../common'); const common = require('../../common');
const assert = require('assert'); 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);