Как вернуть нативный объект из класса, производного от Nan::ObjectWrap?

У меня есть два простых класса A а также B что я пытаюсь выставить в родном модуле в node.js. A непосредственно создается, но B создается только путем вызова 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;
};

Я хочу иметь возможность написать:

const M = require('node_nan_minimal');
const a = new M.A();
const b = a.foo();

Для этого я создаю два класса-обёртки, полученных из Nan::ObjectWrap

class AWrapper : public Nan::ObjectWrap { ... }
class BWrapper : public Nan::ObjectWrap { ... }

Каждый содержит экземпляр A или же B соответственно. С их помощью я могу создать объект типа A из Javascript, но у меня возникли проблемы с реализацией 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?
}

Что я делаю, чтобы эта функция работала?


Полный код файла.cc

#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);

И полный репозиторий примера можно найти по адресу https://github.com/mikeando/node_nan_minimal который вы сможете клонировать, а затем собрать, используя npm install,

1 ответ

Один из способов сделать эту работу:

  • добавьте конструктор в BWrapper, но не предоставляйте эту функцию javascript. Заставьте эту функцию взять указатель на B. Это нужно будет сохранить в init_class функция.
  • добавьте функцию в BWrapper, чтобы создать новый экземпляр, используя этот конструктор. Это также примет указатель на B.
  • сделать foo функция вызова BWrapper::NewInstance,

Это составляет следующие дополнения

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;
  }
}

И изменения в AWrapper::foo являются:

   static NAN_METHOD(foo) {
     AWrapper* obj = Nan::ObjectWrap::Unwrap<AWrapper>(info.Holder());
     B b = obj->a_.foo();
     info.GetReturnValue().Set(BWrapper::NewInstance(&b));
   }

И, наконец, нам нужно убедиться, что класс зарегистрирован.

 NAN_MODULE_INIT(InitModule) {
   BWrapper::init_class();
   AWrapper::register_class(target);
 }

Я подозреваю, что это не самый чистый способ сделать это, и я хотел бы видеть любые альтернативы. Меня особенно интересует, есть ли у этого конструктора BWrapper какие-либо недостатки.

Частично это было взято из прочтения https://github.com/tracelytics/node-traceview-bindings/blob/master/src/metadata.cc#L18 предложения другого вопроса, а затем из моих собственных экспериментов.

Другие вопросы по тегам