Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Пишем асинхронный модуль для node.js с поддержкой C

Anna | 25.06.2014 | нет комментариев
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 и отправлять его строкой), но это костыль, лишенный смысла и безжалостный. И 3-й метод — это нативный модуль. Я не буду описывать, как получить данные от WMI, а опишу что-нибудь менее ёмкое. Кому увлекательно — умоляю под кат.

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.15depsv8include 
node-v0.8.15depsuvinclude 
node-v0.8.15src 

И сейчас добавим файл начального кода. На данном этапе дозволено выйти из 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 ] 

Это моя первая статья, умоляю крепко не бранить.
Если есть вопросы, умоляю, задавайте!
Ссылки

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB