Node.js is evolving, and it’s quite possible to experiment with writing graphical applications or some console utilities and services. During development, it may be necessary to use some kind of system calls, for example, to WMI (WMI cannot be accessed directly from node.js, and WMI requests can be long, which will block the event loop, and, for example, if you are connected via the web -sockets, the connection may break). There are several options. You can use the module (for example, node-ffi) and try to play with it. There is another way, more precisely, a crutch. In Windows, there is a so-called WScript (Windows Script Host) - this is a Windows component designed to run, for example, JScript, VBScript. JScript can access WMI directly, so we can run child_process in which JScript will work and receive data from it (form, for example, JSON and send it as a string), but this is a crutch, meaningless and merciless. And the third way is the native module. I will not describe how to get data from WMI, but describe something less capacious. Who cares - I ask under the cat.
UPD: Setting up the environment
I used to compile and configure VS2010.
First, download the source code node.js, unpack and run vcbuild.bat, which lies in the root. vcbuild.bat creates the necessary projects for Visual Studio and configs. To work batch file need Python 2.7. Next, install node-gyp with the
npm install node-gyp
.
Now we will create a project in Visual Studio (CLR Empty Project), go to the project properties, change the configuration type to .dll, the extension to .node, and set the support for the CLR environment. Go to the directories section, in the include directories add paths to the following folders
node-v0.8.15\deps\v8\include
node-v0.8.15\deps\uv\include
node-v0.8.15\src
')
And now add the source file. At this stage, you can exit VS (optional) and open your favorite Notepad / Sublime / WebStorm.
Now let's move to the source directory and create a binding.gyp file there, this file will tell the node-gyp utility how to build our application. For my example, it is very simple and straightforward.
{ "targets": [ { "target_name": "getSummAsync", "sources": [ "async.cpp" ] } ] }
Now we can compile. We set the
node-gyp configure
line in the console with binding.gyp and then
node-gyp build
Now our compiled module will be in the build / release folder.
Example itself
I will not use any system calls, since there is no sense in it, it will only complicate the example. So, let's begin.
For example, we will pass an array of integers, count its sum, receive positive elements and return them to the user.
To begin with, we will declare a structure in which, in turn, we will declare the data structures we need.
struct Summ_req { vector<int> numbers; vector<int> gtz; int result; Persistent<Function> callback; };
This is the vector in which we will store our numbers.
vector<int> numbers;
A vector in which we will store numbers greater than zero.
vector<int> gtz;
Here we will store the result.
int result;
It is important to understand why we will use vector, although, it seems, you can do with standard v8 templates. But it is not. About this below.
The module will have 3 functions, the main one, which we call from node.js, and two others, which, in fact, make our module asynchronous.
Work functions
getSummAsync takes two arguments, our array of elements and callback. We check if the parameters with which the function is called are correct, and if they are correct, we customize them, that is, in order to be able to communicate with the arguments, they must be converted to the necessary type.
Local<Function> callback = Local<Function>::Cast(args[1]); Local<Array> numbers = Local<Array>::Cast(args[0]);
Next, we initialize the structure and pass our callback into it and write the array into a vector.
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 is desirable because after all, our callback is used not only in this function.
And we run our worker in the queue.
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(); }
In the Worker function, I think everything is clear. Count the numbers and return the results to the structure. Now about why we use the vector, rather than the means of v8. The Worker function works in a separate stream, and node.js and v8 allow only one stream to execute js, that is, you cannot create an array of v8 in a separate stream.
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); } }
Now function After. After the Worker has completed, the After function is called, which can already return data to node.js.
Here, and not in the Worker function, we get the resulting array, for the reason I mentioned above.
Handle<Value> argv[2];
Here we put the return values.
request->callback->Call(Context::GetCurrent()->Global(), 2, argv);
And call our callback with the parameters that are written to 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; }
Now we can call our module from node.js, having first compiled it using the node-gyp utility.
var foo = require('./getSummAsync.node') foo.getSummAsync([1,2,3,6,-5],function(a, b){ console.log(a, b); });
Result
7 [ 1, 2, 3, 6 ]
This is my first article, please do not scold.
If you have questions, please ask!
Links