2

I'm new to NAPI, and I'm trying to convert and old Nan code to NAPI.

What happens is that I have a structure like this:

class PointWrapper : public Napi::ObjectWrap<PointWrapper> {
public:
  static void init(Napi::Env env, Napi::Object exports);
  PointWrapper(const Napi::CallbackInfo& info);
private:
  Point point;
}

And I wrapped everything in the right way, so if I call on JS new Pointer(1, 2) it'll instantiate a PointerWrapper and set the right fields to Point. So far, so good.

Now, the problem is that somewhere later I have a C++ code that wraps a Range - a Range is basically start and end, each containing a Point.

I also have RangeWrapper that does the same thing as PointWrapper, but for range. This RangeWrapper have a getStart that basically needs to return a PointWrapper.

Now, how do I instantiate a PointWrapper from RangeWrapper? Basically, I want a constructor on PointWrapper that, giving a Point, I can get a PointWrapper, all this in C++ and not on JS. Is it possible? Every code I saw tried to instantiate from inside PointWrapper, never outside

Maurício Szabo
  • 677
  • 1
  • 6
  • 13

1 Answers1

1

Ok, so I found a solution - it's clumsy, but at least it works. The first thing I had to do was to make the "constructor" a variable that other places could use. So I changed my class to have a *constructor pointer:

class PointWrapper : public Napi::ObjectWrap<PointWrapper> {
public:
  static void init(Napi::Env env, Napi::Object exports);
  PointWrapper(const Napi::CallbackInfo& info);
  static Napi::FunctionReference *constructor; // <-- Added this
private:
  Point point;
};

Then on my init implementation (file point-wrapper.cpp), I am using this constructor to "construct" the Point:

Napi::FunctionReference *PointWrapper::constructor; // <-- Added this
void PointWrapper::init(Napi::Env env, Napi::Object exports) {
  Napi::Function func = DefineClass(env, "Point", {
    // methods, etc...
  });

  constructor = new Napi::FunctionReference(); // <-- Used it here
  *constructor = Napi::Persistent(func); // <-- and here
  exports.Set("Point", func);
}

So, to instantiate the PointWrapper what I need is to call the constructor then unwrap things. Now, the class itself only have one possible constructor, that receives a Napi::CallbackInfo& info. To instantiate this class with something from C++ side, we need to "wrap" a C++ object into Napi::External:

// Supposing you have a point already created:
auto wrapped = Napi::External<Point>::New(env, &Point);
Napi::Value pointWrapperAsVal = PointWrapper::constructor->New({ wrapped });

Finally, you make the controller understand that it can be called with a Napi::External object:

PointWrapper::PointWrapper(const Napi::CallbackInfo& info) 
                   : Napi::ObjectWrap<PointWrapper>(info) {
  if(info[0].IsExternal()) {
    auto point = info[0].As<Napi::External<Point>>();
    this->point = *point.Data();
  } else {
    // normal code
  }
}

Remember that pointWrapperAsVal is a Napi::Value. You may need to convert to the right "type" if you need to use it from CPP - for example, with PointWrapper::Unwrap(pointWrapperAsVal)

Maurício Szabo
  • 677
  • 1
  • 6
  • 13
  • Thanks for your answer, I've been able to apply this to my use case (where I use a smart pointer for the c++ class) and it works properly, however I have a question, why there is not a Wrap method if there is an Unwrap one in Napi::ObjectWrap... – gabry May 31 '23 at 14:36