如何在 v8 中获取正确的 js 上下文

How to get the correct js context in v8

我正在尝试 nodejs 中的 c++ 插件,这是我的测试代码

let obj = new addon.MyClass(function (v) {
  console.log(v);
});
obj.run(1);
setTimeout(() => {
  obj.run(3); // [TypeError: obj.run is not a function]
}, 1000);

当我在js中延迟一秒再次调用该函数时,会出现这个错误

  obj.run(3);
      ^

[TypeError: obj.run is not a function]

这是插件的C++代码。插件导出一个对象。在js中实例化对象时,需要初始化一个回调函数。当运行函数被调用时,回调被调用。我不知道问题出在哪里。我该如何修复代码

#include <node.h>
#include <node_object_wrap.h>

using namespace v8;

#define V8_STR(str) String::NewFromUtf8(isolate, str, NewStringType::kNormal).ToLocalChecked()

class MyClass : public node::ObjectWrap
{
private:
  Eternal<Context>* context_;
  Eternal<Function>* cb_;

public:
  explicit MyClass(Eternal<Context>* context, Eternal<Function>* cb) : cb_(cb), context_(context) {
  }

  static void Init(Local<Object> exports) {
    Isolate* isolate = exports->GetIsolate();
    Local<Context> context = isolate->GetCurrentContext();

    Local<ObjectTemplate> class_tpl = ObjectTemplate::New(isolate);
    class_tpl->SetInternalFieldCount(1);
    Local<Object> obj = class_tpl->NewInstance(context).ToLocalChecked();

    Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New, obj);
    tpl->SetClassName(V8_STR("MyClass"));
    tpl->InstanceTemplate()->SetInternalFieldCount(1);

    NODE_SET_PROTOTYPE_METHOD(tpl, "run", run);

    Local<Function> constructor = tpl->GetFunction(context).ToLocalChecked();
    obj->SetInternalField(0, constructor);

    exports->Set(context, V8_STR("MyClass"), constructor).FromJust();
  };

  static void New(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    Eternal<Context> e_context(isolate, isolate->GetCurrentContext());
    Eternal<Function> e_cb(isolate, args[0].As<Function>());
    MyClass* obj = new MyClass(&e_context, &e_cb);
    obj->Wrap(args.This());
  }

  static void run(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    HandleScope scope(isolate);

    auto context = Context::New(isolate);

    MyClass* self = ObjectWrap::Unwrap<MyClass>(args.Holder());

    const unsigned argc = 1;
    Local<Value> argv[argc] = { args[0] };

    // call callback
    self->cb_->Get(isolate)->Call(context, Null(isolate), argc, argv);
  }

};

void Initialize(Local<Object> exports) {
  MyClass::Init(exports);
}

NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

我的节点版本

λ node -v
v14.15.4

正如我在您提出的上一个问题中告诉您的那样:不要存储指向堆栈分配对象的指针。这与 V8 或上下文无关。

在函数New中,Eternal<Function> e_cb是一个堆栈分配的对象。构造函数调用 new MyClass(..., &e_cb) 创建并传递指向此堆栈分配对象的指针。但是一旦那个函数returns,它所有的堆栈分配对象都会被拆除,所以指针将指向一个无效的堆栈槽(可能被重用并因此填充“随机”数据)。当然,context_ 也是如此,但你没有在任何地方使用它,所以它坏掉的事实是不可见的。

在这种特定情况下,您可以简单地按值传递 Eternal,或者在 MyClass 构造函数中创建它。一般来说,我建议阅读 C++ 基础知识。

至于获取“正确”上下文:您已经在使用的 Isolate::GetCurrentContext() 有什么问题?

将 Eternal 初始化移至构造函数

class MyClass : public node::ObjectWrap
{
private:
  Eternal<Function> cb_;

public:
  explicit MyClass(const FunctionCallbackInfo<Value>& args)
  {
    Isolate* isolate = args.GetIsolate();
    cb_ = Eternal<Function>(isolate, args[0].As<Function>());
  }

...

 static void New(const FunctionCallbackInfo<Value>& args) {
    MyClass* obj = new MyClass(args);
    obj->Wrap(args.This());
  }

...

  static void run(const FunctionCallbackInfo<Value>& args) {
    Isolate* isolate = args.GetIsolate();
    auto context = isolate->GetCurrentContext();
    MyClass* self = ObjectWrap::Unwrap<MyClass>(args.Holder());

    const unsigned argc = 1;
    Local<Value> argv[argc] = { args[0] };
    self->cb_.Get(isolate)->Call(context, Null(isolate), argc, argv);
  }
}