node.js内建原生模块的实现
内建模块是使用c++编写的原生模块。在用户模块中可以通过process.binding("内建模块")
获取。内建原生模块代码需要和node源代码一起编译构建。在node进行初始化的时候执行注册。内建模块一般是需要和操作系统API交互的模块。例如文件模块,网络模块等。在node中,经常可以看到的一种设计是。把一些需要执行操作系统调用的模块使用内建原生模块去编写,内置扩展中不会做太多的逻辑。逻辑会放到对应的内建的javaScript文件。例如当我们在用户模块使用 require("fs")
的时候。不会立即调用原生扩展 fs模块,而是会先调用内建javaScript模块,如做一些数据校验,路径的拼接,文件的查找检验。接着内建javaScript模块再调用内建原生模块去完成文件的读写。
一个内建模块的创建需要在c++文件中使用NODE_MODULE_CONTEXT_AWARE_INTERNAL();
进行注册函数。然后再在node_binding.cc
文件的NODE_BUILTIN_STANDARD_MODULES()
添加注册列表。下面我们看一下一个内建原生模块的创建流程 和原理。
我们拿fs_dir模块做例子。在node_dir.cc
文件。我们看文件的结尾。
// 代码有省略
void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
env->SetMethod(target, "opendir", OpenDir);
}
NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
NODE_MODULE_CONTEXT_AWARE_INTERNAL(模块名称/字符串/,初始化函数/ 函数/)
在编写内建模块的时候使用NODE_MODULE_CONTEXT_AWARE_INTERNAL()
宏去执行注册,需要提供两个参数。第一个为模块名称。第二个为注册函数。node::fs_dir::Initialize函数是模块提供给模块系统的。用于模块系统对内建模块执行初始化。提供的函数签名为:
typedef void (*addon_context_register_func)(
v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context,
void* priv);
v8::Local<v8::Object> exports
: exports为一个v8的javsScript对象。我们可以直接在整个对象中设置属性。这样模块外部就可以得到本模块需要对外暴露的属性和方法。v8::Local<v8::Value> module
:模块对象。名字有点歧义。在模块加载中,使用了undefined
作为参数。无解。按照commonjs规范。module对象应该为一个v8的javaScript对象。它包含了上面的exports属性和依赖属性dependencies项。还有一点,模块初始化没有传递require函数。c++的模块不能通过 require 函数去获取其他的内建函数。这点就很不符合commonjs规范。v8::Local<v8::Context> context
:v8的执行上下文。Environment对象通过存放在上下文的嵌入式数据(EmbedderData)上。在 上下文章节 我们知道,上下文嵌入式数据的生命周期和上下文生命周期一样。 因此可以通过上下文对象获取到当前的Environment对象,在node中有多种方式去获取当前的node上下文 Environment对象。
// 通过隔离实例
static inline Environment* GetCurrent(v8::Isolate* isolate);
// 通过上下文
static inline Environment* GetCurrent(v8::Local<v8::Context> context);
// 通过函数调用参数
static inline Environment* GetCurrent( const v8::FunctionCallbackInfo<v8::Value>& info);
// 通过属性访问器
template <typename T>
static inline Environment* GetCurrent( const v8::PropertyCallbackInfo<T>& info);
最终这些函数也是通过获取当前的上下下文去获取到 Environment对象。
// 最终所有的函数都是通过调用该函数获取到Environment对象Environment对象
inline Environment* Environment::GetCurrent(v8::Local<v8::Context> context) {
if (UNLIKELY(context.IsEmpty())) {
return nullptr;
}
if (UNLIKELY(context->GetNumberOfEmbedderDataFields() <=
ContextEmbedderIndex::kContextTag)) {
return nullptr;
}
if (UNLIKELY(context->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::kContextTag) !=
Environment::kNodeContextTagPtr)) {
return nullptr;
}
return static_cast<Environment*>(
context->GetAlignedPointerFromEmbedderData(
ContextEmbedderIndex::kEnvironment));
}
我们看一下宏NODE_MODULE_CONTEXT_AWARE_INTERNAL()
展开的效果
static node::node_module _module = {
NODE_MODULE_VERSION,// 版本号,
NM_F_INTERNAL,// 模块的标识枚举
nullptr,
__FILE__,
nullptr,
(node::addon_context_register_func)(node::fs_dir::Initialize),
NODE_STRINGIFY(fs_dir),
nullptr,
nullptr
};
void _register_fs_dir() {
node_module_register(&_module);
}
/* 下面不是宏展开代码,在这里只是展开阅读 */
// node::addon_context_register_func 函数的声明,对应着node::fs_dir::Initialize的声明
typedef void (*addon_context_register_func)(
v8::Local<v8::Object> exports,
v8::Local<v8::Value> module,
v8::Local<v8::Context> context,
void* priv);
// 模块的标识
enum {
NM_F_BUILTIN = 1 << 0, // 该枚举值还没有被使用.
NM_F_LINKED = 1 << 1, // 嵌入式模块
NM_F_INTERNAL = 1 << 2, // 内建模块
NM_F_DELETEME = 1 << 3,
};
// node_module的定义
struct node_module {
int nm_version; // 模块的版本号
unsigned int nm_flags; // 模块类型枚举,内建模块还是c++插件模块
void* nm_dso_handle; // 用于储存打开node c++扩展动态链接库的句柄或者文件描述符
const char* nm_filename;// 文件名称
node::addon_register_func nm_register_func; // 注册函数
node::addon_context_register_func nm_context_register_func; // 注册函数
const char* nm_modname; // 模块名称,node通过模块名称去查找模块,所以模块同一种模块的名字必须唯一,不同类型模块。名字可以重复
void* nm_priv; // 用于存放数据的指针
struct node_module* nm_link; // 同一种类型的node_module会以链表的形式排列。nm_link表示的是上一个node_module的指针
};
在上面的内容我们可以知道,通过 NODE_MODULE_CONTEXT_AWARE_INTERNAL()
会在当前模块定义了一个结构图node::node_module
,因此一个文件只能使用一次 NODE_MODULE_CONTEXT_AWARE_INTERNAL()
。接着定义下一个函数_register_fs_dir()
。函数再执行了node_module_register()
函数。那在那再哪里调用_register_fs_dir()
函数呢?node的启动流程分析章节中 我们知道。在node执行初始化的时候在InitializeNodeWithArgs()
函数中会调用binding::RegisterBuiltinModules()
完成内建模块的注册工作。
int InitializeNodeWithArgs(std::vector<std::string>* argv,
std::vector<std::string>* exec_argv,
std::vector<std::string>* errors) {
// 省略部分代码
// 注册c++内建模块
binding::RegisterBuiltinModules();
}
在node_binding.cc文件中查看 binding::RegisterBuiltinModules()
函数。
// 省略了部分代码, 这里暂时只讨论 NODE_BUILTIN_STANDARD_MODULES宏
#define NODE_BUILTIN_STANDARD_MODULES(V) \ \
V(fs) \
V(fs_dir) \
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V) \
NODE_BUILTIN_PROFILER_MODULES(V) \
NODE_BUILTIN_DTRACE_MODULES(V)
#define V(modname) void _register_##modname();
NODE_BUILTIN_MODULES(V)
#undef V
void RegisterBuiltinModules() {
#define V(modname) _register_##modname();
NODE_BUILTIN_MODULES(V)
#undef V
}
这里使用了嵌套宏,嵌套宏容易理解,定义一个宏,并把当前宏函数作为参数传递给另一个宏。嵌套宏在v8和node中大量使用。上面的例子进行宏展后得到。
// 注册内建模块。省略部分代码
void RegisterBuiltinModules() {
void _register_fs_dir();
void _register_fs();
// ...etc
}
在这里执行了上面 node_dir.cc 的注册函数。接着再调用node_module_register
函数完成注册。
// 链接模块和内建原生模块都使用了该函数实现模块的注册。只是两种模块的时机不一样
// 内建原始模块通过手动注册的方式在node进行初始化的时候执行注册函数去完成注册
// 链接模块使用的是系统特效。通过全局构造函数去实现模块的自动执行。下面链接模块章节做出原理的解析。
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) {
// modlist_internal变量标识的是内建模块链表中最后一个模块指针
// 设置当前模块的上一个模块的指针,并把当前模块设置到最后的内建模块指针变量 modlist_internal上
mp->nm_link = modlist_internal;
modlist_internal = mp;
} else if (!node_is_initialized) {
//如果node还没有初始化完成,把该模块当作链接模块
//下面章节做出解析
mp->nm_flags = NM_F_LINKED;
mp->nm_link = modlist_linked;
modlist_linked = mp;
} else {
thread_local_modpending = mp;
}
}
最终内建原生模块的数据存储方式。
内建原生模块node_module通过链表的数据结构进行关联的。node::node_module* modlist_internal
记录了链表最后的节点。每个链表都存着指向上一个node_module
的指针。所以可以通过变量modlist_internal
和模块的名称。查找node_module
链表的某个模块。为此我们可以知道一个完整的内建原生模块注册流程。
内建原生扩展注册流程
在node的期待流程中我们知道。当我们需要在用户模块中使用一个内建原生模块的时候需要通过process.``binding("fs")
的 方式去获取。我们看一下process.binding()
的实现。在 internal\bootstrap\loaders.js文件下。
// 在用户模块中中我们使用 process.binding去绑定模块的时候,只能binding下面的模块。
const internalBindingAllowlist = new SafeSet([
'buffer',
'fs',
'fs_event_wrap',
'zlib', // 内容有省略
]);
// 为进程对象添加了binding 和 _linkedBinding 函数。
{
const bindingObj = ObjectCreate(null);
process.binding = function binding(module) {
module = String(module);
// 判断是否是在白名单内的内建模块。
if (internalBindingAllowlist.has(module)) {
// internalBind是对上面c++传递下来的函数 getInternalBinding的包装
//
return internalBinding(module);
}
throw new Error(`No such module: ${module}`);
};
}
const moduleLoadList = [];
let internalBinding;
{
const bindingObj = ObjectCreate(null);
// internalBinding是对getInternalBinding 函数的封装。在执行前面加了一个javsScript对象做缓存。
internalBinding = function internalBinding(module) {
let mod = bindingObj[module];
if (typeof mod !== 'object') {
mod = bindingObj[module] = getInternalBinding(module);
// 把当前的内建模块名称存放在 moduleLoadList数组里面。
ArrayPrototypePush(moduleLoadList, `Internal Binding ${module}`);
}
return mod;
};
}
const loaderExports = {
internalBinding,
// 下面讨论
NativeModule,
require: nativeModuleRequire
};
return loaderExports;
在代码中我们看到。当我们使用process.binding()
内建原生模块的时候,首先会调用 internalBinding()
函数。 internalBinding()
函数的逻辑只要是使用了一个javaScript对象做了一层缓存的包装,以模块的名称作为key。模块到处对象作为value。如果获取的模块不在缓存对象里面。最后通过调用getInternalBinding()
函数获取。在 internal\bootstrap\loaders.js文件中并没有 getInternalBinding()
函数,改函数在哪里传递进来的呢?在node的期待流程中我们知道 internal\bootstrap\loaders.js文件在 Environment::BootstrapInternalLoaders()
进行了加载执行。
MaybeLocal<Value> Environment::BootstrapInternalLoaders() {
EscapableHandleScope scope(isolate_);
// 创建形参列表,用于把 internal/bootstrap/loaders内容包裹成一个函数。
// function(process,getLinkedBinding,getInternalBinding, primordials ) {
// /* internal/bootstrap/loaders.js文件内容 */
// }
std::vector<Local<String>> loaders_params = {
process_string(),
FIXED_ONE_BYTE_STRING(isolate_, "getLinkedBinding"),
FIXED_ONE_BYTE_STRING(isolate_, "getInternalBinding"),
primordials_string()};
// 构建实参列表
std::vector<Local<Value>> loaders_args = {
process_object(),
NewFunctionTemplate(binding::GetLinkedBinding)
->GetFunction(context())
.ToLocalChecked(),
NewFunctionTemplate(binding::GetInternalBinding)
->GetFunction(context())
.ToLocalChecked(),
primordials()};
Local<Value> loader_exports;
// 包裹函数,并执行。
if (!ExecuteBootstrapper( this, "internal/bootstrap/loaders", &loaders_params, &loaders_args).ToLocal(&loader_exports)) {
return MaybeLocal<Value>();
}
// 通javaScript函数调用的放回值导出到c++层。
Local<Object> loader_exports_obj = loader_exports.As<Object>();
// 获取 internalBinding函数
Local<Value> internal_binding_loader =
loader_exports_obj->Get(context(), internal_binding_string())
.ToLocalChecked();
// 保存internalBinding函数
set_internal_binding_loader(internal_binding_loader.As<Function>());
// 获取 require 函数
Local<Value> require = loader_exports_obj->Get(context(), require_string()).ToLocalChecked();
// 保存require
set_native_module_require(require.As<Function>());
return scope.Escape(loader_exports);
}
ExecuteBootstrapper()
函数的作用是把形参列表和模块内容包裹成一个javaScript函数。编译执行改函数。最后通过实参列表和编译好进行函数调用。并返回执行的结果。
MaybeLocal<Value> ExecuteBootstrapper(Environment* env,
const char* id,
std::vector<Local<String>>* parameters,
std::vector<Local<Value>>* arguments) {
EscapableHandleScope scope(env->isolate());
// 把模块编译成javaScript函数
MaybeLocal<Function> maybe_fn = NativeModuleEnv::LookupAndCompile(env->context(), id, parameters, env);
Local<Function> fn;
if (!maybe_fn.ToLocal(&fn)) {
return MaybeLocal<Value>();
}
// 函数的执行,和javaScript的Function.protototy.call类似
MaybeLocal<Value> result = fn->Call(env->context(),
Undefined(env->isolate()),
arguments->size(),
arguments->data());
if (result.IsEmpty()) {
env->async_hooks()->clear_async_id_stack();
}
return scope.EscapeMaybe(result);
}
在上面我们可以知道,在执行 internal\bootstrap\loaders.js模块传递的参数getInternalBinding()
函数的实际调用的是binding::GetInternalBinding()
函数。
void GetInternalBinding(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
// info[0]为javaScript传递过来的模块名称
Local<String> module = args[0].As<String>();
node::Utf8Value module_v(env->isolate(), module);
Local<Object> exports;
// 再上面我们可以知道,模块是以链表的方式保存,模块对象保存着上一个模块的指针,modlist_internal保存了模块聊表尾节点的指针。
// 可以通过 modlist_internal 变量遍历模块链表。
node_module* mod = FindModule(modlist_internal, *module_v, NM_F_INTERNAL);
if (mod != nullptr) {
// 初始化模块
exports = InitModule(env, mod, module);
} else if (!strcmp(*module_v, "constants")) {
// 如果是常量模块,这里加载的常量是为一些模块添加属性,例如os, fs等模块。
exports = Object::New(env->isolate());
CHECK(
exports->SetPrototype(env->context(), Null(env->isolate())).FromJust());
DefineConstants(env->isolate(), exports);
} else if (!strcmp(*module_v, "natives")) {
// native 模块为内建javaScript模块源文件
exports = native_module::NativeModuleEnv::GetSourceObject(env->context());
CHECK(exports
->Set(env->context(),
env->config_string(),
native_module::NativeModuleEnv::GetConfigString(
env->isolate()))
.FromJust());
} else {
char errmsg[1024];
snprintf(errmsg, sizeof(errmsg), "No such module: %s", *module_v);
return THROW_ERR_INVALID_MODULE(env, errmsg);
}
args.GetReturnValue().Set(exports);
}
在上面的内建原生模块注册中可以知道内建原生模块以一个链表的形式去存储所有的注册模块。模块对象存储了上一个模块的指针,并把最后一个链表的指针存储在node::node_module* modlist_internal
的变量中。在调用GetInternalBinding()
函数获取内建模块的时候,通过FindModule()
函数去遍历链表,根据模块名称和链表的尾节点指针查找内建模块。
inline struct node_module* FindModule(struct node_module* list,
const char* name,
int flag) {
struct node_module* mp;
// 以单向链表的方式查找
for (mp = list; mp != nullptr; mp = mp->nm_link) {
if (strcmp(mp->nm_modname, name) == 0) break;
}
return mp;
}
当查找完成后,需要对模块进行初始化,初始化就是通过构建一个v8的javaScript对象。把该对象作为参数调用模块的注册函数。注册函数在模块对象上设置属性的方式,达到模块导出的效果。
static Local<Object> InitModule(Environment* env,
node_module* mod,
Local<String> module) {
Local<Function> ctor = env->binding_data_ctor_template()
->GetFunction(env->context())
.ToLocalChecked();
// 创建一个v8的javaScript对象
Local<Object> exports = ctor->NewInstance(env->context()).ToLocalChecked();
Local<Value> unused = Undefined(env->isolate());
// 在上面可以知道,在使用 NODE_MODULE_CONTEXT_AWARE_INTERNAL(fs_dir, node::fs_dir::Initialize)
// node::fs_dir::Initialize 函数被赋值给node:node_module的nm_context_register_func属性。
// typedef void (*addon_context_register_func)(
// v8::Local<v8::Object> exports,
// v8::Local<v8::Value> module,
// v8::Local<v8::Context> context,
// void* priv);
mod->nm_context_register_func(exports, unused, env->context(), mod->nm_priv);
// 通过v8 javaScript对象的传递引用关系。函数内部通过对exports属性设置属性。
return exports;
}
通过讲解整个模块的创建和加载。我们已经了解了内建模块的加载机制。下面通过通过一个流程图总结一下内建模块的加载机制。
使用v8实现node的内建模块系统
例子使用了wsl开发,clion开发工具。c++14,v8 9.0版本。项目地址。
定义保存node_nodle的类。用于保存内建模块。与node.js不同的是。直接在NodeModule中保存了模块的exports对象。没有再使用一份javaScript做缓存处理。初始化 exports对象是一个空的MaybeLocal对象。通过判断该MaybeLocal对象是否为空可以知道该模块是否被加载过。
/**
* 模块结构体
*/
class NodeModule{
public:
// 模块名称
std::string nodeModuleName;
// 模块注册函数
std::function<void(v8::Local<v8::Context> context,
v8::Local<v8::Object> module,
v8::Local<v8::Object> exports,
v8::Local<v8::Function> require)> nodeModuleRegisterFun;
NodeModule* front = nullptr;
// 模块的导出数据
v8::MaybeLocal<v8::Object> exports = v8::MaybeLocal<v8::Object>();
};
定义一个全局的 NodeModule
指针,用于保存内建模块链表结构的最后一个模块的指针。
// 保存着最后一个内建模块的
static NodeModule* buildInNodeModule = nullptr;
定义internalBinding()
函数,该函数用于获取在javaScript层和c++层获取模块。
/**
* 获取内建模块的函数,
* @param info
*/
void internalBinding(const v8::FunctionCallbackInfo<v8::Value> &info) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
v8::Local<v8::Context> context = isolate->GetCurrentContext();
std::string moduleName(*v8::String::Utf8Value(isolate, info[0].As<v8::String>()));
NodeModule* nodeModule = buildInNodeModule;
// 遍历内建模块链表,根据模块名称获取模块
while (nodeModule != nullptr) {
if (nodeModule->nodeModuleName == moduleName) {
// 如果模块已经加载过。直接从缓存取
if (!nodeModule->exports.IsEmpty()) {
info.GetReturnValue().Set(nodeModule->exports.ToLocalChecked());
}
v8::Local<v8::Object> module = v8::Object::New(isolate);
v8::Local<v8::Object> exports = v8::Object::New(isolate);
EXPECT_TRUE(module->Set(context, v8::String::NewFromUtf8Literal(isolate, "exports"),exports).FromJust());
// 调用注册函数
nodeModule->nodeModuleRegisterFun(context, module, exports, v8::Function::New(context,internalBinding).ToLocalChecked());
// 导出 module对象的exports熟悉值
info.GetReturnValue().Set(module->Get(context, v8::String::NewFromUtf8Literal(isolate, "exports")).ToLocalChecked());
return;
}
nodeModule = nodeModule->front;
}
// 如果没有找到,返回null
info.GetReturnValue().SetNull();
}
定义注册函数,用于程序在执行主模块前执行内建模块的注册。与node.js不同的是。该例子的模块注册函数多了一个require
函数,require
函数用于内建模块可以获取到其他的内建模块的导出函数或者变量。node也可以通过获取Environment对象,再从Environment的方法Environment::get_internal_binding_loader()
获取内建模块的函数。或者通过Environment的方法Environment::native_module_require()
获取内建javaScript模块。
/**
* 内建模块的注册函数
* @param nodeModuleName
* @param nodeModuleRegisterFun
*/
void buildInNodeModuleRegister(std::string nodeModuleName, void(*nodeModuleRegisterFun) (v8::Local<v8::Context> context,
v8::Local<v8::Object> module,
v8::Local<v8::Object> exports,
v8::Local<v8::Function> require)) {
NodeModule* nodeModule = new NodeModule();
nodeModule->nodeModuleName = std::move(nodeModuleName);
nodeModule->nodeModuleRegisterFun = nodeModuleRegisterFun;
// 保存上一个nodeModule到当前的模块下
nodeModule->front = buildInNodeModule;
// 更新最后一个模块
buildInNodeModule = nodeModule;
}
由于 NodeModule
实例通过new运算符去创建。所以定义一个函数,用于在程序退出的时候进行delete。
/**
* 清空内建模块的链表
*/
void clearBuildInNodeModule() {
while (buildInNodeModule != nullptr) {
NodeModule* nodeModule = buildInNodeModule;
buildInNodeModule = buildInNodeModule->front;
delete nodeModule;
}
}
定义两个内建模块foo和bar。
/**
* foo模块
* @param context
* @param module
* @param exports
* @param require
*/
void fooBuildInModule(v8::Local<v8::Context >context,
v8::Local<v8::Object> module,
v8::Local<v8::Object> exports,
v8::Local<v8::Function> require) {
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handleScope(isolate);
// 导出加法函数
EXPECT_TRUE(exports->Set(context, v8::String::NewFromUtf8Literal(isolate,"add"), v8::Function::New(context, [](const v8::FunctionCallbackInfo<v8::Value> &info) -> void {
double first = info[0].As<v8::Number>()->Value();
double second = info[1].As<v8::Number>()->Value();
info.GetReturnValue().Set(first+ second);
}).ToLocalChecked()).FromJust());
EXPECT_TRUE(exports->Set(context,v8::String::NewFromUtf8Literal(isolate,"result"), v8::Number::New(isolate, 1)).FromJust());
}
/**
* 内建bar模块
* @param context
* @param module
* @param exports
* @param require
*/
void barBuildInModule (v8::Local<v8::Context >context,
v8::Local<v8::Object> module,
v8::Local<v8::Object> exports,
v8::Local<v8::Function> require){
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handleScope(isolate);
// 乘法函数
EXPECT_TRUE(exports->Set(context, v8::String::NewFromUtf8Literal(isolate,"mul"),
v8::Function::New(context, [](const v8::FunctionCallbackInfo<v8::Value> &info) -> void {
double first = info[0].As<v8::Number>()->Value();
double second = info[1].As<v8::Number>()->Value();
info.GetReturnValue().Set(first * second);
}).ToLocalChecked()).FromJust());
v8::Local<v8::Value> argv[] = { v8::String::NewFromUtf8Literal(isolate, "foo")};
// 调用foo模块
v8::Local<v8::Object> fooModule = require->Call(context,context->Global(), 1, argv).ToLocalChecked().As<v8::Object>();
// 把模块foo模块的属性值result设置到bar的属性params上
EXPECT_TRUE(exports->Set(context, v8::String::NewFromUtf8Literal(isolate, "params"),
fooModule->Get(context, v8::String::NewFromUtf8Literal(isolate, "result")).ToLocalChecked()).FromJust());
}
编写测试用例。在测试中。先进行内建模块的注册。然后在c++层创建process对象。注入到全局代理对象上。process对象定义了属性binding
指向了internalBinding
函数。这样javaScript下可以通过 process.binding()
的方式去获取内建模块。
TEST_F(Environment, node_build_in_module_test) {
v8::Isolate* isolate = getIsolate();
v8::HandleScope handleScope(isolate);
v8::Local<v8::Context> context = v8::Context::New(isolate);
v8::Context::Scope context_scope(context);
// 注册内建模块
buildInNodeModuleRegister("foo", fooBuildInModule);
buildInNodeModuleRegister("bar", barBuildInModule);
v8::Local<v8::Object> process =v8::Object::New(isolate);
// 设置 process.binding()函数
EXPECT_TRUE(process->Set(context, v8::String::NewFromUtf8Literal(isolate, "binding"),
v8::Function::New(context, internalBinding).ToLocalChecked()).FromJust());
// 设置全局对象process
EXPECT_TRUE(context->Global()->Set(context, v8::String::NewFromUtf8Literal(isolate, "process"), process).FromJust());
const char* source = "const { params, mul } = process.binding('bar');\n"
"const { result, add } = process.binding('foo');\n"
"mul(add(params, result), result);";
v8::Local<v8::Script> script = v8::Script::Compile(context, v8::String::NewFromUtf8(isolate, source).ToLocalChecked()).ToLocalChecked();
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
EXPECT_TRUE(result.As<v8::Number>()->Value() == 2);
// 清空所有的内建模块
clearBuildInNodeModule();
}