C /
CModulesInNodeSummaryAccessors will need to bind interactions with novel technologies -- both software and hardware, which are inaccessible from JavaScript. Cape Code provides Nashorn to provide a JS bindings for Java methods. Node allows developers to author "add-ons" in C++, which can be accessed from Node using `require` keyword. Node is an amalgam of:
The problem with Node is that all three of the above are moving targets, developed by different teams. That is, the "RTFM" mantra is particularly difficult to put into practice, as you need to read 3 manuals, with at least 3 of these frequently lying due to API changes. This page is set up to collate various bits of practical advice for anyone wishing to bind their C/C++ libraries into Node modules to build accessors. AudienceThis document assumes familiarity with C, C++, and threads. TODO: outline C++11 features used in code examples as I write the code examples... Documentation Resources
Addressing Documentation Shortcomings:
Code Snippets for Common TasksTo build anything, you will need `node-gyp` installed globally: sudo npm install -g node-gyp For more information on how to use it, see the node-gyp section below. Addon Basics
Storing Persistent VariablesC++ functions that are accessed from a Node module will have the following signature: void myFunction( const v8::FunctionCallbackInfo<Value>& args ) The 1. Note that when getting values from javascript, V8 will pass a To store a javascript callback: v8::Persistent<v8::Function> storedJsCallback; // for clarity, as a global variable ... void functionToStoreCallback( const v8::FunctionCallbackInfo<Value>& args ) { auto isolate = args.GetIsolate(); // get a pointer to the V8 javascript VM v8::Local<v8::Function> jsCallback = v8::Local<v8::Function>::Cast( args[0] ); // get a Local handle to a callback passed to your function storedJsCallback.Reset( isolate, jsCallback ); // due to breaking changes this call is poorly documented } Note that as of V8 v0.12, To call a stored //Persistent<Function> storedJsCallback is a global as above void functionToCallTheCallback( const FunctionCallbackInfo<Value>& args ) { auto isolate = args.GetIsolate(); Local<Function> callable = Local<Function>::New( isolate, storedJsCallback ); // obtain a local handle // generate an array of javascript values to be passed as function arguments callable->Call(isolate->GetCurrentContext(), ...argc..., ...argv...); // for example: // callable->Call(isolate->GetCurrentContext(), args.Length(), args ); // will call the stored callback with the arguments passed to this function } Async OperationAsynchronous operations with Node require interacting with spawning an async operation:
waking up the Node event loop (V8 / javascript) from a different thread: * uv_async_t
* uv_async_send()
You own the new thread:That is, you would like to spawn a new thread that will do some work. After completion, you will need the main event loop in Node to call a javascript function with the results of the computation as arguments. This requires some setup:
# convert javascript arguments to C++ datatypes
# store these C++ datatypes in a structure that will be accessible to the worker thread
# this function is a plain vanilla C++ function that has no access to the V8 Isolate, or any JavaScript variables
# this function will be called from the event loop thread, and therefore can interact with V8/Javascript
# use Isolate::GetCurrent() to obtain a pointer to the V8 instance for creating javascript variables & calling functions
Note that because we are dealing with Javascript callbacks, we will be using #include <node.h> #include <uv.h> #include <iostream> #include <string> #include <thread> using namespace v8; using namespace std; // the structure for data accessible from different threads: struct Baton { // the uv_work_t must be the initial element to ensure safe casts of uv_work_t* to Baton* uv_work_t request; Persistent<Function> callback; // callback if you need data back in JS int countdown; // whatever else data fields as necessary }; void AsyncTestWork (uv_work_t* req); void AsyncTestAfter(uv_work_t* req, int status); void AsyncTestPrep(const FunctionCallbackInfo< Value >& args) // this function is called from Node { cout << "AsyncTestPrep called from thread " << std::this_thread::get_id() << '\n'; auto isolate = args.GetIsolate(); Baton* baton = new Baton(); baton->request.data = baton; // set the uv_work_t data field to point to our Baton baton->callback.Reset(isolate, Local<Function>::Cast(args[0])); baton->countdown = 3; uv_queue_work(uv_default_loop(), &baton->request, AsyncTestWork, AsyncTestAfter); args.GetReturnValue().Set(Undefined(isolate)); } void AsyncTestWork(uv_work_t* req) { // This method will run in a separate thread where you can do your blocking background work. // In this function, you cannot under any circumstances access any V8/node js variables. (Use the Baton) cout << "AsyncTestWork Enter\n"; cout << "AsyncTestWork called from thread " << std::this_thread::get_id() << '\n'; Baton* baton = static_cast<Baton*>(req->data); // data field of req points to the Baton as per above baton->countdown -= 1; // async subtraction -- the work we're doing cout << "AsyncTestWork Exit\n"; } void AsyncTestAfter(uv_work_t* req, int status) { // This is what is called after the 'task' is done, you can now move any data from // Baton to the V8/Nodejs space and invoke callback functions cout << "AsyncTestAfter Enter\n"; cout << "AsyncTestAfter called from thread " << std::this_thread::get_id() << '\n'; Baton* baton = static_cast<Baton*>(req->data); cout << "baton countdown is : " << baton->countdown << '\n'; auto isolate = Isolate::GetCurrent(); // get the JS VM HandleScope scope(isolate); Local<Value> argv1[] = { Number::New( isolate, baton->countdown) }; cout << "created javascript arguments from countdown\n"; // Call back to the JS function(s): auto callbackHandle = Local< Function >::New( isolate, baton->callback ); callbackHandle->Call( isolate->GetCurrentContext()->Global(), 1, argv1); // resubmit if we're not done, otherwise cleanup: if (baton->countdown > 0) { uv_queue_work(uv_default_loop(), &baton->request, AsyncTestWork, AsyncTestAfter); } else { baton->callback.Reset(); delete baton; } } void Init(Local< Object > exports) { NODE_SET_METHOD( exports, "asyncTest", AsyncTestPrep ); } NODE_MODULE( addon, Init ) The `bindings.gyp` file for creating the add-on: { "targets": [ { "target_name": "async", "sources": [ "threadworker.cpp" ], "cflags": ["-Wall", "-std=c++11"], 'xcode_settings': { 'OTHER_CFLAGS': [ '-std=c++11' ], }, 'msvs_settings': { 'VCCLCompilerTool': { 'ExceptionHandling': 1 # /EHsc } }, 'configurations': { 'Release': { 'msvs_settings': { 'VCCLCompilerTool': { 'ExceptionHandling': 1, } } } }, "conditions": [ [ 'OS=="mac"', { "xcode_settings": { 'OTHER_CPLUSPLUSFLAGS' : ['-std=c++11','-stdlib=libc++'], 'OTHER_LDFLAGS': ['-stdlib=libc++'], 'MACOSX_DEPLOYMENT_TARGET': '10.7' } } ] ] } ] } Sample output: > require('./build/Release/async').asyncTest(function(v){ console.log("hello from javascript : " + v); }); AsyncTestPrep called from thread 0x7fff7b9e6000 AsyncTestWork Enter AsyncTestWork called from thread 0x70000221b000 AsyncTestWork Exit undefined > AsyncTestAfter Enter AsyncTestAfter called from thread 0x7fff7b9e6000 baton countdown is : 2 created javascript arguments from countdown hello from javascript : 2 AsyncTestWork Enter AsyncTestWork called from thread 0x700001215000 AsyncTestWork Exit AsyncTestAfter Enter AsyncTestAfter called from thread 0x7fff7b9e6000 baton countdown is : 1 created javascript arguments from countdown hello from javascript : 1 AsyncTestWork Enter AsyncTestWork called from thread 0x700001a18000 AsyncTestWork Exit AsyncTestAfter Enter AsyncTestAfter called from thread 0x7fff7b9e6000 baton countdown is : 0 created javascript arguments from countdown hello from javascript : 0 Notice that although Hardware owns the new thread:Node-GYP NotesIf you were thinking "I love learning new build systems because I love it when I have to learn entirely unfamiliar ways to pass some flags to my compiler" boy do I have good news for you! Node uses GYP (Generate Your Projects) meta build system, which Google wrote out of spite for CMake. (This sounds like a joke, but I assure you it isn't.) To build a node addon, you need to write a TODO: post my own that build on Mac & Linux and write something snarky about Windows. (TODO: check if Microsoft is our sponsor before proceeding with the last part...) For now, please see "binding.gyp" files out in the wild for examples. See Also |