Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

n-api: initialize a module via a special symbol #20161

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions doc/api/n-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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][].

Expand Down
9 changes: 9 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2225,6 +2225,13 @@ inline InitializerCallback GetInitializerCallback(DLib* dlib) {
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).
// Used to load 'module.node' dynamically shared objects.
//
Expand Down Expand Up @@ -2281,6 +2288,8 @@ static void DLOpen(const FunctionCallbackInfo<Value>& 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.");
Expand Down
15 changes: 10 additions & 5 deletions src/node_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -860,16 +860,23 @@ void napi_module_register_cb(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context,
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
// 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
Expand All @@ -881,8 +888,6 @@ void napi_module_register_cb(v8::Local<v8::Object> exports,
}
}

} // end of anonymous namespace

// Registers a NAPI module.
void napi_module_register(napi_module* mod) {
node::node_module* nm = new node::node_module {
Expand Down
21 changes: 20 additions & 1 deletion src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/node_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "tracing/trace_event.h"
#include "node_perf_common.h"
#include "node_debug_options.h"
#include "node_api.h"

#include <stdint.h>
#include <stdlib.h>
Expand Down Expand Up @@ -833,6 +834,10 @@ static inline const char *errno_string(int errorno) {

} // 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

Expand Down
4 changes: 1 addition & 3 deletions test/addons-napi/1_hello_world/binding.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
11 changes: 9 additions & 2 deletions test/addons-napi/1_hello_world/test.js
Original file line number Diff line number Diff line change
@@ -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);