januw a
januw a

Reputation: 2248

How to get the correct js context in v8

I am trying the c++ plugin in nodejs, this is my test code

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);

When I call the function again with a delay of one second in js, this error will appear

  obj.run(3);
      ^

[TypeError: obj.run is not a function]

This is the C++ code of the plug-in. The plug-in exports an object. When instantiating the object in js, a callback function needs to be initialized. When the run function is called, the callback is called. I don't know where the problem is. How can I fix the code

#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)

my node version

λ node -v
v14.15.4

Upvotes: 1

Views: 1311

Answers (2)

januw a
januw a

Reputation: 2248

move Eternal initialization to the constructor

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);
  }
}

Upvotes: 0

jmrk
jmrk

Reputation: 40501

As I told you in the last question you asked: don't store pointers to stack-allocated objects. This has nothing to do with V8 or contexts.

In function New, Eternal<Function> e_cb is a stack-allocated object. The constructor call new MyClass(..., &e_cb) creates and passes along a pointer to this stack-allocated object. But as soon as that function returns, all its stack-allocated objects will get torn down, so the pointer will then point to an invalid stack slot (likely reused and hence filled with "random" data). Of course, the same is true for context_, but you're not using that anywhere, so the fact that it's broken is invisible.

In this specific case, you can simply pass the Eternal by value instead, or create it in the MyClass constructor. In general, I recommend reading up on C++ basics.

As for getting the "correct" context: what's wrong with Isolate::GetCurrentContext(), which you are already using?

Upvotes: 1

Related Questions