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, а опишу что-нибудь менее ёмкое. Кому интересно — прошу под кат.
Я использовал для компиляции и настройки VS2010.
Сначала скачиваем исходники node.js, распаковываем и запускаем vcbuild.bat, который лежит в корне. vcbuild.bat создает необходимые проекты для Visual Studio и конфиги. Для работы батника потребуется Python 2.7. Далее устанавливаем node-gyp командой
Теперь создадим проект в Visual Studio (CLR Empty Project), заходим в свойства проекта, тип конфигурации меняем на .dll, расширение на .node, и ставим поддержку CLR-среды. Переходим в раздел каталоги, в каталогах включения добавляем пути до следующих папок
И теперь добавим файл исходного кода. На данном этапе можно выйти из VS (по желанию) и открыть свой любимый Notepad/Sublime/WebStorm.
Теперь перейдём в каталог с исходниками и создадим там файл binding.gyp, этот файл подскажет утилите node-gyp, как и из чего собирать наше приложение. Для моего примера он очень простой и понятный.
Теперь можем компилировать. Прописываем в консоли в директории с binding.gyp строчку
Теперь наш скомпилированный модуль будет лежать в папке build/Release
Я не буду использовать какие-то системные вызовы, т.к. смысла особого в этом нет, это лишь усложнит пример. И так, начнём.
Для примера мы будем передавать массив целых чисел, считать его сумму, получать положительные элементы и возвращать их пользователю.
Для начала объявим структуру, в которой, в свою очередь, объявим нужные нам структуры данных.
Это вектор, в котором мы будем хранить наши числа.
Вектор, в котором будем хранить числа больше нуля.
Здесь мы будем хранить результат
Важно понимать, зачем мы будем использовать vector, хотя, вроде бы, можно обойтись стандартными шаблонами v8. Но это не так. Об этом чуть ниже.
В модуле будет 3 функции, основная, которую мы вызываем из node.js, и две другие, которые, собственно, и делают наш модуль асинхронным.
Функции work
getSummAsync принимает два аргумента, наш массив элементов и callback. Проверяем, верны ли параметры, с которыми вызвана функция, и, если верны, кастомизируем их, то есть, чтобы уметь общаться с аргументами, их надо привести к нужному типу.
Далее инициализируем структуру и передадим в неё наш callback и массив записываем в вектор.
Persistent желательно, т.к. всё-таки наш callback используется не только в этой функции.
И запускаем наш воркер в очередь.
В функции Worker, думаю, всё понятно. Считаем числа и возвращаем результаты в структуру. Теперь о том, почему мы используем вектор, а не средства v8. Функция Worker работает в отдельном потоке, а node.js и v8 позволяют лишь один поток для выполнения js, то есть нельзя создать массив v8 в отдельном потоке.
Теперь функция After. После того, как Worker отработал, вызывается функция After, которая уже может вернуть данные в node.js.
Здесь, а не в функции Worker, мы получим результирующий массив, по причине, о которой я говорил выше.
Сюда мы поместим возвращаемые значения
И вызовем наш callback с параметрами, которые записали в argv.
Теперь можем вызвать из node.js наш модуль, предварительно скомпилировав его с помощью утилиты node-gyp.
Результат
Это моя первая статья, прошу сильно не ругать.
Если есть вопросы, прошу, задавайте!
Ссылки
UPD: Настройка окружения
Я использовал для компиляции и настройки VS2010.
Сначала скачиваем исходники node.js, распаковываем и запускаем vcbuild.bat, который лежит в корне. vcbuild.bat создает необходимые проекты для Visual Studio и конфиги. Для работы батника потребуется Python 2.7. Далее устанавливаем node-gyp командой
npm install node-gyp
.Теперь создадим проект в Visual Studio (CLR Empty Project), заходим в свойства проекта, тип конфигурации меняем на .dll, расширение на .node, и ставим поддержку CLR-среды. Переходим в раздел каталоги, в каталогах включения добавляем пути до следующих папок
node-v0.8.15\deps\v8\include
node-v0.8.15\deps\uv\include
node-v0.8.15\src
И теперь добавим файл исходного кода. На данном этапе можно выйти из VS (по желанию) и открыть свой любимый Notepad/Sublime/WebStorm.
Теперь перейдём в каталог с исходниками и создадим там файл binding.gyp, этот файл подскажет утилите node-gyp, как и из чего собирать наше приложение. Для моего примера он очень простой и понятный.
{
"targets": [
{
"target_name": "getSummAsync",
"sources": [ "async.cpp" ]
}
]
}
Теперь можем компилировать. Прописываем в консоли в директории с binding.gyp строчку
node-gyp configure
и потом node-gyp build
Теперь наш скомпилированный модуль будет лежать в папке build/Release
Сам пример
Я не буду использовать какие-то системные вызовы, т.к. смысла особого в этом нет, это лишь усложнит пример. И так, начнём.
Для примера мы будем передавать массив целых чисел, считать его сумму, получать положительные элементы и возвращать их пользователю.
Для начала объявим структуру, в которой, в свою очередь, объявим нужные нам структуры данных.
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 ]
Это моя первая статья, прошу сильно не ругать.
Если есть вопросы, прошу, задавайте!
Ссылки