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 theBWrapper::NewInstance
.This amounts to the following additions
And the changes to
AWrapper::foo
are:And finally we need to ensure the class gets registered.
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.