Skip to content

Commit

Permalink
n-api: initialize a module via a special symbol
Browse files Browse the repository at this point in the history
Much like regular modules, N-API modules can also benefit from having
a special symbol which they can expose.

Fixes: #19845
PR-URL: #20161
Reviewed-By: Ben Noordhuis <[email protected]>
  • Loading branch information
Gabriel Schulhof authored and MylesBorins committed May 4, 2018
1 parent 4289402 commit cd83df3
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 11 deletions.
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 @@ -2227,6 +2227,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 @@ -2283,6 +2290,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 @@ -858,16 +858,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 @@ -879,8 +886,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 @@ -840,6 +841,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);

0 comments on commit cd83df3

Please sign in to comment.