Skip to content

Commit

Permalink
src: make process.dlopen() load well-known symbol
Browse files Browse the repository at this point in the history
Look for symbol `node_register_module_v${NODE_MODULE_VERSION}` if the
add-on didn't self-register.  This can be used to create add-ons that
support multiple Node.js versions from a single shared object.

PR-URL: #18934
Reviewed-By: Colin Ihrig <[email protected]>
Reviewed-By: Daniel Bevenius <[email protected]>
Reviewed-By: Franziska Hinkelmann <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Matheus Marchini <[email protected]>
Reviewed-By: Tobias Nießen <[email protected]>
  • Loading branch information
bnoordhuis authored and MylesBorins committed Mar 7, 2018
1 parent 89edbae commit 4fae6e3
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 5 deletions.
29 changes: 27 additions & 2 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2483,6 +2483,7 @@ struct DLib {

inline bool Open();
inline void Close();
inline void* GetSymbolAddress(const char* name);

const std::string filename_;
const int flags_;
Expand Down Expand Up @@ -2510,6 +2511,10 @@ void DLib::Close() {
dlclose(handle_);
handle_ = nullptr;
}

void* DLib::GetSymbolAddress(const char* name) {
return dlsym(handle_, name);
}
#else // !__POSIX__
bool DLib::Open() {
int ret = uv_dlopen(filename_.c_str(), &lib_);
Expand All @@ -2527,8 +2532,23 @@ void DLib::Close() {
uv_dlclose(&lib_);
handle_ = nullptr;
}

void* DLib::GetSymbolAddress(const char* name) {
void* address;
if (0 == uv_dlsym(&lib_, name, &address)) return address;
return nullptr;
}
#endif // !__POSIX__

using InitializerCallback = void (*)(Local<Object> exports,
Local<Value> module,
Local<Context> context);

inline InitializerCallback GetInitializerCallback(DLib* dlib) {
const char* name = "node_register_module_v" STRINGIFY(NODE_MODULE_VERSION);
return reinterpret_cast<InitializerCallback>(dlib->GetSymbolAddress(name));
}

// DLOpen is process.dlopen(module, filename, flags).
// Used to load 'module.node' dynamically shared objects.
//
Expand Down Expand Up @@ -2583,10 +2603,15 @@ static void DLOpen(const FunctionCallbackInfo<Value>& args) {
}

if (mp == nullptr) {
dlib.Close();
env->ThrowError("Module did not self-register.");
if (auto callback = GetInitializerCallback(&dlib)) {
callback(exports, module, context);
} else {
dlib.Close();
env->ThrowError("Module did not self-register.");
}
return;
}

if (mp->nm_version == -1) {
if (env->EmitNapiWarning()) {
if (ProcessEmitWarning(env, "N-API is an experimental feature and could "
Expand Down
3 changes: 3 additions & 0 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,9 @@ extern "C" NODE_EXTERN void node_module_register(void* mod);
} \
}

// Usage: `NODE_MODULE(NODE_GYP_MODULE_NAME, InitializerFunction)`
// If no NODE_MODULE is declared, Node.js looks for the well-known
// symbol `node_register_module_v${NODE_MODULE_VERSION}`.
#define NODE_MODULE(modname, regfunc) \
NODE_MODULE_X(modname, regfunc, NULL, 0) // NOLINT (readability/null_usage)

Expand Down
10 changes: 7 additions & 3 deletions test/addons/hello-world/binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ void Method(const v8::FunctionCallbackInfo<v8::Value>& args) {
args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world"));
}

void init(v8::Local<v8::Object> exports) {
#define CONCAT(a, b) CONCAT_HELPER(a, b)
#define CONCAT_HELPER(a, b) a##b
#define INITIALIZER CONCAT(node_register_module_v, NODE_MODULE_VERSION)

extern "C" NODE_MODULE_EXPORT void INITIALIZER(v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context) {
NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, init)

0 comments on commit 4fae6e3

Please sign in to comment.