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);
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 в отдельном потоке.
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 отработал, вызывается worker, которая уже может вернуть данные в node.js.
Здесь, а не в функции Worker, мы получим результирующий массив, по причине, о которой я говорил выше.
Handle<Value> argv[2];
Сюда мы поместим возвращаемые значения
request->callback->Call(Context::GetCurrent()->Global(), 2, argv);
И вызовем наш callback с параметрами, которые записали в argv.
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 ]
Это моя первая статья, прошу сильно не ругать.
Если есть вопросы, прошу, задавайте!
Ссылки
Автор: dixoNich