【问题标题】:Understanding Node Addon API (N-API) HandleScope了解 Node Addon API (N-API) HandleScope
【发布时间】:2020-01-20 15:59:54
【问题描述】:

我很难理解如何正确使用HandleScopeEscapableHandleScope。比如来自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 的示例,什么时候需要?

【问题讨论】:

    标签: node.js nan v8 node.js-addon


    【解决方案1】:

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

    [Handle Scope] 是一种抽象,用于控制和修改在特定范围内创建的对象的生命周期。通常,N-API 值是在句柄范围的上下文中创建的。 当从 JavaScript 调用原生方法时,将存在默认句柄范围。 如果用户没有显式创建新的句柄范围,则将在默认句柄范围内创建 N-API 值。对于本地方法执行之外的任何代码调用(例如,在 libuv 回调调用期间),在调用可能导致创建 JavaScript 的任何函数之前,该模块需要创建一个范围价值观。

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

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

    【讨论】:

      【解决方案2】:

      有关 HandleScope 是什么以及它们的用途的说明,请参阅 V8's documentation,例如对于班级Local

      有两种类型的句柄:本地句柄和持久句柄。

      本地句柄是轻量级和瞬态的,通常用于 本地操作。它们由 HandleScopes 管理。这意味着一个 HandleScope 在创建时必须存在于堆栈中,并且它们仅在 HandleScope 活动期间有效 创建。为了将本地句柄传递给外部 HandleScope,一个 必须使用 EscapableHandleScope 及其 Escape() 方法。

      对于班级HandleScope

      控制多个本地句柄的堆栈分配类。后 已创建句柄范围,将分配所有本地句柄 在该句柄范围内,直到句柄范围被删除或 创建另一个句柄范围。如果已经有句柄范围 并创建一个新的,所有分配都将在新的 处理范围,直到它被删除。之后,新的手柄将再次 在原始句柄范围内分配。

      本地句柄的句柄范围被删除后的垃圾 收集器将不再跟踪存储在句柄中的对象,并且可能 释放它。访问句柄的行为 句柄范围已被删除未定义。

      务实:

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

      【讨论】:

      • 这是不正确的。 HandleScope 在 C++ 方法的顶层通常是多余的。引用 NAPI 文档:“默认范围的生命周期与本机方法调用的生命周期相关联。结果是,默认情况下,句柄保持有效,并且与这些句柄关联的对象将在本机方法调用。”
      • 这与我写的并不矛盾。你总是需要一个 HandleScope。如果某个抽象层(如 NAPI)已经隐式创建了一个(你提到的这个“默认范围”),那么你不需要手动创建另一个。
      • 但是 OP 的问题是关于 Node Addon API 和 Napi::HandleScope,而不是关于 V8 和 v8::HandleScope。
      • 嗯,我明白为什么在工厂中创建对象需要 EscapableHandleScope 。但这意味着任何 getter 函数也应该使用EscapableHandleScope!虽然 node-addon-api 示例不是这样的。这是为什么呢?
      • @norekhov:回到 JavaScript 时,你根本不需要任何 HandleScopes(它们是 C++ 概念)。 EscapableHandleScopes 用于返回其他 C++(以及因此 HandleScope 管理的)代码。
      【解决方案3】:

      为什么我们需要在sn-p中新建一个HandleScope?

      其实这里我们可以选择不新建HandleScope。 Node.js 中有一个外部作用域,它包含我们在此函数中创建的句柄。但是,在这个函数中创建的所有句柄都会比必要的寿命更长,并且会消耗资源。

      我们什么时候需要 EscapableHandleScope?

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

      参考

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

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

      【讨论】:

        猜你喜欢
        • 2022-10-21
        • 2020-11-26
        • 1970-01-01
        • 1970-01-01
        • 2019-06-21
        • 1970-01-01
        • 1970-01-01
        • 2020-03-01
        • 2023-03-17
        相关资源
        最近更新 更多