Node-addon-api: Initialize object wrap from C++

Created on 1 Aug 2017  路  19Comments  路  Source: nodejs/node-addon-api

How do I do that?

I have this code in https://github.com/mcollina/native-hdr-histogram/blob/master/hdr_histogram_wrap.cc#L154-L158, and there seem no clear way on how to port it.

stale

Most helpful comment

Is it possible to create an instance without going through the constructor at all? Just like in JS you can swap the prototype.

All 19 comments

  1. The HdrHistogramWrap class should extend Napi::ObjectWrap<HdrHistogramWrap>. See https://github.com/jasongin/node-canvas/blob/napi/src/Canvas.h#L52 for an example.

  2. The Napi::ObjectWrap::DefineClass() method returns a Napi::Function that is the class constructor. You should save that constructor for later use. Typically that is done using a Napi::FunctionReference static member. See https://github.com/jasongin/node-canvas/blob/napi/src/Canvas.cc#L60 for an example.

  3. Create an instance of the class by calling Napi::FunctionReference::New() on the constructor reference. That returns a Napi::Object.

  4. Unwrap the object by calling HdrHistogramWrap::Unwrap(). That returns a HdrHistogramWrap*.

I think canvas need to be updated to current ObjectWrap constructor.

@sampsongao You're right, it is out of date, since we recently changed the constructor.

That worked. All the steps above should be in an example or (better) a unit test.

@mcollina thanks a lot for your valuable feedback- this has been awesome, please keep it coming! (and we welcome PRs too 馃槈)

@jasongin Is there any examples of how the new ObjectWrap constructor is used?
I have tried to follow along with node-canvas, and I cant figure out the right way to create a new object from a "parent" object. I have a PdfDocument class which is wrapped in an ObjectWrap and has a method GetPage(int n) which gets the underlying PoDoFo::PdfPage. This PdfPage is what I have attempted to wrap in the code below ( here's a reference to the call I am making PoDoFo. It's the result of this call that I wish to wrap in the code below ) I can't figure out how to first of all invoke this wrapper class properly from another wrapper, and secondly how to pass the PdfPage class that was returned from the PoDoFo::PdfDocument::GetPage

#include <napi.h>
#include <podofo/podofo.h>

using namespace Napi;
using namespace std;

class Page : public Napi::ObjectWrap<Page>
{
public:
  explicit Page(const CallbackInfo& callbackInfo); // can this take another parameter? PoDoFo::PdfPage* 
  ~Page() { delete _page; }
  static Napi::FunctionReference constructor;
  static void Initialize(Napi::Env& env, Napi::Object& target)
  {
    Napi::HandleScope scope(env);
    Napi::Function ctor =
      DefineClass(env,
                  "PdfPage",
                  { InstanceMethod("getRotation", &Page::GetRotation),
                    InstanceMethod("getNumFields", &Page::GetNumFields),
                    InstanceMethod("getField", &Page::GetField),
                    InstanceMethod("setRotation", &Page::SetRotation) },
                  nullptr);
    constructor = Napi::Persistent(ctor);
    constructor.SuppressDestruct();
    target.Set("Page", constructor);
  }
  Napi::Value GetRotation(const CallbackInfo&);
  Napi::Value GetNumFields(const CallbackInfo&);
  Napi::Value GetField(const CallbackInfo&);
  void SetRotation(const CallbackInfo&);
  inline PoDoFo::PdfPage* page() { return _page; }
  void SetPdfPage(PoDoFo::PdfPage* p) { _page = p; } // or do I have to create the class and then set the result from PoDoFo::PdfDocument::GetPage()

private:
  PoDoFo::PdfPage* _page;
};

// Document ...
Napi::Value
Document::GetPage(const CallbackInfo& info)
{
  if (info.Length() != 1 || info[0].IsNumber() == false) {
    throw Napi::Error::New(info.Env(),
                           "getPage takes a single argument, of type number.");
  }
  int n = info[0].As<Number>();
  PoDoFo::PdfPage* page = _document->GetPage(n);
  auto instance = Page::constructor.New({}); // undefined symbol pageconstructor
  //  instance.Set("_page", page); // I am just throwing stuff at it here.... hoping something will stick :( <- sad face
  //  auto rawPage = Page::Unwrap(instance);
  //  rawPage->SetPdfPage(page);
  return instance;
}

You can use a Napi::External to pass an arbitrary pointer as the first argument to the constructor. Something like this:

auto instance = Page::constructor.New({ Napi::External::New(info.Env(), page) });
return instance;

And then extract the pointer from the callback info's first argument in the constructor:

Page::Page(const Napi::CallbackInfo& info) {
  PoDoFo::PdfPage* page = info[0].As<Napi::External>().Data();

}

I am painfully aware that there is not enough documentation for Napi::ObjectWrap at this time.

@jasongin Thank you very much. Quick note for anyone else, External requires a template parameter: External<T>

Ahh, sorry I had forgotten we made the change to have that class templated on the pointer type. I'm surprised it compiled without the template parameter then.

So it's working now? Or are you still getting the fatal error?

@jasongin no, your correct, it needs the template parameter to compile, but yes it work!! Thank you. The error was just due to me forgetting to initialize it in the module.

OK, great. I'm just a little concerned about one thing. While I would expect an error if the constructor wasn't initialized, it shouldn't have been a fatal error. (It should have been thrown as a C++ exception, if you have exceptions enabled in the module.) That fatal error there means napi_get_last_error_info() failed _while trying to construct an Error instance_, which is very bad and should never happen.

@jasongin Well bummer... It seems to be reproducible by removing Initialize
``` cpp
void
init(Napi::Env env, Napi::Object exports, Napi::Object module)
{
Document::Initialize(env, exports);
Page::Initialize(env, exports); // removing this line causes that error when calling GetPage
...

// Document

Napi::Value
Document::GetPage(const CallbackInfo& info)
{
if (info.Length() != 1 || !info[0].IsNumber()) {
throw Napi::Error::New(info.Env(),
"getPage takes an argument of 1, of type number.");
}
int n = info[0].As();
PoDoFo::PdfPage* page = _document->GetPage(n);
auto exp = Napi::External::New(info.Env(), page);
auto instance = Page::constructor.New({ exp });
return instance;
}
``

Are you able to use a debugger to check the value of the status code at the point of the fatal error? Here's the line in napi-inl.h: https://github.com/nodejs/node-addon-api/blob/master/napi-inl.h#L1427

Hmm, it would have to be napi_invalid_arg, except the only way that could happen is if the env is null, and I don't know how that could be.

@jasongin So, I am not really sure how to debug this, this is my first attempt at a native node module, and my usual means of debugging are not cooperating. The code is still very small (~200LOC) which your more than welcome to look over here. I will continue to work on setting up a debugger ( will need one regardless, any suggestion?)

Is it possible to create an instance without going through the constructor at all? Just like in JS you can swap the prototype.

Hello everyone. I am trying to make a wrapper for cv::Mat to pass it across c++ and node. But when I try to create an instance of my ObjectWraped class called Image inside another wrapped class, I get "Error: Invalid argument". Here's my code (just 40 lines, please look at Image::New() method.)

Napi::FunctionReference Nodoface::Image::constructor;

Napi::Object Nodoface::Image::Init(Napi::Env env, Napi::Object exports) {
    Napi::HandleScope scope(env);
    Napi::Function func = DefineClass(env, "Image", {
        InstanceMethod("shape", &Nodoface::Image::Shape),
        InstanceMethod("type", &Nodoface::Image::Type)
    });
    constructor = Napi::Persistent(func);
    constructor.SuppressDestruct();
    exports.Set("Image", func);
    return exports;
}

Nodoface::Image::Image(const Napi::CallbackInfo& info) : ObjectWrap<Nodoface::Image>(info) {
    std::cout<<"inside Mat constructor\n";
    if(info.Length() == 1) {
        std::cout<<"Mat Unwrap start\n";
        Image * img = Napi::ObjectWrap<Image>::Unwrap(info[0].As<Napi::Object>());
        std::cout<<"Mat Unwrap end\n";
        this->mat = img->mat;
        this->shape = GetShape(info.Env(), this->mat);
    }
    std::cout<<"Mat constructor finish\n";
}

Nodoface::Image Nodoface::Image::New(const Napi::CallbackInfo& info, cv::Mat& mat) {
    auto env = info.Env();
    std::cout<<"inside Mat New\n";
    Nodoface::Image img(info);
    img.mat = mat;
    img.shape = GetShape(env, mat);
    return img;
}

Napi::Int32Array Nodoface::Image::GetShape(Napi::Env env, cv::Mat &mat) {
    std::vector<int32_t > vec(mat.size.p, mat.size.p + mat.size.dims());
    vec.push_back(mat.channels());
    auto ab = Napi::ArrayBuffer::New(env, vec.data(), sizeof(int32_t)/ sizeof(uint8_t));
    auto arr = Napi::Int32Array::New(env, vec.size(), ab, 0);
    return arr;
}

I am creating local instance from cv::Mat here

Napi::Value Nodoface::ImageCapture::GetNextImage(const Napi::CallbackInfo &info) {
    Napi::Env env = info.Env();
    cv::Mat mat = this->imageCapture->GetNextImage();
    auto img = Nodoface::Image::New(info, mat);
    return img.Value();
}

Then in node, I initialise ImageCapture. (Tested previously to work flawlessly without passing Mat)
And call imageCapture.getNextImage(). This triggers the error:

/home/sziraqui/Projects/nodoface/examples/image_capture_example.js:17
imageCapture.getNextImage();
             ^

Error: Invalid argument
    at Object.<anonymous> (/home/sziraqui/Projects/nodoface/examples/image_capture_example.js:17:14)
    at Module._compile (internal/modules/cjs/loader.js:701:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:712:10)
    at Module.load (internal/modules/cjs/loader.js:600:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:539:12)
    at Function.Module._load (internal/modules/cjs/loader.js:531:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:754:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:622:3)

@jasongin can you please help?

This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.

I think the discussion in this issue is quite old or has completed. I'm going to close and please open a new issue to discuss any remaining questions. Please let us know if you think that was not the right thing to do.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jfathman picture jfathman  路  3Comments

mhdawson picture mhdawson  路  4Comments

felipezacani picture felipezacani  路  8Comments

D-Andreev picture D-Andreev  路  6Comments

blagoev picture blagoev  路  6Comments