среда, 15 июня 2011 г.

Node.Js: модули на C/C++

Во время работы встала задача научиться писать интерфейсы к C/C++ функциям для Node.JS. Эта задача становится всё более актуальной в свете того, что основные камни летят в node именно из-за нехватки некоторого привычного для разработчиков функционала (ничего они не понимают в жизни).
В данной статье я хотел бы немного описать своё видение платформы Node.JS, а также поговорить немного о том, как писать модули к Node на C++ .
Сразу оговорюсь: я не профессиональный web-разработчик, никогда  в жизни профессионально не занимался даже бразуерным JavaScript-ом до недавнего времени, когда ко мне обратились примерно со следующей просьбой: "Узнай, что  вообще такое этот node и попробуй прикрутить туда какую-нибудь интересную штуку, например мои любимые linux-библиотеки на C". Было бы знание, да незнание помогло. Порой труднее оказывается объяснить, что такое серверный JavaScript людям, для которых DOM - родной дом.
На первый взгляд node действительно оказывается привлекательным для работы с сокетами, портами и вообще всем, что нужно время от времени опрашивать и периодически формировать на это какие-либо ответы. Его однопоточной модели с Event Loop оказывается вполне достаточно для проектирования приложений в рамках своих "родных" модулей, то есть философия данной платформы является вполне оправданной и самодостаточной. Один из самых полных обзоров node на языке Пушкина http://fprog.ru/2010/issue6/dmitry-demeshchuk-node.js-vs-erlang/ изложен именно в ключе сравнения с языком Erlang, который на сегодня является одной из самых эффективных платформ для создания коммуникационных узлов в сети.
"Ну а как же тонны уже написанного кода, а также инструменты, без которых нельзя?"
Этим, собственно, сейчас и приходит ся заниматься сообществу node.js-разработчиков.

Первую статью хотелось бы посвятить обзору важных составляющих движка V8, на котором, собственно, и написан node.js и благодаря которому в Chrome и в Node так быстро обрабатывается JavaScript.


Первое и самое важное, что нам необходимо для нашей задачи - заголовочный файл v8.h или его Doxygen представление (например http://bespin.cz/~ondras/html/).

Основной элемент представления в V8 -  Handle, который представляет нам любую структурную единицу языка JavaScript, а также взаимодействует со сборщиком мусора V8.
Все эти элементы существуют в определённом scope, который мы явно определяем.
Через определённые промежутки времени сборщик мусора V8 занимается удалением из памяти handle-ов, на которые больше никто не ссылается, таким образом освобождая место в куче для новых объектов JavaScript.

Правда тут есть одно исключение - помимо обычных handle-ов (локальных, Local) существуют также и постоянные, которые находятся вне ведения сборщика мусора (Persistent).
Каждый Handle может содержать в себе некоторый JavaScript-объект.
Структура объектов JavaScript в представлении V8 имеет следующий вид:

Значения, которые мы передаём в наш модуль из JavaScript - как правило, Value и должны быть явно преобразованы к тому типу, который мы ожидаем с помощью встроенных в V8 преобразователей типов.
Раз мы пишем модуль, значит нам нужно представить в нём определённый набор функций, которые будут выполнять C/C++ код, но при этом могут быть вызваны из JavaScript (что-то вроде FFI).
Начнём с очень простого. Модуль, написанный нами на C++ и запрашиваемый в Node (require('./our_module')) есть не что иное, как обычный JavaScript-объект. Задача, которая стоит перед нами - проста: описать необходимые поля этого модуля. В случае, когда мы имеем библиотеку функций на C удобно представить поля нашего объекта, как набор функций - просто чтобы не мучаться с архитектурой и не городить новые сущности.
#include <stdio.h>
#include <node.h>

using namespace v8;

static Handle<Value> ExampleFunction(const Arguments &args)
{
    printf("DEBUG:: This is our example function\n");
}

extern "C" void
init (Handle<Object> target)
{
HandleScope scope;
    target->Set(String::NewSymbol("ExampleFunction"), FunctionTemplate::New(ExampleFunction)->GetFunction());
}
В V8 для того, чтобы получать в Run-time какие-либо новые JavaScript функции, должен быть использован объект v8::FunctionTemplate. Он является связующим звеном между реальной функцией и контекстом выполнения. Наш основной объект, который мы запрашиваем, носит название target. В функции init мы просто задаём поле с именем ExampleFunction нашего основного объекта как функцию, взятую из v8::FunctionTemplate. Единственный аргумент функции, на которую ссылается FunctionTemplate имеет тип v8::Arguments - это массив из элементов v8::Handle аргументов функции, которую мы вызвали из JavaScript. Наша функция также будет возвращать Value (помним, что в JavaScript динамическая типизация).
Для того, чтобы собрать наш модуль, редактируем файл wscript:
srcdir = ''
blddir = 'lib'
VERSION = '0.0.1'

def set_options(opt):
  opt.tool_options('compiler_cxx')

def configure(conf):
  conf.check_tool('compiler_cxx')
  conf.check_tool('node_addon')
  conf.env.append_unique('CXXFLAGS', ["-lpthread"])

def build(bld):
  obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
  obj.target = 'sample'
  obj.source = './binding.cc'
Собираем и тестируем:

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

Ссылки:
http://code.google.com/apis/v8/embed.html
http://howtonode.org/how-to-module
http://fprog.ru/2010/issue6/dmitry-demeshchuk-node.js-vs-erlang/

Комментариев нет: