LotusH LotusH - 2 months ago 27
Node.js Question

Nan can't wrap and unwrap object with v8::Object field

At first I defined it like this(basically the same as the object wrap example in the documentation, the only difference is the example wrapped a

double
value property, but mine is an
v8::Object
):

.h:

#include <nan.h>

using v8::Local;
using v8::Object;
using Nan::FunctionCallbackInfo;

class MyObject : public Nan::ObjectWrap {
public:
static void Init(v8::Local<v8::Object> module);

private:
explicit MyObject(Local<Object>);
~MyObject();

static Nan::Persistent<v8::Function> constructor;
static void New(const FunctionCallbackInfo<v8::Value>& info);
static void GetConfig(const FunctionCallbackInfo<v8::Value>& info);

Local<Object> Config;
};


.cc:

using v8::Local;
using v8::Object;

Nan::Persistent<v8::Function> MyObject::constructor;
MyObject::MyObject(Local<Object> config) : Config(config){}
MyObject::~MyObject(){}

// return an object when required
void MyObject::Init(Local<Object> module)
{
Nan::HandleScope scope;

Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("myObject").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);

// prototypes
Nan::SetPrototypeMethod(tpl, "getConfig", GetConfig);

constructor.Reset(tpl->GetFunction());
module->Set(Nan::New("exports").ToLocalChecked(), tpl->GetFunction());
}

void MyObject::New(const Nan::FunctionCallbackInfo<v8::Value>& info) {
Local<Object> conf = info[0].As<Object>();
MyObject* myObject = new MyObject(conf);
MyObject->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}

void MyObject::GetConfig(const Nan::FunctionCallbackInfo<v8::Value>& info)
{
MyObject* myObject = ObjectWrap::Unwrap<MyObject>(info.Holder());
Local<Object> conf = myObject->Config;

info.GetReturnValue().Set(conf);
}


js:

var Test = require("./build/Release/test");
var test = new Test({a: 1});
console.log(test.getConfig()); // logs 3276x randomly


Seems the
Config
field got garbage collected. If that was the situation, I don't understand why, since
MyObject
instance is clearly still in scope. But still I tried to make
Config
persistent.

Interestingly, directly changing
Config
's type to
Persistent<Object> *
didn't work either, but when I added these seemed irrelevant lines to test whether I passed the right object from js before the object got wrapped in
MyObject::New()
, it worked:

Local<Object> test = Nan::New(*MyObject->Config);
Local<v8::String> v8Str = Nan::To<v8::String> (test->Get(Nan::New("a").ToLocalChecked())).ToLocalChecked();
v8::String::Utf8Value v8StrUtf8(v8Str);
std::string str = std::string(*v8StrUtf8);
std::cout << str << std::endl;


What's wrong here? What's the right way to wrap a v8::Object? Why those property accessing lines can make it work?

Answer

If you want to hold onto a value for a longer period of time (beyond the current scope), you need to make the reference a Persistent instead of a Local. Doing so will prevent the value from being garbage collected.

So change your Config definition to:

Nan::Persistent<Object> Config;

I haven't tested it, but you may also need to change your constructor to:

MyObject::MyObject(Local<Object> config) {
  Config.Reset(config);
}

Then when you want to retrieve the value you would need to instead get a Local handle from the Persistent like:

Local<Object> conf = Nan::New(myObject->Config);
Comments