Spec-Zone.ru › Node.js 4 LTS

Плагины C/C++

Плагины Node.js — это динамически подключаемые общие объекты, написанные на C или 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 экспортирует ряд API C/C++, которые могут использовать плагины — наиболее важным из которых является класс 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. Более подробная информация, включая платформенно-зависимые требования, представлена в руководстве по установке 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()); // '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 и загрузит либо полный архив исходного кода, либо только заголовочные файлы. Если загружается полный архив исходного кода, плагины получат полный доступ ко всем зависимостям 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. См. примеры в nan примеров для иллюстрации того, как их использовать.

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

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

Каждый из этих примеров использует следующий файл 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));

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

В плагинах часто практикуется передача функций 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); // 'hello world'
});

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

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

Плагины могут создавать и возвращать новые объекты из внутри функции 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');

var obj1 = addon('hello');
var obj2 = addon('world');
console.log(obj1.msg, obj2.msg); // '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');

var fn = addon();
console.log(fn()); // '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');

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

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

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

var obj = addon.createObject();
// instead of:
// var 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');

var obj = createObject(10);
console.log(obj.plusOne()); // 11
console.log(obj.plusOne()); // 12
console.log(obj.plusOne()); // 13

var obj2 = createObject(20);
console.log(obj2.plusOne()); // 21
console.log(obj2.plusOne()); // 22
console.log(obj2.plusOne()); // 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');

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

console.log(result); // 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
#undef NDEBUG
#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(sanity_check);
  AtExit(at_exit_cb2, cookie);
  AtExit(at_exit_cb2, cookie);
  AtExit(at_exit_cb1, exports->GetIsolate());
}

NODE_MODULE(addon, init);

}  // namespace demo

Проверить в JavaScript, выполнив:

// test.js
const addon = 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-v4.x/docs/api/addons.html

Spec-Zone.ru

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