Аддоны — это динамически компонуемые общие объекты, которые можно загружать через функцию require() как обычные модули Node.js. Аддоны предоставляют интерфейс вызова внешних функций между JavaScript и нативным кодом.
прямое использование внутренних библиотек V8, libuv и Node.js
Остальная часть этого документа посвящена последнему варианту, который требует знания нескольких компонентов и API:
V8: библиотека C++, которую Node.js использует для реализации JavaScript. Она предоставляет механизмы для создания объектов, вызова функций и т.д. API V8 в основном документирован в заголовочном файле v8.h (deps/v8/include/v8.h в дереве исходников Node.js), а также доступен онлайн.
libuv: библиотека на языке C, которая реализует цикл событий Node.js, его рабочие потоки и все асинхронное поведение платформы. Она также служит кроссплатформенной библиотекой абстракций, предоставляя простой POSIX-подобный доступ к множеству стандартных системных задач во всех основных операционных системах, включая работу с файловой системой, сокетами, таймерами и системными событиями. libuv также предоставляет абстракцию потоков, похожую на POSIX threads, для более сложных асинхронных аддонов, которым уже недостаточно стандартного цикла событий. Авторам аддонов следует избегать блокировки цикла событий операциями ввода-вывода и другими длительными задачами, передавая работу через libuv неблокирующим системным операциям, рабочим потокам или пользовательскому использованию потоков libuv.
Внутренние библиотеки Node.js: сам Node.js экспортирует C++ API, которые могут использовать аддоны; важнейший из них - класс node::ObjectWrap.
Другие статически скомпонованные библиотеки (включая OpenSSL): эти библиотеки расположены в каталоге deps/ дерева исходников Node.js. Node.js намеренно реэкспортирует только символы libuv, OpenSSL, V8 и zlib, и именно ими аддоны могут пользоваться в той или иной степени. Дополнительную информацию см. в разделе Связывание с библиотеками, включенными в Node.js.
Все приведенные ниже примеры доступны для загрузки и могут использоваться как отправная точка для написания аддона.
// hello.cc#include<node.h>namespacedemo{usingv8::FunctionCallbackInfo;usingv8::Isolate;usingv8::Local;usingv8::NewStringType;usingv8::Object;usingv8::String;usingv8::Value;voidMethod(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();args.GetReturnValue().Set(String::NewFromUtf8(isolate,"world",NewStringType::kNormal).ToLocalChecked());}voidInitialize(Local<Object>exports){NODE_SET_METHOD(exports,"hello",Method);}// Примечание: без точки с запятой, это не функцияNODE_MODULE(NODE_GYP_MODULE_NAME,Initialize)}// namespace demo
На большинстве платформ можно начать со следующего Makefile:
Аддоны, определенные с помощью NODE_MODULE(), нельзя одновременно загружать в нескольких контекстах или нескольких потоках.
Существуют среды, в которых аддоны Node.js могут понадобиться к загрузке несколько раз и в разных контекстах. Например, среда выполнения Electron запускает несколько экземпляров Node.js в одном процессе. У каждого экземпляра будет собственный кэш require(), поэтому каждый экземпляр должен корректно работать с нативным аддоном, загруженным через require(). Это означает, что аддон должен поддерживать многократную инициализацию.
Контекстно-зависимый аддон можно построить с помощью макроса NODE_MODULE_INITIALIZER, который разворачивается в имя функции, которую Node.js ожидает найти при загрузке аддона. Инициализация аддона может выглядеть так:
12345678
usingnamespacev8;extern"C"NODE_MODULE_EXPORTvoidNODE_MODULE_INITIALIZER(Local<Object>exports,Local<Value>module,Local<Context>context){/* Здесь выполняются шаги инициализации аддона. */}
Другой вариант - использовать макрос NODE_MODULE_INIT(), который тоже создает контекстно-зависимый аддон. В отличие от NODE_MODULE(), применяемого для построения аддона вокруг конкретной функции инициализации, NODE_MODULE_INIT() сам служит объявлением такого инициализатора, за которым сразу следует тело функции.
Внутри тела функции после вызова NODE_MODULE_INIT() можно использовать следующие три переменные:
Local<Object> exports,
Local<Value> module, и
Local<Context> context
При создании контекстно-зависимого аддона необходимо внимательно управлять глобальными статическими данными, чтобы обеспечить стабильность и корректность. Поскольку аддон может загружаться несколько раз, потенциально даже из разных потоков, любые глобальные статические данные, хранящиеся в аддоне, должны быть надежно защищены и не должны содержать постоянных ссылок на объекты JavaScript. Причина в том, что объекты JavaScript действительны только в одном контексте и, скорее всего, приведут к сбою при обращении к ним из неправильного контекста или из потока, отличного от того, в котором они были созданы.
Чтобы избежать использования глобальных статических данных, контекстно-зависимый аддон можно организовать следующим образом:
Определить класс, который будет хранить данные конкретного экземпляра аддона и иметь статический член следующего вида:
123
staticvoidDeleteInstance(void*data){// Приведите `data` к экземпляру класса и удалите его.}
Выделить экземпляр этого класса в куче в инициализаторе аддона. Это можно сделать с помощью ключевого слова new.
Вызвать node::AddEnvironmentCleanupHook(), передав созданный выше экземпляр и указатель на DeleteInstance(). Это гарантирует, что экземпляр будет удален при завершении среды.
Сохранить экземпляр класса в v8::External, и
Передать v8::External всем методам, экспортируемым в JavaScript, передав его в v8::FunctionTemplate::New() или v8::Function::New(), которые создают функции JavaScript, поддерживаемые нативным кодом. Третий параметр v8::FunctionTemplate::New() или v8::Function::New() принимает v8::External и делает его доступным в нативном обратном вызове через метод v8::FunctionCallbackInfo::Data().
Это гарантирует, что данные экземпляра аддона попадут в каждую привязку, которую можно вызвать из JavaScript. Эти же данные экземпляра необходимо также передавать в любые асинхронные обратные вызовы, которые может создавать аддон.
Следующий пример иллюстрирует реализацию контекстно-зависимого аддона:
#include<node.h>usingnamespacev8;classAddonData{public:explicitAddonData(Isolate*isolate):call_count(0){// Гарантируем удаление этих данных экземпляра аддона// при очистке среды.node::AddEnvironmentCleanupHook(isolate,DeleteInstance,this);}// Данные конкретного экземпляра аддона.intcall_count;staticvoidDeleteInstance(void*data){deletestatic_cast<AddonData*>(data);}};staticvoidMethod(constv8::FunctionCallbackInfo<v8::Value>&info){// Получаем данные конкретного экземпляра аддона.AddonData*data=reinterpret_cast<AddonData*>(info.Data().As<External>()->Value());data->call_count++;info.GetReturnValue().Set((double)data->call_count);}// Инициализируем этот аддон как контекстно-зависимый.NODE_MODULE_INIT(/* exports, module, context */){Isolate*isolate=Isolate::GetCurrent();// Создаем новый экземпляр `AddonData` для этого экземпляра аддона и// привязываем его жизненный цикл к жизненному циклу среды Node.js.AddonData*data=newAddonData(isolate);// Оборачиваем данные в `v8::External`, чтобы передать их методу,// который мы экспортируем.Local<External>external=External::New(isolate,data);// Экспортируем метод `Method` в JavaScript и гарантируем,// что он получит данные конкретного экземпляра аддона,// созданные выше, передавая `external`// как третий параметр в конструктор `FunctionTemplate`.exports->Set(context,String::NewFromUtf8(isolate,"method").ToLocalChecked(),FunctionTemplate::New(isolate,Method,external)->GetFunction(context).ToLocalChecked()).FromJust();}
либо быть объявлен как контекстно-зависимый с помощью NODE_MODULE_INIT(), как описано выше.
Чтобы поддерживать потоки Worker, аддоны должны освобождать все ресурсы, которые они могли выделить, когда такой поток завершается. Этого можно добиться с помощью функции AddEnvironmentCleanupHook():
Эта функция добавляет перехватчик очистки, который выполнится перед завершением конкретного экземпляра Node.js. При необходимости такие перехватчики можно удалить до их выполнения с помощью RemoveEnvironmentCleanupHook(), имеющей ту же сигнатуру. Обратные вызовы выполняются в порядке LIFO.
При необходимости существует дополнительная пара перегрузок AddEnvironmentCleanupHook() и RemoveEnvironmentCleanupHook(), где перехватчик очистки принимает функцию обратного вызова. Это можно использовать для корректного завершения асинхронных ресурсов, например любых дескрипторов libuv, зарегистрированных аддоном.
Следующий addon.cc использует AddEnvironmentCleanupHook:
// addon.cc#include<node.h>#include<assert.h>#include<stdlib.h>usingnode::AddEnvironmentCleanupHook;usingv8::HandleScope;usingv8::Isolate;usingv8::Local;usingv8::Object;// Примечание: в реальном приложении не полагайтесь// на статические/глобальные данные.staticcharcookie[]="yum yum";staticintcleanup_cb1_called=0;staticintcleanup_cb2_called=0;staticvoidcleanup_cb1(void*arg){Isolate*isolate=static_cast<Isolate*>(arg);HandleScopescope(isolate);Local<Object>obj=Object::New(isolate);assert(!obj.IsEmpty());// проверяем, что VM все еще живаassert(obj->IsObject());cleanup_cb1_called++;}staticvoidcleanup_cb2(void*arg){assert(arg==static_cast<void*>(cookie));cleanup_cb2_called++;}staticvoidsanity_check(void*){assert(cleanup_cb1_called==1);assert(cleanup_cb2_called==1);}// Инициализируем этот аддон как контекстно-зависимый.NODE_MODULE_INIT(/* exports, module, context */){Isolate*isolate=Isolate::GetCurrent();AddEnvironmentCleanupHook(isolate,sanity_check,nullptr);AddEnvironmentCleanupHook(isolate,cleanup_cb2,cookie);AddEnvironmentCleanupHook(isolate,cleanup_cb1,isolate);}
После написания исходного кода его необходимо скомпилировать в бинарный файл addon.node. Для этого создайте в корне проекта файл binding.gyp, описывающий конфигурацию сборки модуля в формате, похожем на JSON. Этот файл используется инструментом node-gyp, специально созданным для компиляции аддонов Node.js.
Версия утилиты node-gyp поставляется вместе с Node.js как часть npm. Эта версия не предназначена для прямого использования разработчиками и нужна только для поддержки команды npm install, которая компилирует и устанавливает аддоны. Разработчики, которые хотят использовать node-gyp напрямую, могут установить его командой npm install -g node-gyp. Дополнительную информацию, включая требования для разных платформ, см. в инструкции по установкеnode-gyp.
После создания файла binding.gyp используйте команду node-gyp configure, чтобы сгенерировать соответствующие файлы сборки для текущей платформы. В каталоге build/ будет создан либо Makefile (на Unix-платформах), либо файл vcxproj (в Windows).
Затем выполните команду node-gyp build, чтобы получить скомпилированный файл addon.node. Он будет помещен в каталог build/Release/.
Когда аддон Node.js устанавливается через npm install, npm использует собственную встроенную версию node-gyp для выполнения тех же действий и по требованию собирает скомпилированную версию аддона под платформу пользователя.
После сборки бинарный аддон можно использовать в Node.js, указав require() на собранный модуль addon.node:
Поскольку точный путь к скомпилированному бинарному файлу аддона может отличаться в зависимости от способа сборки (например, иногда это ./build/Debug/), аддоны могут использовать пакет bindings для загрузки скомпилированного модуля.
Хотя реализация пакета bindings значительно сложнее в части поиска модулей аддонов, по сути она использует шаблон try...catch, похожий на следующий:
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. В этом случае аддон получит доступ ко всему набору зависимостей.
Расширение имени файла скомпилированного бинарного аддона - .node (в отличие от .dll или .so). Функция require() умеет искать файлы с расширением .node и инициализировать их как динамически компонуемые библиотеки.
При вызове require() расширение .node обычно можно опустить, и Node.js все равно найдет и инициализирует аддон. Однако есть одно важное замечание: сначала Node.js попытается найти и загрузить модули или файлы JavaScript с тем же базовым именем. Например, если в том же каталоге, что и бинарный файл addon.node, есть файл addon.js, то require('addon') отдаст приоритет файлу addon.js и загрузит именно его.
Вы можете использовать флаг --experimental-addon-modules, чтобы включить поддержку как статического import, так и динамического import() для загрузки бинарных аддонов.
Если взять пример Hello World выше, можно сделать так:
12345
// hello.mjsimportmyAddonfrom'./hello.node';// Примечание: import {hello} from './hello.node' не сработаетconsole.log(myAddon.hello());
Каждый из примеров в этом документе напрямую использует API Node.js и V8 для реализации аддонов. API V8 может значительно меняться от одной версии V8 к другой (и от одного мажорного релиза Node.js к следующему). При каждом таком изменении аддоны может потребоваться обновлять и пересобирать, чтобы они продолжали работать. График релизов Node.js спроектирован так, чтобы минимизировать частоту и влияние подобных изменений, но обеспечить стабильность API V8 сам Node.js практически не может.
Native Abstractions for Node.js (или nan) предоставляют набор инструментов, которые разработчикам аддонов рекомендуется использовать для сохранения совместимости между прошлыми и будущими релизами V8 и Node.js. Пример использования см. в примерах для nan.
Ниже приведены несколько примеров аддонов, призванных помочь разработчикам начать работу. Эти примеры используют API V8. Для справки по различным вызовам V8 обращайтесь к справочнику V8, а к руководству для встраивающих систем V8 — за объяснением таких понятий, как дескрипторы, области видимости, шаблоны функций и т.д.
Во всех этих примерах используется следующий файл binding.gyp:
Обычно аддоны экспортируют объекты и функции, доступные из JavaScript, выполняющегося внутри Node.js. Когда функции вызываются из JavaScript, входные аргументы и возвращаемое значение необходимо преобразовывать в код C/C++ и обратно.
Следующий пример показывает, как читать аргументы функции, переданные из JavaScript, и как возвращать результат:
// addon.cc#include<node.h>namespacedemo{usingv8::Exception;usingv8::FunctionCallbackInfo;usingv8::Isolate;usingv8::Local;usingv8::Number;usingv8::Object;usingv8::String;usingv8::Value;// Это реализация метода "add"// Входные аргументы передаются через// структуру const FunctionCallbackInfo<Value>& argsvoidAdd(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();// Проверяем количество переданных аргументов.if(args.Length()<2){// Выбрасываем Error, который будет передан обратно в JavaScriptisolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate,"Wrong number of arguments").ToLocalChecked()));return;}// Проверяем типы аргументовif(!args[0]->IsNumber()||!args[1]->IsNumber()){isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate,"Wrong arguments").ToLocalChecked()));return;}// Выполняем операциюdoublevalue=args[0].As<Number>()->Value()+args[1].As<Number>()->Value();Local<Number>num=Number::New(isolate,value);// Устанавливаем возвращаемое значение (через переданный// FunctionCallbackInfo<Value>&)args.GetReturnValue().Set(num);}voidInit(Local<Object>exports){NODE_SET_METHOD(exports,"add",Add);}NODE_MODULE(NODE_GYP_MODULE_NAME,Init)}// namespace demo
После компиляции пример аддона можно подключить и использовать из Node.js:
1234
// test.jsconstaddon=require('./build/Release/addon');console.log('Здесь должно быть восемь:',addon.add(3,5));
В аддонах распространена практика передачи JavaScript-функций в функцию C++ и их вызова оттуда. Следующий пример показывает, как вызывать такие обратные вызовы:
В этом примере используется двухаргументная форма Init(), получающая полный объект module в качестве второго аргумента. Это позволяет аддону полностью перезаписать exports одной функцией вместо добавления функции как свойства exports.
Аддоны могут создавать и возвращать новые объекты прямо из функции C++, как показано в следующем примере. Создается и возвращается объект со свойством msg, которое повторяет строку, переданную в createObject():
// addon.cc#include<node.h>namespacedemo{usingv8::Context;usingv8::Function;usingv8::FunctionCallbackInfo;usingv8::FunctionTemplate;usingv8::Isolate;usingv8::Local;usingv8::Object;usingv8::String;usingv8::Value;voidMyFunction(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();args.GetReturnValue().Set(String::NewFromUtf8(isolate,"hello world").ToLocalChecked());}voidCreateFunction(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();Local<Context>context=isolate->GetCurrentContext();Local<FunctionTemplate>tpl=FunctionTemplate::New(isolate,MyFunction);Local<Function>fn=tpl->GetFunction(context).ToLocalChecked();// Уберите это, чтобы сделать функцию анонимнойfn->SetName(String::NewFromUtf8(isolate,"theFunction").ToLocalChecked());args.GetReturnValue().Set(fn);}voidInit(Local<Object>exports,Local<Object>module){NODE_SET_METHOD(module,"exports",CreateFunction);}NODE_MODULE(NODE_GYP_MODULE_NAME,Init)}// namespace demo
В myobject.cc реализуйте методы, которые должны быть экспортированы. В следующем коде метод plusOne() экспортируется путем добавления его в прототип конструктора:
// myobject.cc#include"myobject.h"namespacedemo{usingv8::Context;usingv8::Function;usingv8::FunctionCallbackInfo;usingv8::FunctionTemplate;usingv8::Isolate;usingv8::Local;usingv8::Number;usingv8::Object;usingv8::ObjectTemplate;usingv8::String;usingv8::Value;MyObject::MyObject(doublevalue):value_(value){}MyObject::~MyObject(){}voidMyObject::Init(Local<Object>exports){Isolate*isolate=Isolate::GetCurrent();Local<Context>context=isolate->GetCurrentContext();Local<ObjectTemplate>addon_data_tpl=ObjectTemplate::New(isolate);addon_data_tpl->SetInternalFieldCount(1);// 1 поле для MyObject::New()Local<Object>addon_data=addon_data_tpl->NewInstance(context).ToLocalChecked();// Подготавливаем шаблон конструктораLocal<FunctionTemplate>tpl=FunctionTemplate::New(isolate,New,addon_data);tpl->SetClassName(String::NewFromUtf8(isolate,"MyObject").ToLocalChecked());tpl->InstanceTemplate()->SetInternalFieldCount(1);// ПрототипNODE_SET_PROTOTYPE_METHOD(tpl,"plusOne",PlusOne);Local<Function>constructor=tpl->GetFunction(context).ToLocalChecked();addon_data->SetInternalField(0,constructor);exports->Set(context,String::NewFromUtf8(isolate,"MyObject").ToLocalChecked(),constructor).FromJust();}voidMyObject::New(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();Local<Context>context=isolate->GetCurrentContext();if(args.IsConstructCall()){// Вызывается как конструктор: `new MyObject(...)`doublevalue=args[0]->IsUndefined()?0:args[0]->NumberValue(context).FromMaybe(0);MyObject*obj=newMyObject(value);obj->Wrap(args.This());args.GetReturnValue().Set(args.This());}else{// Вызывается как обычная функция `MyObject(...)`, превращаем в вызов конструктора.constintargc=1;Local<Value>argv[argc]={args[0]};Local<Function>cons=args.Data().As<Object>()->GetInternalField(0).As<Value>().As<Function>();Local<Object>result=cons->NewInstance(context,argc,argv).ToLocalChecked();args.GetReturnValue().Set(result);}}voidMyObject::PlusOne(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();MyObject*obj=ObjectWrap::Unwrap<MyObject>(args.This());obj->value_+=1;args.GetReturnValue().Set(Number::New(isolate,obj->value_));}}// namespace demo
Чтобы собрать этот пример, файл myobject.cc необходимо добавить в binding.gyp:
Деструктор объекта-обертки будет вызван, когда объект будет собран сборщиком мусора. Для тестирования деструкторов существуют флаги командной строки, позволяющие принудительно запускать сборку мусора. Эти флаги предоставляются базовым движком JavaScript V8. Они могут измениться или быть удалены в любой момент. Node.js и V8 их не документируют, и использовать их вне тестирования никогда не следует.
При завершении процесса или потоков Worker деструкторы движком JS не вызываются. Поэтому ответственность за отслеживание этих объектов и их корректное уничтожение во избежание утечек ресурсов лежит на пользователе.
// myobject.cc#include<node.h>#include"myobject.h"namespacedemo{usingnode::AddEnvironmentCleanupHook;usingv8::Context;usingv8::Function;usingv8::FunctionCallbackInfo;usingv8::FunctionTemplate;usingv8::Global;usingv8::Isolate;usingv8::Local;usingv8::Number;usingv8::Object;usingv8::String;usingv8::Value;// Внимание! Это не потокобезопасно, такой аддон нельзя использовать// в потоках worker.Global<Function>MyObject::constructor;MyObject::MyObject(doublevalue):value_(value){}MyObject::~MyObject(){}voidMyObject::Init(){Isolate*isolate=Isolate::GetCurrent();// Подготавливаем шаблон конструктораLocal<FunctionTemplate>tpl=FunctionTemplate::New(isolate,New);tpl->SetClassName(String::NewFromUtf8(isolate,"MyObject").ToLocalChecked());tpl->InstanceTemplate()->SetInternalFieldCount(1);// ПрототипNODE_SET_PROTOTYPE_METHOD(tpl,"plusOne",PlusOne);Local<Context>context=isolate->GetCurrentContext();constructor.Reset(isolate,tpl->GetFunction(context).ToLocalChecked());AddEnvironmentCleanupHook(isolate,[](void*){constructor.Reset();},nullptr);}voidMyObject::New(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();Local<Context>context=isolate->GetCurrentContext();if(args.IsConstructCall()){// Вызывается как конструктор: `new MyObject(...)`doublevalue=args[0]->IsUndefined()?0:args[0]->NumberValue(context).FromMaybe(0);MyObject*obj=newMyObject(value);obj->Wrap(args.This());args.GetReturnValue().Set(args.This());}else{// Вызывается как обычная функция `MyObject(...)`, превращаем в вызов конструктора.constintargc=1;Local<Value>argv[argc]={args[0]};Local<Function>cons=Local<Function>::New(isolate,constructor);Local<Object>instance=cons->NewInstance(context,argc,argv).ToLocalChecked();args.GetReturnValue().Set(instance);}}voidMyObject::NewInstance(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();constunsignedargc=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);}voidMyObject::PlusOne(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();MyObject*obj=ObjectWrap::Unwrap<MyObject>(args.This());obj->value_+=1;args.GetReturnValue().Set(Number::New(isolate,obj->value_));}}// namespace demo
И снова, чтобы собрать этот пример, файл myobject.cc необходимо добавить в binding.gyp:
Помимо оборачивания и возврата объектов C++, можно передавать обернутые объекты дальше, разворачивая их с помощью вспомогательной функции Node.js node::ObjectWrap::Unwrap. Следующий пример показывает функцию add(), которая может принимать два объекта MyObject как входные аргументы:
// myobject.cc#include<node.h>#include"myobject.h"namespacedemo{usingnode::AddEnvironmentCleanupHook;usingv8::Context;usingv8::Function;usingv8::FunctionCallbackInfo;usingv8::FunctionTemplate;usingv8::Global;usingv8::Isolate;usingv8::Local;usingv8::Object;usingv8::String;usingv8::Value;// Внимание! Это не потокобезопасно, такой аддон нельзя использовать// в потоках worker.Global<Function>MyObject::constructor;MyObject::MyObject(doublevalue):value_(value){}MyObject::~MyObject(){}voidMyObject::Init(){Isolate*isolate=Isolate::GetCurrent();// Подготавливаем шаблон конструктораLocal<FunctionTemplate>tpl=FunctionTemplate::New(isolate,New);tpl->SetClassName(String::NewFromUtf8(isolate,"MyObject").ToLocalChecked());tpl->InstanceTemplate()->SetInternalFieldCount(1);Local<Context>context=isolate->GetCurrentContext();constructor.Reset(isolate,tpl->GetFunction(context).ToLocalChecked());AddEnvironmentCleanupHook(isolate,[](void*){constructor.Reset();},nullptr);}voidMyObject::New(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();Local<Context>context=isolate->GetCurrentContext();if(args.IsConstructCall()){// Вызывается как конструктор: `new MyObject(...)`doublevalue=args[0]->IsUndefined()?0:args[0]->NumberValue(context).FromMaybe(0);MyObject*obj=newMyObject(value);obj->Wrap(args.This());args.GetReturnValue().Set(args.This());}else{// Вызывается как обычная функция `MyObject(...)`, превращаем в вызов конструктора.constintargc=1;Local<Value>argv[argc]={args[0]};Local<Function>cons=Local<Function>::New(isolate,constructor);Local<Object>instance=cons->NewInstance(context,argc,argv).ToLocalChecked();args.GetReturnValue().Set(instance);}}voidMyObject::NewInstance(constFunctionCallbackInfo<Value>&args){Isolate*isolate=args.GetIsolate();constunsignedargc=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