Spec-Zone.ru › Node.js 6 LTS

C++ Плагины

Плагины Node.js — это динамически подключаемые общие объекты, написанные на C++, которые могут быть загружены в Node.js с помощью функции require(), и используются так же, как обычный модуль Node.js. Они используются главным образом для обеспечения интерфейса между JavaScript, выполняемым в Node.js, и библиотеками C/C++.

В настоящее время метод реализации плагинов довольно сложен, требуя знаний нескольких компонентов и API:

  • V8: библиотека C++, которую Node.js в настоящее время использует для обеспечения реализации JavaScript. V8 предоставляет механизмы для создания объектов, вызова функций и т. д. API V8 документирован в основном в заголовочном файле v8.h (deps/v8/include/v8.h в дереве исходного кода Node.js), который также доступен онлайн.

  • libuv: Библиотека C, которая реализует цикл событий Node.js, его потоки-работники и все асинхронные поведения платформы. Она также служит библиотекой абстракции кросс-платформ, обеспечивая лёгкий, похожий на POSIX доступ через все основные операционные системы к многим общим системным задачам, таким как взаимодействие с файловой системой, сокетами, таймерами и системными событиями. libuv также предоставляет абстракцию потоков, подобную pthreads, которая может быть использована для создания более сложных асинхронных плагинов, которым необходимо выйти за пределы стандартного цикла событий. Разработчикам плагинов рекомендуется подумать о том, как избежать блокировки цикла событий с помощью ввода-вывода или других задач с интенсивным использованием времени, переложив работу через libuv на неблокирующие системные операции, потоки-работники или пользовательское использование потоков libuv.

  • Внутренние библиотеки Node.js. Сам Node.js экспортирует ряд C++ API, которые могут использовать плагины — наиболее важным из которых является класс node::ObjectWrap.

  • Node.js включает ряд других статически связанных библиотек, включая OpenSSL. Эти другие библиотеки расположены в каталоге deps/ в дереве исходного кода Node.js. Только символы V8 и OpenSSL преднамеренно повторно экспортируются Node.js и могут быть использованы плагинами в разной степени. Дополнительную информацию см. в разделе Связывание с собственными зависимостями Node.js.

Все следующие примеры доступны для скачивания и могут быть использованы в качестве отправной точки для собственного плагина.

Привет мир

Этот пример «Привет мир» — это простой плагин, написанный на C++, который эквивалентен следующему коду JavaScript:

module.exports.hello = () => 'world';

Сначала создайте файл hello.cc:

// hello.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void Method(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "world"));
}

void init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "hello", Method);
}

NODE_MODULE(addon, init)

}  // namespace demo

Обратите внимание, что все плагины Node.js должны экспортировать функцию инициализации, следуя шаблону:

void Initialize(Local<Object> exports);
NODE_MODULE(module_name, Initialize)

После NODE_MODULE нет точки с запятой, так как это не функция (см. node.h).

module_name должно соответствовать имени конечного двоичного файла (без суффикса .node).

В примере hello.cc функция инициализации — init, а имя модуля плагина — addon.

Компиляция

После написания исходного кода его необходимо скомпилировать в двоичный файл addon.node. Для этого создайте файл binding.gyp в корне проекта, описывающий конфигурацию сборки модуля в формате, похожем на JSON. Этот файл используется инструментом node-gyp — инструментом, специально написанным для компиляции плагинов Node.js.

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "hello.cc" ]
    }
  ]
}

Примечание: версия утилиты node-gyp включена и распространяется с Node.js в составе npm. Эта версия не предоставляется разработчикам напрямую и предназначена только для поддержки возможности использования команды npm install для компиляции и установки плагинов. Разработчики, которые хотят использовать node-gyp напрямую, могут установить его с помощью команды npm install -g node-gyp. См. инструкции по установке для получения дополнительной информации, включая платформенно-специфичные требования.

После создания файла binding.gyp используйте node-gyp configure для генерации соответствующих файлов проекта сборки для текущей платформы. Это сгенерирует либо файл Makefile (на платформах Unix), либо файл vcxproj (на платформах Windows) в каталоге build/.

Далее, вызовите команду node-gyp build для генерации скомпилированного файла addon.node. Он будет помещён в каталог build/Release/.

При использовании npm install для установки плагина Node.js, npm использует свою встроенную версию node-gyp для выполнения этого набора действий, генерируя скомпилированную версию плагина для платформы пользователя по требованию.

После компиляции двоичный плагин может быть использован изнутри Node.js, указав require() на скомпилированный модуль addon.node:

// hello.js
const addon = require('./build/Release/addon');

console.log(addon.hello());
// Prints: 'world'

Дополнительную информацию см. в примерах ниже или на https://github.com/arturadib/node-qt для примера в производстве.

Поскольку точный путь к скомпилированному двоичному плагину может изменяться в зависимости от способа его компиляции (например, иногда он может находиться в ./build/Debug/), плагины могут использовать пакет bindings для загрузки скомпилированного модуля.

Обратите внимание, что хотя реализация пакета bindings более сложна в том, как она находит модули плагинов, она по сути использует шаблон try-catch, аналогичный:

try {
  return require('./build/Release/addon.node');
} catch (err) {
  return require('./build/Debug/addon.node');
}

Связывание с собственными зависимостями Node.js

Node.js использует ряд статически связанных библиотек, таких как V8, libuv и OpenSSL. Все плагины должны быть связаны с V8 и могут быть связаны с другими зависимостями. Обычно это делается простым включением соответствующих инструкций #include <...> (например, #include <v8.h>) и node-gyp автоматически найдёт соответствующие заголовки. Однако есть несколько нюансов:

  • При выполнении node-gyp он обнаружит конкретную версию Node.js и загрузит либо полную исходную tar-архив, либо только заголовки. Если загружается полный исходный код, плагины получат полный доступ к полному набору зависимостей Node.js. Однако, если загружаются только заголовки Node.js, то доступны только экспортированные Node.js символы.

  • node-gyp может быть запущен с флагом --nodedir, указывающим на локальный образ исходного кода Node.js. При использовании этого варианта плагин получит доступ к полному набору зависимостей.

Загрузка плагинов с помощью require()

Расширение имени файла скомпилированного двоичного плагина — .node (в отличие от .dll или .so). Функция require() написана так, чтобы искать файлы с расширением .node и инициализировать их как динамически подключаемые библиотеки.

При вызове require(), расширение .node обычно можно опустить, и Node.js всё равно найдёт и инициализирует плагин. Однако есть один нюанс: Node.js сначала попытается найти и загрузить модули или файлы JavaScript, которые имеют одинаковое базовое имя. Например, если в той же директории, что и двоичный файл addon.node, есть файл addon.js, то require('addon') отдаст предпочтение файлу addon.js и загрузит его вместо этого.

Нативные абстракции для Node.js

Каждый из примеров, представленных в этом документе, напрямую использует API Node.js и V8 для реализации плагинов. Важно понимать, что API V8 может и изменяется кардинально от одной версии V8 к другой (и одной версии Node.js к другой). При каждом изменении плагинам может потребоваться обновление и перекомпиляция для продолжения работы. График выпуска Node.js предназначен для минимизации частоты и воздействия таких изменений, но в настоящее время Node.js может сделать немного для обеспечения стабильности API V8.

Нативные абстракции для Node.js (или nan) предоставляют набор инструментов, использование которых рекомендуется разработчикам плагинов для поддержания совместимости между прошлыми и будущими версиями V8 и Node.js. См. примеры для иллюстрации того, как это можно использовать.

N-API

Устойчивость: 1 - Экспериментальная

N-API — это API для создания нативных плагинов. Он независим от базовой JavaScript среды выполнения (например, V8) и поддерживается в Node.js. Этот API будет стабильным в бинарном интерфейсе приложения (ABI) в различных версиях Node.js. Он предназначен для изоляции плагинов от изменений в базовом движке JavaScript и позволяет модулям, скомпилированным для одной версии, работать в более поздних версиях Node.js без перекомпиляции. Плагины создаются/упаковываются с использованием того же подхода/инструментов, что описано в этом документе (node-gyp и т. д.). Единственное отличие — это набор API, которые используется нативным кодом. Вместо использования API V8 или Нативных абстракций для Node.js, используются функции, доступные в N-API.

