Node.js中JS是如何引用C++的

JavaScript 使用 internalBunding 方法调用 C++ 内容,看了一下大致的流程。

internalBinding

C++ 模块暴露给 JS

Node.js 中 C++ 模块的最后都会调用NODE_MODULE_CONTEXT_AWARE_INTERNAL来将该模块暴露给 JavaScript,例如 src/node_contextify.cc 文件中,将node::contextify::Initialize作为参数调用了NODE_MODULE_CONTEXT_AWARE_INTERNAL

1
NODE_MODULE_CONTEXT_AWARE_INTERNAL(contextify, node::contextify::Initialize)

NODE_MODULE_CONTEXT_AWARE_INTERNAL

src/node_binding.h

1
2
3
4
5
6
7
8
9
10
11
12
#define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags)           \
static node::node_module _module = { \
NODE_MODULE_VERSION, \
flags, \
nullptr, \
__FILE__, \
nullptr, \
(node::addon_context_register_func)(regfunc), \
NODE_STRINGIFY(modname), \
priv, \
nullptr}; \
void _register_##modname() { node_module_register(&_module); }

这个宏首先创建一个 node::node_module 实例。

之后,生成一个__register_##modname_函数,调用这个函数,会调用 node_module_register 方法。

node_module_register

src/node_binding.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extern "C" void node_module_register(void* m) {
struct node_module* mp = reinterpret_cast<struct node_module*>(m);

if (mp->nm_flags & NM_F_INTERNAL) {
mp->nm_link = modlist_internal;
modlist_internal = mp;
} else if (!node_is_initialized) {
// "Linked" modules are included as part of the node project.
// Like builtins they are registered *before* node::Init runs.
mp->nm_flags = NM_F_LINKED;
mp->nm_link = modlist_linked;
modlist_linked = mp;
} else {
thread_local_modpending = mp;
}
}

将模块放在模块链中。

有两个链:modlist_internalmodlist_linked。分别表示内部模块和外部模块。

internalBinding

lib/internal/bootstrap/loaders.js

JavaScript 中使用 internalBinding 来引用 C++ 模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Set up internalBinding() in the closure.
let internalBinding;
{
const bindingObj = Object.create(null);
// eslint-disable-next-line no-global-assign
internalBinding = function internalBinding(module) {
let mod = bindingObj[module];
if (typeof mod !== 'object') {
mod = bindingObj[module] = getInternalBinding(module);
moduleLoadList.push(`Internal Binding ${module}`);
}
return mod;
};
}

getInternalBinding 方法是 C++ 定义的,同 process 等作为参数传给 internal/bootstrap/loaders 执行。

1
2
3
4
5
6
7
// Bootstrap internal loaders
Local<Value> loader_exports;
if (!ExecuteBootstrapper(
this, "internal/bootstrap/loaders", &loaders_params, &loaders_args)
.ToLocal(&loader_exports)) {
return MaybeLocal<Value>();
}

internal/bootstrap/loaders 返回的是这样一个对象:

1
2
3
4
5
const loaderExports = {
internalBinding,
NativeModule,
require: nativeModuleRequire
};

其中就包含定义好的 internalBinding。之后取出 internal_binding_loader,调用set_internal_binding_loader

1
2
3
4
5
6
Local<Object> loader_exports_obj = loader_exports.As<Object>();
Local<Value> internal_binding_loader =
loader_exports_obj->Get(context(), internal_binding_string())
.ToLocalChecked();
CHECK(internal_binding_loader->IsFunction());
set_internal_binding_loader(internal_binding_loader.As<Function>());

set_internal_binding_loader 的作用是将 internal_binding_loader 设置在 Environment 上。

set_internal_binding_loader 是由一个宏来定义的:

1
2
3
4
5
6
7
8
9
10
#define V(PropertyName, TypeName)                                             \
inline v8::Local<TypeName> Environment::PropertyName() const { \
return PersistentToLocal::Strong(PropertyName ## _); \
} \
inline void Environment::set_ ## PropertyName(v8::Local<TypeName> value) { \
PropertyName ## _.Reset(isolate(), value); \
}
ENVIRONMENT_STRONG_PERSISTENT_TEMPLATES(V)
ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)
#undef V

其中 ENVIRONMENT_STRONG_PERSISTENT_VALUES 里调用了 V(internal_binding_loader, v8::Function):

1
2
3
#define ENVIRONMENT_STRONG_PERSISTENT_VALUES(V)                                \
... \
V(internal_binding_loader, v8::Function)

GetInternalBinding

src/node_binding.cc

GetInternalBinding 中,通过 get_internal_module 方法来获取到内部模块。module_v 是 utf8 形式的字符串。

1
node_module* mod = get_internal_module(*module_v);

get_internal_module 就是通过遍历链表来寻找模块。

之后调用 InitModule 方法执行模块:

1
exports = InitModule(env, mod, module);

InitModule 方法会调用模块的 nm_context_register_func 方法,这个 nm_context_register_func 方法就是 C++ 模块注册时传入的初始化方法。

1
mod->nm_context_register_func(exports, unused, env->context(), mod->nm_priv);

Initialize 函数的参数

所以再回到最初的 C++ 注册方法,可以看到最终 node::contextify::Initialize 被调用时,传入的参数就是 exports, unused, env->context(), mod->nm_priv

1
NODE_MODULE_CONTEXT_AWARE_INTERNAL(contextify, node::contextify::Initialize)