...

воскресенье, 23 июня 2013 г.

[Из песочницы] Пишем асинхронный модуль для node.js с помощью C++

Node.js развивается, и, вполне уже можно экспериментировать с написанием графических приложений либо каких-то консольных утилит и сервисов. В процессе разработки может возникнуть необходимость использовать какие-то системные вызовы, например, к WMI (к WMI нельзя обратиться напрямую из node.js, и запросы WMI могут быть долгими, что заблокирует event loop, и, например, если связь у Вас через веб-сокеты, связь может оборваться). Тут существует несколько вариантов. Можно воспользоваться модулем (например, node-ffi) и попробовать поиграться с ним. Есть ещё способ, точнее, костыль. В Windows существует так называемый WScript (Windows Script Host) — это компонент Windows, предназначенный для запуска, например, JScript, VBScript. JScript может обращаться к WMI напрямую, так что мы имеем возможность запустить child_process, в котором будет работать JScript, и получать от него данные (формировать, например, JSON и отправлять его строкой), но это костыль, бессмысленный и беспощадный. И третий способ — это нативный модуль. Я не буду описывать, как получить данные от WMI, а опишу что-нибудь менее ёмкое. Кому интересно — прошу под кат.


Я не буду использовать какие-то системные вызовы, т.к. смысла особого в этом нет, это лишь усложнит пример. И так, начнём.


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


Для начала объявим структуру, в которой, в свою очередь, объявим нужные нам структуры данных.



struct Summ_req
{
vector<int> numbers;
vector<int> gtz;
int result;
Persistent<Function> callback;
};




Это вектор, в котором мы будем хранить наши числа.

vector<int> numbers;




Вектор, в котором будем хранить числа больше нуля.

vector<int> gtz;




Здесь мы будем хранить результат

int result;


Важно понимать, зачем мы будем использовать vector, хотя, вроде бы, можно обойтись стандартными шаблонами v8. Но это не так. Об этом чуть ниже.


В модуле будет 3 функции, основная, которую мы вызываем из node.js, и две другие, которые, собственно, и делают наш модуль асинхронным.

Функции work

getSummAsync принимает два аргумента, наш массив элементов и callback. Проверяем, верны ли параметры, с которыми вызвана функция, и, если верны, кастомизируем их, то есть, чтобы уметь общаться с аргументами, их надо привести к нужному типу.



Local<Function> callback = Local<Function>::Cast(args[1]);
Local<Array> numbers = Local<Array>::Cast(args[0]);


Далее инициализируем структуру и передадим в неё наш callback и массив записываем в вектор.



Summ_req* request = new Summ_req;
request->callback = Persistent<Function>::New(callback);
for (size_t i = 0; i < numbers->Length(); i++) {
request->numbers.push_back(numbers->Get(i)->Int32Value());
}


Persistent желательно, т.к. всё-таки наш callback используется не только в этой функции.


И запускаем наш воркер в очередь.



uv_queue_work(uv_default_loop(), req, Worker, After);




getSummAsync


static Handle<Value> getSummAsync (const Arguments& args)
{

HandleScope scope;

if (args.Length() < 2 || !args[0]->IsArray())
{
return ThrowException(Exception::TypeError(String::New("Bad arguments")));
}


if (args[1]->IsFunction())
{
Local<Function> callback = Local<Function>::Cast(args[1]);
Local<Array> numbers = Local<Array>::Cast(args[0]);

Summ_req* request = new Summ_req;
request->callback = Persistent<Function>::New(callback);
for (size_t i = 0; i < numbers->Length(); i++) {
request->numbers.push_back(numbers->Get(i)->Int32Value());
}
uv_work_t* req = new uv_work_t();
req->data = request;

uv_queue_work(uv_default_loop(), req, Worker, After);
}
else
{
return ThrowException(Exception::TypeError(String::New("Callback missing")));
}

return Undefined();
}







В функции Worker, думаю, всё понятно. Считаем числа и возвращаем результаты в структуру. Теперь о том, почему мы используем вектор, а не средства v8. Функция Worker работает в отдельном потоке, а node.js и v8 позволяют лишь один поток для выполнения js, то есть нельзя создать массив v8 в отдельном потоке.
Worker


void Worker(uv_work_t* req)
{
Summ_req* request = (Summ_req*)req->data;
request->result = 0;
for (vector<int>::iterator it = request->numbers.begin(); it != request->numbers.end(); ++it) {
request->result += *it;
if (*it > 0) {
request->gtz.push_back(*it);
}
}
// request->result = request->int1 + request->int2;
}







Теперь функция After. После того, как Worker отработал, вызывается функция After, которая уже может вернуть данные в node.js.

Здесь, а не в функции Worker, мы получим результирующий массив, по причине, о которой я говорил выше.

Handle<Value> argv[2];




Сюда мы поместим возвращаемые значения

request->callback->Call(Context::GetCurrent()->Global(), 2, argv);




И вызовем наш callback с параметрами, которые записали в argv.

After


void After(uv_work_t* req)
{
HandleScope scope;

Summ_req* request = (Summ_req*)req->data;
delete req;

Handle<Value> argv[2];

argv[0] = Integer::New(request->result);
Local<Array> gtz = Array::New();
size_t i = 0;
for (vector<int>::iterator it = request->gtz.begin(); it != request->gtz.end(); ++it) {
gtz->Set(i, Integer::New(*it));
i++;
}
argv[1] = gtz;
TryCatch try_catch;

request->callback->Call(Context::GetCurrent()->Global(), 2, argv);

if (try_catch.HasCaught())
{
FatalException(try_catch);
}

request->callback.Dispose();

delete request;
}







Теперь можем вызвать из node.js наш модуль, предварительно скомпилировав его с помощью утилиты node-gyp.

var foo = require('./getSummAsync.node')

foo.getSummAsync([1,2,3,6,-5],function(a, b){
console.log(a, b);
});




Результат

7 [ 1, 2, 3, 6 ]

Это моя первая статья, прошу сильно не ругать.

Если есть вопросы, прошу, задавайте!

Ссылки


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends: 'You Say What You Like, Because They Like What You Say' - http://www.medialens.org/index.php/alerts/alert-archive/alerts-2013/731-you-say-what-you-like-because-they-like-what-you-say.html


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

Отправить комментарий