Функции, доступные и как их использовать, описаны в разделе, озаглавленном C/C++ плагины — N-API.

Примеры плагинов

Ниже приведены некоторые примеры плагинов, предназначенные для помощи разработчикам в начале работы. Примеры используют API V8. Обратитесь к онлайн справочнику V8 за помощью по различным вызовам V8 и Руководству для разработчика V8 для объяснения нескольких используемых понятий, таких как обработчики, области видимости, шаблоны функций и т. д.

Каждый из этих примеров использует следующий файл binding.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "addon.cc" ]
    }
  ]
}

В случаях, когда существует более одного файла .cc , просто добавьте дополнительное имя файла в массив sources. Например:

"sources": ["addon.cc", "myexample.cc"]

После того как файл binding.gyp готов, примеры плагинов можно настроить и скомпилировать с помощью node-gyp:

$ node-gyp configure build

Аргументы функций

Плагины обычно экспонируют объекты и функции, к которым можно получить доступ из JavaScript, выполняемого в Node.js. Когда функции вызываются из JavaScript, входные аргументы и возвращаемое значение должны быть сопоставлены с кодом C/C++ и обратно.

Следующий пример иллюстрирует, как читать аргументы функций, переданные из JavaScript, и как вернуть результат:

// addon.cc
#include <node.h>

namespace demo {

using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

// This is the implementation of the "add" method
// Input arguments are passed using the
// const FunctionCallbackInfo<Value>& args struct
void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  // Check the number of arguments passed.
  if (args.Length() < 2) {
    // Throw an Error that is passed back to JavaScript
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate, "Wrong number of arguments")));
    return;
  }

  // Check the argument types
  if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
    isolate->ThrowException(Exception::TypeError(
        String::NewFromUtf8(isolate, "Wrong arguments")));
    return;
  }

  // Perform the operation
  double value = args[0]->NumberValue() + args[1]->NumberValue();
  Local<Number> num = Number::New(isolate, value);

  // Set the return value (using the passed in
  // FunctionCallbackInfo<Value>&)
  args.GetReturnValue().Set(num);
}

void Init(Local<Object> exports) {
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(addon, Init)

}  // namespace demo

После компиляции пример плагина можно загрузить и использовать внутри Node.js:

// test.js
const addon = require('./build/Release/addon');

console.log('This should be eight:', addon.add(3, 5));

Обратные вызовы

В Addons принято передавать JavaScript-функции в C++-функцию и выполнять их оттуда. Следующий пример иллюстрирует, как вызывать такие обратные вызовы:

// addon.cc
#include <node.h>

namespace demo {

using v8::Function;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Null;
using v8::Object;
using v8::String;
using v8::Value;

void RunCallback(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  Local<Function> cb = Local<Function>::Cast(args[0]);
  const unsigned argc = 1;
  Local<Value> argv[argc] = { String::NewFromUtf8(isolate, "hello world") };
  cb->Call(Null(isolate), argc, argv);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", RunCallback);
}

NODE_MODULE(addon, Init)

}  // namespace demo

Обратите внимание, что в этом примере используется двухаргументная форма Init() , которая получает весь объект module в качестве второго аргумента. Это позволяет дополнению полностью перезаписать exports одной функцией вместо добавления функции как свойства exports.

Для тестирования выполните следующий JavaScript:

// test.js
const addon = require('./build/Release/addon');

addon((msg) => {
  console.log(msg);
// Prints: 'hello world'
});

Обратите внимание, что в этом примере функция обратного вызова вызывается синхронно.

Фабрика объектов

Addons могут создавать и возвращать новые объекты из C++-функции, как показано в следующем примере. Объект создаётся и возвращается со свойством msg , которое эхом повторяет строку, переданную в createObject():

