Spec-Zone.ru › Node.js 8 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. Только символы libuv, OpenSSL, V8 и zlib преднамеренно повторно экспортируются 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(NODE_GYP_MODULE_NAME, init)

}  // namespace demo

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

void Initialize(Local<Object> exports);
NODE_MODULE(NODE_GYP_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());
// 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 и загрузит либо полный архив исходных кодов, либо только заголовки. Если загружается полный архив исходных кодов, плагины получат полный доступ ко всем зависимостям 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 примерами для иллюстрации того, как это можно использовать.

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.

Чтобы использовать N-API в вышеприведённом примере «Привет, мир», замените содержимое hello.cc следующим. Все остальные инструкции остаются неизменными.

// hello.cc using N-API
#include <node_api.h>

namespace demo {

napi_value Method(napi_env env, napi_callback_info args) {
  napi_value greeting;
  napi_status status;

  status = napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &greeting);
  if (status != napi_ok) return nullptr;
  return greeting;
}

napi_value init(napi_env env, napi_value exports) {
  napi_status status;
  napi_value fn;

  status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
  if (status != napi_ok) return nullptr;

  status = napi_set_named_property(env, exports, "hello", fn);
  if (status != napi_ok) return nullptr;
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, init)

}  // namespace demo

Доступные функции и способы их использования описаны в разделе 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(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

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

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

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

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

В плагинах Addon принято передавать 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(NODE_GYP_MODULE_NAME, Init)

}  // namespace demo

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

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

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

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

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

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

Плагины Addon могут создавать и возвращать новые объекты изнутри 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_MODULE_NAME, 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(NODE_GYP_MODULE_NAME, 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-v8.x/docs/api/addons.html

Spec-Zone.ru

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