如何清理在 Local<External> 引用中跟踪的 C++ 对象?

How can I clean up C++ objects tracked in Local<External> references?

我正在创建纯 C++ 对象(仅来自 C++),然后将它们附加到作为 API 包装器实例返回的 JS 公开对象。附件的方式是使用通过 SetInternalField 存储的 External::New - 几乎完全遵循 V8 Embedder's Guide.

中概述的模式

这些对象本应由 JS 进行内存管理,但即使在 JS 中删除引用并触发垃圾回收后,C++ 对象仍然存在。不调用析构函数,如果我在别处保存一个独立引用,它仍然有效。

我认为 V8 的局部值清理不知道如何(或是否)破坏 void* 的内容是有道理的,但这不是一个突出的用例吗?一般而言,我无法找到以任何方式挂钩到外部或本地的 de-scoping/cleanup。此外,我对 persistents 所做的任何事情(比如 MakeWeak)都不会影响在 JS 端变得无法访问的局部变量(对吧?)。

当包含它们的 JS 包装器对象超出范围时,我如何确保这些 C++ 对象最终(最好是立即)被销毁?


传递给 JS 的实例化示例:

Local<Value> createWindow(HWND handle, Isolate* isolate) {
    // Build an instance from a premade FunctionTemplate with object
    // and method prototypes, and SetInternalFieldCount(1)
    Local<Function> fn = Local<Function>::New(isolate, window);

    constructingInternally = true;
    Local<Object> obj = fn->NewInstance(Context::New(isolate)).ToLocalChecked();
    constructingInternally = false;

    CppWindow* win = CppWindow(handle);
    lastWin = win; // added for debugging
    obj->SetInternalField(0, External::New(isolate, win));
    return obj;
}

更新:经过数小时的盲目、反复试验猜测后,我终于有了可以编译的代码和在对象被垃圾回收时触发的回调。尝试在回调中使用 Local<External>; 或包含的 Local<Object>; 浪费了很多时间,我无缘无故地理解,这些尝试会拒绝编译并出现令人困惑的错误(例如“'Local<External>' 没有成员 'Value'”,或“'Local<Object>' 没有成员 'GetInternalField'”)。当他们最终编译时,在试图到达我的对象的过程中,进程会在回调中默默地死去。最后我明白了,直接传入了一个指向对象的指针,现在我可以直接删除它了。

Local<Value> createWindow(HWND handle, Isolate* isolate) {
    Local<Function> fn = Local<Function>::New(isolate, window);

    constructingInternally = true;
    Local<Object> obj = fn->NewInstance(Context::New(isolate)).ToLocalChecked();
    constructingInternally = false;

    CppWindow* win = CppWindow(handle);
    obj->SetInternalField(0, External::New(isolate, win));

    Persistent<Object>(isolate, obj).SetWeak(
        win,
        [](const WeakCallbackInfo<CppWindow>& data) {
            delete data.GetParameter();
        },
        WeakCallbackType::kParameter
    );

    return obj;
}

没有一个代码示例可以证明这种简单、直接的用例是完全犯罪的。 jmrk 提供的第二个 link 至少让我能够拼凑出最终解决问题所需的一些模糊线索,但这些来源几乎等量地误导了 C++ 新手。

正确,V8 无法神奇地猜测您的 C++ 对象是否存在,或者如何释放它们。

解决这个问题的通常方法是使用 Persistents。对于弱持久句柄,可以设置回调,当 V8 释放对象的最后一个引用时调用。在该回调中,您随后可以调用释放任何依赖的 C++ 对象所需的任何析构函数。

如果您可以预测对象的生命周期(例如,取决于您的应用程序在做什么,"while a request is being handled" 或类似的),一个潜在的替代方案是您自己管理对象。该选项可能更快,可能更容易实现,或者可能不可能,如果你无法预测对象的生命周期。