// addon.cc
#include <node.h>

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  Local<Object> obj = Object::New(isolate);
  obj->Set(String::NewFromUtf8(isolate, "msg"), args[0]->ToString());

  args.GetReturnValue().Set(obj);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(addon, Init)

}  // namespace demo

Для тестирования на JavaScript:

// test.js
const addon = require('./build/Release/addon');

const obj1 = addon('hello');
const obj2 = addon('world');
console.log(obj1.msg, obj2.msg);
// Prints: 'hello world'

Фабрика функций

Другой распространённый сценарий — создание JavaScript-функций, которые оборачивают C++-функции и возвращают их обратно в JavaScript:

// addon.cc
#include <node.h>

namespace demo {

using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void MyFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();
  args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world"));
}

void CreateFunction(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, MyFunction);
  Local<Function> fn = tpl->GetFunction();

  // omit this to make it anonymous
  fn->SetName(String::NewFromUtf8(isolate, "theFunction"));

  args.GetReturnValue().Set(fn);
}

void Init(Local<Object> exports, Local<Object> module) {
  NODE_SET_METHOD(module, "exports", CreateFunction);
}

NODE_MODULE(addon, Init)

}  // namespace demo

Для тестирования:

// test.js
const addon = require('./build/Release/addon');

const fn = addon();
console.log(fn());
// Prints: 'hello world'

Оборачивание C++-объектов

Также возможно обернуть C++-объекты/классы таким образом, чтобы новые экземпляры можно было создавать с помощью JavaScript-оператора new:

// addon.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::Local;
using v8::Object;

void InitAll(Local<Object> exports) {
  MyObject::Init(exports);
}

NODE_MODULE(addon, InitAll)

}  // namespace demo

Затем, в myobject.h, класс-обёртка наследуется от node::ObjectWrap:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Local<v8::Object> exports);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  static v8::Persistent<v8::Function> constructor;
  double value_;
};

}  // namespace demo

#endif

В myobject.cc, реализуются различные методы, которые должны быть экспонированы. Ниже метод plusOne() экспонируется путём добавления его в прототип конструктора:

// myobject.cc
#include "myobject.h"

namespace demo {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::Persistent;
using v8::String;
using v8::Value;

Persistent<Function> MyObject::constructor;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Local<Object> exports) {
  Isolate* isolate = exports->GetIsolate();

  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  // Prototype
  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

  constructor.Reset(isolate, tpl->GetFunction());
  exports->Set(String::NewFromUtf8(isolate, "MyObject"),
               tpl->GetFunction());
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Context> context = isolate->GetCurrentContext();
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    Local<Object> result =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(result);
  }
}

void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value_ += 1;

  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
}

}  // namespace demo

Для построения этого примера файл myobject.cc должен быть добавлен в binding.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [
        "addon.cc",
        "myobject.cc"
      ]
    }
  ]
}

Протестируйте с:

// test.js
const addon = require('./build/Release/addon');

const obj = new addon.MyObject(10);
console.log(obj.plusOne());
// Prints: 11
console.log(obj.plusOne());
// Prints: 12
console.log(obj.plusOne());
// Prints: 13

Фабрика обернутых объектов

В качестве альтернативы, можно использовать паттерн фабрики, чтобы избежать явного создания экземпляров объектов с помощью JavaScript-оператора new:

const obj = addon.createObject();
// instead of:
// const obj = new addon.Object();

Сначала метод createObject() реализуется в addon.cc:

// addon.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  MyObject::NewInstance(args);
}

void InitAll(Local<Object> exports, Local<Object> module) {
  MyObject::Init(exports->GetIsolate());

  NODE_SET_METHOD(module, "exports", CreateObject);
}

NODE_MODULE(addon, InitAll)

}  // namespace demo

В myobject.h, статический метод NewInstance() добавляется для обработки создания объекта. Этот метод заменяет использование new в JavaScript:

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Isolate* isolate);
  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static void PlusOne(const v8::FunctionCallbackInfo<v8::Value>& args);
  static v8::Persistent<v8::Function> constructor;
  double value_;
};

}  // namespace demo

