I have two simple classes A
and B
that I'm trying to expose in a native module in node.js. A
is directly creatable, but a B
is only created by calling A::foo()
.
class Internal {};
class B {
public:
Internal internal;
explicit B(Internal internal):internal(internal){}
};
class A {
public:
A() : internal() {};
B foo() { return B(internal); }
private:
Internal internal;
};
I want to be able to write:
const M = require('node_nan_minimal');
const a = new M.A();
const b = a.foo();
To do this I'm creating two wrapper classes deriving from Nan::ObjectWrap
class AWrapper : public Nan::ObjectWrap { ... }
class BWrapper : public Nan::ObjectWrap { ... }
Each contains an instance of A
or B
respectively. With these I can create an object of type A from within javascript, but I'm having trouble with the implementation of AWrapper::foo
.
static NAN_METHOD(foo) {
AWrapper* obj = Nan::ObjectWrap::Unwrap<AWrapper>(info.Holder());
B b = obj->a_.foo();
BWrapper * result = new BWrapper(b);
// Something to get a B object to javascript
// ...
// info.GetReturnValue().Set(result->Wrap());
// ...
// doesn't work - so what should it be?
}
What do I do to make this function work?
The complete code for the .cc file is
#include <node.h>
#include <nan.h>
class Internal {
};
class B {
public:
Internal internal;
explicit B(Internal internal):internal(internal){}
};
class A {
public:
A() : internal() {};
B foo() { return B(internal); }
private:
Internal internal;
};
class BWrapper : public Nan::ObjectWrap {
public:
B b_;
explicit BWrapper(B b) : b_(b) {}
~BWrapper() {}
};
class AWrapper : public Nan::ObjectWrap {
public:
A a_;
explicit AWrapper(A a) : a_(a) {}
~AWrapper() {}
static void register_class(Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target) {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("A").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(tpl, "foo", foo);
constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
Nan::Set(target, Nan::New("A").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked());
}
private:
static NAN_METHOD(New) {
if (info.IsConstructCall()) {
A a;
AWrapper *obj = new AWrapper(a);
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
} else {
const int argc = 1;
v8::Local<v8::Value> argv[argc] = {info[0]};
v8::Local<v8::Function> cons = Nan::New(constructor());
info.GetReturnValue().Set(cons->NewInstance(argc, argv));
}
}
static NAN_METHOD(foo) {
AWrapper* obj = Nan::ObjectWrap::Unwrap<AWrapper>(info.Holder());
B b = obj->a_.foo();
BWrapper * result = new BWrapper(b);
// Something to get a B object to javascript
//...
//info.GetReturnValue().Set(result->Wrap());
}
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
};
NAN_MODULE_INIT(InitModule) {
AWrapper::register_class(target);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, InitModule);
And a complete repository of the example can be found at https://github.com/mikeando/node_nan_minimal which you should be able to clone and then build using npm install
.
One way to make this work is:
init_class
function.foo
function call the BWrapper::NewInstance
.This amounts to the following additions
class BWrapper {
...
static void init_class() {
v8::Local<v8::FunctionTemplate> tpl = Nan::New<v8::FunctionTemplate>(New);
tpl->SetClassName(Nan::New("B").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
constructor().Reset(Nan::GetFunction(tpl).ToLocalChecked());
}
static NAN_METHOD(New) {
if (!info.IsConstructCall()) {
return Nan::ThrowError("File() must be called as a constructor");
}
if (info.Length() != 1 || ! info[0]->IsExternal()) {
return Nan::ThrowError("File() can only be called internally");
}
B* b = static_cast<B*>(info[0].As<v8::External>()->Value());
BWrapper *obj = new BWrapper(*b);
obj->Wrap(info.This());
info.GetReturnValue().Set(info.This());
}
static v8::Local<v8::Object> NewInstance(B* b) {
Nan::EscapableHandleScope scope;
const unsigned argc = 1;
v8::Local<v8::Value> argv[argc] = { Nan::New<v8::External>(b) };
v8::Local<v8::Function> cons = Nan::New<v8::Function>(constructor());
v8::Local<v8::Object> instance = cons->NewInstance(argc, argv);
return scope.Escape(instance);
}
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
}
And the changes to AWrapper::foo
are:
static NAN_METHOD(foo) {
AWrapper* obj = Nan::ObjectWrap::Unwrap<AWrapper>(info.Holder());
B b = obj->a_.foo();
info.GetReturnValue().Set(BWrapper::NewInstance(&b));
}
And finally we need to ensure the class gets registered.
NAN_MODULE_INIT(InitModule) {
BWrapper::init_class();
AWrapper::register_class(target);
}
I suspect this is not the cleanest way to do it, and I'd love to see any alternatives. I'm particularly interested in whether exposing the constructor to BWrapper in this way has any downsides.
Partly this was taken from reading https://github.com/tracelytics/node-traceview-bindings/blob/master/src/metadata.cc#L18 at the suggestion of another question and then a dash of my own experimentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With