上下文感知的插件
在某些环境中,可能需要在多个上下文中多次加载 Node.js 插件。
例如,Electron 运行时在单个进程中运行多个 Node.js 实例。
每个实例都有自己的 require()
缓存,因此当通过 require()
加载时,每个实例都需要原生插件才能正确运行。
可以使用宏 NODE_MODULE_INITIALIZER
构建上下文感知插件,该宏扩展为 Node.js 在加载插件时期望找到的函数的名称。
因此可以像下面的示例一样初始化插件:
using namespace v8;
extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
/* 在此处执行插件初始化步骤。 */
}
另一种选择是使用宏 NODE_MODULE_INIT()
,它也将构建上下文感知插件。
与 NODE_MODULE()
不同,NODE_MODULE()
用于围绕给定的 addon 初始化函数构造插件,而 NODE_MODULE_INIT()
用作此类初始化器的声明,然后是函数体。
在调用 NODE_MODULE_INIT()
之后,可以在函数体内使用以下三个变量:
Local<Object> exports
,Local<Value> module
,和Local<Context> context
选择构建上下文感知插件承担着仔细管理全局静态数据的责任。 由于插件可能被多次加载,甚至可能来自不同的线程,因此必须适当保护存储在插件中的任何全局静态数据,并且不得包含对 JavaScript 对象的任何持久引用。 这样做的原因是 JavaScript 对象仅在上下文中有效,并且当从错误的上下文或从与创建它们的线程不同的线程访问时,可能会导致崩溃。
通过执行以下步骤,可以构造上下文感知插件以避免全局静态数据:
- 定义一个类,该类将保存每个插件实例数据并具有该形式的静态成员
static void DeleteInstance(void* data) { // 将 `data` 转换为类的实例并将其删除。 }
- 在插件初始值设定项中堆分配此类的实例。
这可以使用
new
关键字来完成。 - 调用
node::AddEnvironmentCleanupHook()
,将上面创建的实例和指向DeleteInstance()
的指针传给它。 这将确保在拆除环境时删除实例。 - 将类的实例存储在
v8::External
中,并且 - 通过将
v8::External
传给创建原生支持的 JavaScript 函数的v8::FunctionTemplate::New()
或v8::Function::New()
,将v8::External
传给所有暴露给 JavaScript 的方法。v8::FunctionTemplate::New()
或v8::Function::New()
的第三个参数接受v8::External
并使用v8::FunctionCallbackInfo::Data()
方法使其在原生回调中可用。
这将确保每个插件实例数据到达可以从 JavaScript 调用的每个绑定。 每个插件实例数据还必须传入到插件可能创建的任何异步回调中。
以下示例说明了上下文感知插件的实现:
#include <node.h>
using namespace v8;
class AddonData {
public:
explicit AddonData(Isolate* isolate):
call_count(0) {
// 确保在环境清理时删除此每个插件实例的数据。
node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
}
// 每个插件的数据。
int call_count;
static void DeleteInstance(void* data) {
delete static_cast<AddonData*>(data);
}
};
static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
// 检索每个插件实例的数据。
AddonData* data =
reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
data->call_count++;
info.GetReturnValue().Set((double)data->call_count);
}
// 将此插件初始化为上下文感知。
NODE_MODULE_INIT(/* exports, module, context */) {
Isolate* isolate = context->GetIsolate();
// 为该插件实例创建新的 `AddonData` 实例,
// 并将其生命周期与 Node.js 环境的生命周期联系起来。
AddonData* data = new AddonData(isolate);
// 将数据包装在 `v8::External` 中,
// 以便可以将其传给暴露的方法。
Local<External> external = External::New(isolate, data);
// 将方法 `Method` 暴露给 JavaScript,
// 并通过将 `external` 作为第三个参数传给 `FunctionTemplate` 构造函数
// 来确保它接收到上面创建的每个插件实例的数据。
exports->Set(context,
String::NewFromUtf8(isolate, "method", NewStringType::kNormal)
.ToLocalChecked(),
FunctionTemplate::New(isolate, Method, external)
->GetFunction(context).ToLocalChecked()).FromJust();
}
There are environments in which Node.js addons may need to be loaded multiple
times in multiple contexts. For example, the Electron runtime runs multiple
instances of Node.js in a single process. Each instance will have its own
require()
cache, and thus each instance will need a native addon to behave
correctly when loaded via require()
. From the addon's perspective, this means
that it must support multiple initializations.
A context-aware addon can be constructed by using the macro
NODE_MODULE_INITIALIZER
, which expands to the name of a function which Node.js
will expect to find when it loads an addon. An addon can thus be initialized as
in the following example:
using namespace v8;
extern "C" NODE_MODULE_EXPORT void
NODE_MODULE_INITIALIZER(Local<Object> exports,
Local<Value> module,
Local<Context> context) {
/* Perform addon initialization steps here. */
}
Another option is to use the macro NODE_MODULE_INIT()
, which will also
construct a context-aware addon. Unlike NODE_MODULE()
, which is used to
construct an addon around a given addon initializer function,
NODE_MODULE_INIT()
serves as the declaration of such an initializer to be
followed by a function body.
The following three variables may be used inside the function body following an
invocation of NODE_MODULE_INIT()
:
Local<Object> exports
,Local<Value> module
, andLocal<Context> context
The choice to build a context-aware addon carries with it the responsibility of carefully managing global static data. Since the addon may be loaded multiple times, potentially even from different threads, any global static data stored in the addon must be properly protected, and must not contain any persistent references to JavaScript objects. The reason for this is that JavaScript objects are only valid in one context, and will likely cause a crash when accessed from the wrong context or from a different thread than the one on which they were created.
The context-aware addon can be structured to avoid global static data by performing the following steps:
- Define a class which will hold per-addon-instance data and which has a static
member of the form
static void DeleteInstance(void* data) { // Cast `data` to an instance of the class and delete it. }
- Heap-allocate an instance of this class in the addon initializer. This can be
accomplished using the
new
keyword. - Call
node::AddEnvironmentCleanupHook()
, passing it the above-created instance and a pointer toDeleteInstance()
. This will ensure the instance is deleted when the environment is torn down. - Store the instance of the class in a
v8::External
, and - Pass the
v8::External
to all methods exposed to JavaScript by passing it tov8::FunctionTemplate::New()
orv8::Function::New()
which creates the native-backed JavaScript functions. The third parameter ofv8::FunctionTemplate::New()
orv8::Function::New()
accepts thev8::External
and makes it available in the native callback using thev8::FunctionCallbackInfo::Data()
method.
This will ensure that the per-addon-instance data reaches each binding that can be called from JavaScript. The per-addon-instance data must also be passed into any asynchronous callbacks the addon may create.
The following example illustrates the implementation of a context-aware addon:
#include <node.h>
using namespace v8;
class AddonData {
public:
explicit AddonData(Isolate* isolate):
call_count(0) {
// Ensure this per-addon-instance data is deleted at environment cleanup.
node::AddEnvironmentCleanupHook(isolate, DeleteInstance, this);
}
// Per-addon data.
int call_count;
static void DeleteInstance(void* data) {
delete static_cast<AddonData*>(data);
}
};
static void Method(const v8::FunctionCallbackInfo<v8::Value>& info) {
// Retrieve the per-addon-instance data.
AddonData* data =
reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());
data->call_count++;
info.GetReturnValue().Set((double)data->call_count);
}
// Initialize this addon to be context-aware.
NODE_MODULE_INIT(/* exports, module, context */) {
Isolate* isolate = context->GetIsolate();
// Create a new instance of `AddonData` for this instance of the addon and
// tie its life cycle to that of the Node.js environment.
AddonData* data = new AddonData(isolate);
// Wrap the data in a `v8::External` so we can pass it to the method we
// expose.
Local<External> external = External::New(isolate, data);
// Expose the method `Method` to JavaScript, and make sure it receives the
// per-addon-instance data we created above by passing `external` as the
// third parameter to the `FunctionTemplate` constructor.
exports->Set(context,
String::NewFromUtf8(isolate, "method", NewStringType::kNormal)
.ToLocalChecked(),
FunctionTemplate::New(isolate, Method, external)
->GetFunction(context).ToLocalChecked()).FromJust();
}