#endif

Реализация в myobject.cc аналогична предыдущему примеру:

// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::Persistent;
using v8::String;
using v8::Value;

Persistent<Function> MyObject::constructor;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Isolate* isolate) {
  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  // Prototype
  NODE_SET_PROTOTYPE_METHOD(tpl, "plusOne", PlusOne);

  constructor.Reset(isolate, tpl->GetFunction());
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    Local<Context> context = isolate->GetCurrentContext();
    Local<Object> instance =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(instance);
  }
}

void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  const unsigned argc = 1;
  Local<Value> argv[argc] = { args[0] };
  Local<Function> cons = Local<Function>::New(isolate, constructor);
  Local<Context> context = isolate->GetCurrentContext();
  Local<Object> instance =
      cons->NewInstance(context, argc, argv).ToLocalChecked();

  args.GetReturnValue().Set(instance);
}

void MyObject::PlusOne(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  MyObject* obj = ObjectWrap::Unwrap<MyObject>(args.Holder());
  obj->value_ += 1;

  args.GetReturnValue().Set(Number::New(isolate, obj->value_));
}

}  // namespace demo

Ещё раз, для построения этого примера файл myobject.cc должен быть добавлен в binding.gyp:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [
        "addon.cc",
        "myobject.cc"
      ]
    }
  ]
}

Протестируйте с:

// test.js
const createObject = require('./build/Release/addon');

const obj = createObject(10);
console.log(obj.plusOne());
// Prints: 11
console.log(obj.plusOne());
// Prints: 12
console.log(obj.plusOne());
// Prints: 13

const obj2 = createObject(20);
console.log(obj2.plusOne());
// Prints: 21
console.log(obj2.plusOne());
// Prints: 22
console.log(obj2.plusOne());
// Prints: 23

Передача обернутых объектов

Помимо обертывания и возврата C++-объектов, можно передавать обернутые объекты, распаковывая их с помощью вспомогательной функции Node.js node::ObjectWrap::Unwrap. Следующий пример показывает функцию add() , которая может принимать два объекта MyObject в качестве входных аргументов:

// addon.cc
#include <node.h>
#include <node_object_wrap.h>
#include "myobject.h"

namespace demo {

using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;

void CreateObject(const FunctionCallbackInfo<Value>& args) {
  MyObject::NewInstance(args);
}

void Add(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  MyObject* obj1 = node::ObjectWrap::Unwrap<MyObject>(
      args[0]->ToObject());
  MyObject* obj2 = node::ObjectWrap::Unwrap<MyObject>(
      args[1]->ToObject());

  double sum = obj1->value() + obj2->value();
  args.GetReturnValue().Set(Number::New(isolate, sum));
}

void InitAll(Local<Object> exports) {
  MyObject::Init(exports->GetIsolate());

  NODE_SET_METHOD(exports, "createObject", CreateObject);
  NODE_SET_METHOD(exports, "add", Add);
}

NODE_MODULE(addon, InitAll)

}  // namespace demo

В myobject.h, добавляется новый публичный метод для доступа к закрытым значениям после распаковки объекта.

// myobject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <node.h>
#include <node_object_wrap.h>

namespace demo {

class MyObject : public node::ObjectWrap {
 public:
  static void Init(v8::Isolate* isolate);
  static void NewInstance(const v8::FunctionCallbackInfo<v8::Value>& args);
  inline double value() const { return value_; }

 private:
  explicit MyObject(double value = 0);
  ~MyObject();

  static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
  static v8::Persistent<v8::Function> constructor;
  double value_;
};

}  // namespace demo

#endif

Реализация myobject.cc аналогична предыдущей:

// myobject.cc
#include <node.h>
#include "myobject.h"

