Node.js хорош по многим причинам, одна из которых - скорость, с которой вы можете создавать значимые приложения. Однако, как мы все знаем, это происходит за счет производительности (по сравнению с собственным кодом). Чтобы обойти это, вы можете написать свой код для взаимодействия с более быстрым кодом, написанным на C или C ++. Все, что нам нужно сделать, это сообщить Node, где найти этот код и как с ним взаимодействовать.
Есть несколько способов решить эту проблему в зависимости от того, какой уровень абстракции вы хотите. Мы начнем с самой низкой абстракции, а именно с Node Addon .
Дополнения
Аддон работает, обеспечивая связь между библиотеками Node и C / C ++. Для типичного разработчика Node это может быть немного сложно, так как вам придется фактически написать код C / C ++ для настройки интерфейса. Однако между этой статьей и документацией по Node вы сможете заставить работать несколько простых интерфейсов.
Есть несколько вещей, которые нам нужно сделать, прежде чем мы сможем приступить к созданию аддонов. Прежде всего, нам нужно знать, как компилировать (о чем разработчики Node к счастью забывают) собственный код. Это делается с помощью node-gyp . Затем мы кратко поговорим о nan , который помогает обрабатывать различные версии Node API.
узел-гипс
Существует множество различных типов процессоров (x86, ARM, PowerPC и т.
Д.) И даже больше операционных систем, с которыми приходится иметь дело
при компиляции кода. К счастью, за вас все это node-gyp
Как описано на
их странице Github, node-gyp
- это «кроссплатформенный инструмент
командной строки, написанный на Node.js для компиляции собственных
дополнительных модулей для Node.js». По сути, node-gyp
- это просто
оболочка вокруг gyp , созданная
командой Chromium.
В README проекта есть отличные инструкции по установке и использованию
пакета, поэтому вам следует прочитать их для получения более подробной
информации. Короче говоря, чтобы использовать node-gyp
вам нужно
сделать следующее.
Перейдите в каталог вашего проекта:
$ cd my_node_addon
Сгенерируйте соответствующие файлы сборки с помощью команды configure
, которая создаст либо Makefile
(в Unix), либо vcxproj
(в Windows):
$ node-gyp configure
И наконец, соберите проект:
$ node-gyp build
Это создаст /build
содержащий, среди прочего, скомпилированный
двоичный файл.
Даже при использовании более высоких абстракций, таких как ffi
, все
же хорошо понимать, что происходит под капотом, поэтому я рекомендую вам
потратить время на изучение тонкостей node-gyp
.
нан
nan
(собственные абстракции для Node) - это модуль, о котором легко
забыть, но он избавит вас от многих часов разочарования. Между версиями
Node v0.8
, v0.10
и v0.12
используемые версии V8 претерпели
некоторые большие изменения (в дополнение к изменениям внутри самого
узла), поэтому nan
помогает скрыть эти изменения от вас и обеспечивает
приятный согласованный интерфейс. .
Эта собственная абстракция работает, предоставляя объекты / функции C /
C ++ в файле заголовка #include <nan.h>
Чтобы использовать его, установите пакет nan
$ npm install --save nan
Добавьте эти строки в свой файл binding.gyp:
"include_dirs" : [
"<!(node -e \"require('nan')\")"
]
И вы готовы использовать методы /
функции
из nan.h
в своих хуках вместо исходного кода #include <node.h>
Я
очень рекомендую вам использовать nan
. В этом случае нет особого
смысла изобретать колесо.
Создание аддона
Перед тем, как начать работу над аддоном, убедитесь, что у вас есть время, чтобы ознакомиться со следующими библиотеками:
- Библиотека V8 JavaScript C ++, которая используется для фактического
взаимодействия с JavaScript (например, для создания функций, вызова
объектов и т. Д.).
- Примечание:
node.h
файл по умолчанию предлагается, но на самом делеnan.h
следует использовать вместо
- Примечание:
- libuv , кроссплатформенная библиотека асинхронного ввода-вывода, написанная на C. Эта библиотека полезна при выполнении любого типа ввода-вывода (открытие файла, запись в сеть, установка таймера и т. д.) в ваших собственных библиотеках и вы нужно сделать его асинхронным.
- Внутренние библиотеки узлов. Одним из наиболее важных объектов для
понимания является
node::ObjectWrap
, от которого происходит большинство объектов.
В оставшейся части этого раздела я покажу вам реальный пример. В этом
случае мы создадим перехватчик функции pow
библиотеки <cmath>
Поскольку вы почти всегда должны использовать nan
, я буду
использовать его во всех примерах.
В этом примере в вашем проекте аддона должны присутствовать как минимум следующие файлы:
pow.cpp
binding.gyp
package.json
Файл C ++ не обязательно называть pow.cpp
, но имя обычно отражает
либо то, что это надстройка, либо его конкретная функция.
// pow.cpp
#include <cmath>
#include <nan.h>
void Pow(const Nan::FunctionCallbackInfo<v8::Value>& info) {
if (info.Length() < 2) {
Nan::ThrowTypeError("Wrong number of arguments");
return;
}
if (!info[0]->IsNumber() || !info[1]->IsNumber()) {
Nan::ThrowTypeError("Both arguments should be numbers");
return;
}
double arg0 = info[0]->NumberValue();
double arg1 = info[1]->NumberValue();
v8::Local<v8::Number> num = Nan::New(pow(arg0, arg1));
info.GetReturnValue().Set(num);
}
void Init(v8::Local<v8::Object> exports) {
exports->Set(Nan::New("pow").ToLocalChecked(),
Nan::New<v8::FunctionTemplate>(Pow)->GetFunction());
}
NODE_MODULE(pow, Init)
Обратите внимание, что в конце NODE_MODULE
нет точки с запятой ( ;
). Это сделано намеренно, поскольку NODE_MODULE
самом деле не функция
- это макрос.
Приведенный выше код поначалу может показаться немного устрашающим для
тех, кто не писал на C ++ какое-то время (или когда-либо), но на самом
деле это не так уж сложно понять. Функция Pow
- это основа кода, в
котором мы проверяем количество переданных аргументов, типы аргументов,
вызываем встроенную pow
и возвращаем результат в приложение Node.
info
объект содержит все, что нам нужно знать о вызове, включая
аргументы (и их типы) и место для возврата результата.
Функция Init
основном просто связывает функцию Pow
имя "pow", а NODE_MODULE
фактически обрабатывает регистрацию
надстройки в Node.
package.json
мало чем отличается от обычного модуля Node. Хотя это не
кажется обязательным, в большинстве модулей Addon установлено значение
"gypfile": true
, но процесс сборки, кажется, все еще работает без
него. Вот что я использовал для этого примера:
{
"name": "addon-hook",
"version": "0.0.0",
"description": "Node.js Addon Example",
"main": "index.js",
"dependencies": {
"nan": "^2.0.0"
},
"scripts": {
"test": "node index.js"
}
}
Затем этот код необходимо встроить в файл «pow.node», который является
двоичным файлом аддона. Для этого вам нужно указать node-gyp
какие
файлы ему нужно скомпилировать, и имя файла, полученного в результате
двоичного файла. Хотя есть много других опций / конфигураций, которые вы
можете использовать с node-gyp
, для этого примера нам не нужно много.
binding.gyp
может быть таким простым, как:
{
"targets": [
{
"target_name": "pow",
"sources": [ "pow.cpp" ],
"include_dirs": [
"<!(node -e \"require('nan')\")"
]
}
]
}
Теперь, используя node-gyp
, сгенерируйте соответствующие файлы сборки
проекта для данной платформы:
$ node-gyp configure
И наконец, соберите проект:
$ node-gyp build
Это должно привести к pow.node
файла pow.node, который будет
находиться в каталоге build/Release/
Для того, чтобы использовать этот
крюк в коде приложения, просто require
в pow.node
файл (без «.node»
расширение):
var addon = require('./build/Release/pow');
console.log(addon.pow(4, 2)); // Prints '16'
Интерфейс внешней функции узла
Примечание . Пакет ffi
ранее назывался node-ffi
. Обязательно
добавьте новое ffi
в свои зависимости, чтобы избежать путаницы во
время npm install
:)
Хотя функциональность Addon, предоставляемая Node, дает вам всю
необходимую гибкость, не всем разработчикам / проектам она понадобится.
Во многих случаях такая абстракция, как ffi
подойдет и обычно требует
очень небольшого программирования на C / C ++ или вообще не требует его.
ffi
загружает только динамические библиотеки, что может быть
ограничивающим для некоторых, но также значительно упрощает настройку
хуков.
var ffi = require('ffi');
var libm = ffi.Library('libm', {
'pow': [ 'double', [ 'double', 'double' ] ]
});
console.log(libm.pow(4, 2)); // 16
Приведенный выше код работает, указывая библиотеку для загрузки (libm)
и, в частности, какие методы загружать из этой библиотеки (pow).
[ 'double', [ 'double', 'double' ] ]
строка говорит ffi
, что тип
возвращаемого значения и параметры метода в том, что в этом случае
состоит из двух double
параметров и double
вернулся.
Заключение
Хотя сначала это может показаться пугающим, создание аддона на самом деле не так уж и плохо после того, как у вас будет возможность самостоятельно проработать такой небольшой пример. По возможности я бы предложил подключиться к динамической библиотеке, чтобы значительно упростить создание интерфейса и загрузку кода, хотя для многих проектов это может быть невозможно или лучший выбор.
Есть ли примеры библиотек, для которых вы хотели бы видеть привязки? Дайте нам знать об этом в комментариях!