了解节点插件 API (N-API) HandleScope

Understanding Node Addon API (N-API) HandleScope

我很难理解如何正确使用 HandleScope and EscapableHandleScope. For example, from this Node example:

MyObject::MyObject(const Napi::CallbackInfo& info) : Napi::ObjectWrap<MyObject>(info) {
  Napi::Env env = info.Env();
  Napi::HandleScope scope(env);

  this->val_ = info[0].As<Napi::Number>().DoubleValue();
};

为什么我们需要在这种情况下创建一个新的 HandleScope?从 this other example:

Napi::Object CreateObject(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();
  Napi::Object obj = Napi::Object::New(env);
  obj.Set(Napi::String::New(env, "msg"), info[0].ToString());

  return obj;
}

这里为什么不需要?

此外,我没有找到任何使用 EscapableHandleScope 的示例,什么时候需要这个?

有关 HandleScope 是什么以及它们的用途的解释,请参阅 V8's documentation,例如对于 class Local:

There are two types of handles: local and persistent handles.

Local handles are light-weight and transient and typically used in local operations. They are managed by HandleScopes. That means that a HandleScope must exist on the stack when they are created and that they are only valid inside of the HandleScope active during their creation. For passing a local handle to an outer HandleScope, an EscapableHandleScope and its Escape() method must be used.

而对于 class HandleScope:

A stack-allocated class that governs a number of local handles. After a handle scope has been created, all local handles will be allocated within that handle scope until either the handle scope is deleted or another handle scope is created. If there is already a handle scope and a new one is created, all allocations will take place in the new handle scope until it is deleted. After that, new handles will again be allocated in the original handle scope.

After the handle scope of a local handle has been deleted the garbage collector will no longer track the object stored in the handle and may deallocate it. The behavior of accessing a handle for which the handle scope has been deleted is undefined.

务实地:

  • 从 JavaScript 调用 C++ 时,如果 C++ 代码创建任何 Local<>,则至少需要一个 HandleScope。通常只有一个 HandleScope 是正确的数字。
  • 创建和销毁 HandleScope 是有代价的,所以如果您有许多细粒度的 HandleScope,那是在浪费时间。另一方面,HandleScope(通过设计,这就是它的目的!)使其中包含的句柄所引用的所有对象保持活动状态(在 GC 意义上),因此对于非常长的 运行 代码或循环对于多次迭代,您可能希望引入短期 HandleScopes,以便可以释放您已完成的临时对象。
  • 如文档所述,如果您想要 return 一个超出作用域生命周期结束的对象,则需要一个 EscapableHandleScope

以下似乎适用于 nan、N-API 和 node-addon-api:

[Handle Scope] is an abstraction used to control and modify the lifetime of objects created within a particular scope. In general, N-API values are created within the context of a handle scope. When a native method is called from JavaScript, a default handle scope will exist. If the user does not explicitly create a new handle scope, N-API values will be created in the default handle scope. For any invocations of code outside the execution of a native method (for instance, during a libuv callback invocation), the module is required to create a scope before invoking any functions that can result in the creation of JavaScript values.

来源:https://nodejs.org/api/n-api.html#n_api_napi_handle_scope

这意味着在给出的示例中,因为两者都期望 const Napi::CallbackInfo& info 很明显它们都是直接从 JavaScript 调用的,所以它们已经具有 JS 运行时提供的范围 - 额外的调用只有当你想自己执行内存管理,或者你的代码独立于 JS 引擎执行的情况下(例如在计时器上,来自 JS 代码以外的回调等),才需要创建范围

为什么我们需要在代码片段中创建一个新的 HandleScope?

其实这里我们可以选择不新建HandleScope。 Node.js 中有一个外部范围,它包含我们在此函数中创建的句柄。但是在这个函数中创建的所有句柄都将比必要的寿命更长并且处理成本资源。

我们什么时候需要 EscapableHandleScope?

当来自内部作用域的句柄需要超过该作用域的生命周期时。例如,返回函数中新创建的数据时。

参考

V8 嵌入:https://v8.dev/docs/embed#handles-and-garbage-collection

节点插件-api:https://github.com/nodejs/node-addon-api/blob/master/doc/object_lifetime_management.md