namespace demo {

using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::Persistent;
using v8::String;
using v8::Value;

Persistent<Function> MyObject::constructor;

MyObject::MyObject(double value) : value_(value) {
}

MyObject::~MyObject() {
}

void MyObject::Init(Isolate* isolate) {
  // Prepare constructor template
  Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
  tpl->SetClassName(String::NewFromUtf8(isolate, "MyObject"));
  tpl->InstanceTemplate()->SetInternalFieldCount(1);

  constructor.Reset(isolate, tpl->GetFunction());
}

void MyObject::New(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  if (args.IsConstructCall()) {
    // Invoked as constructor: `new MyObject(...)`
    double value = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
    MyObject* obj = new MyObject(value);
    obj->Wrap(args.This());
    args.GetReturnValue().Set(args.This());
  } else {
    // Invoked as plain function `MyObject(...)`, turn into construct call.
    const int argc = 1;
    Local<Value> argv[argc] = { args[0] };
    Local<Context> context = isolate->GetCurrentContext();
    Local<Function> cons = Local<Function>::New(isolate, constructor);
    Local<Object> instance =
        cons->NewInstance(context, argc, argv).ToLocalChecked();
    args.GetReturnValue().Set(instance);
  }
}

void MyObject::NewInstance(const FunctionCallbackInfo<Value>& args) {
  Isolate* isolate = args.GetIsolate();

  const unsigned argc = 1;
  Local<Value> argv[argc] = { args[0] };
  Local<Function> cons = Local<Function>::New(isolate, constructor);
  Local<Context> context = isolate->GetCurrentContext();
  Local<Object> instance =
      cons->NewInstance(context, argc, argv).ToLocalChecked();

  args.GetReturnValue().Set(instance);
}

}  // namespace demo

Протестируйте с:

// test.js
const addon = require('./build/Release/addon');

const obj1 = addon.createObject(10);
const obj2 = addon.createObject(20);
const result = addon.add(obj1, obj2);

console.log(result);
// Prints: 30

Обработчики AtExit

Обработчик «AtExit» — это функция, которая вызывается после завершения цикла событий Node.js, но перед завершением JavaScript-виртуальной машины и выключением Node.js. Обработчики «AtExit» регистрируются с помощью API node::AtExit.

void AtExit(callback, args)

  • callback <void (*)(void*)> Указатель на функцию, которая должна быть вызвана при выходе.
  • args <void*> Указатель для передачи обратнму вызову при выходе.

Регистрирует обработчики выхода, которые выполняются после завершения цикла событий, но перед уничтожением виртуальной машины.

AtExit принимает два параметра: указатель на функцию обратного вызова, которая должна быть выполнена при выходе, и указатель на данные контекста произвольного типа, которые должны быть переданы этому обратнму вызову.

Обратные вызовы выполняются в порядке LIFO.

Следующий addon.cc реализует AtExit:

// addon.cc
#include <assert.h>
#include <stdlib.h>
#include <node.h>

namespace demo {

using node::AtExit;
using v8::HandleScope;
using v8::Isolate;
using v8::Local;
using v8::Object;

static char cookie[] = "yum yum";
static int at_exit_cb1_called = 0;
static int at_exit_cb2_called = 0;

static void at_exit_cb1(void* arg) {
  Isolate* isolate = static_cast<Isolate*>(arg);
  HandleScope scope(isolate);
  Local<Object> obj = Object::New(isolate);
  assert(!obj.IsEmpty()); // assert VM is still alive
  assert(obj->IsObject());
  at_exit_cb1_called++;
}

static void at_exit_cb2(void* arg) {
  assert(arg == static_cast<void*>(cookie));
  at_exit_cb2_called++;
}

static void sanity_check(void*) {
  assert(at_exit_cb1_called == 1);
  assert(at_exit_cb2_called == 2);
}

void init(Local<Object> exports) {
  AtExit(at_exit_cb2, cookie);
  AtExit(at_exit_cb2, cookie);
  AtExit(at_exit_cb1, exports->GetIsolate());
  AtExit(sanity_check);
}

NODE_MODULE(addon, init)

}  // namespace demo

Тестируйте в JavaScript, выполнив:

// test.js
require('./build/Release/addon');

© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v6.x/docs/api/addons.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API