Search code examples
c++node.jsconstructornode-addon-api

Node-addon-api - construct ObjectWrap from C++


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


Solution

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