I'm currently building an app (Electron) and i need to connect it with a c++ library. I have done most of the binding using the NodeJS c++ addons, but i'm missing an important part that is related with receiving the events generated by the c++ library on my Javascript code.
void Event1(int64_t id)
{
ArrayBufferAllocator allocator;
Isolate::CreateParams create_params;
create_params.array_buffer_allocator = &allocator;
Isolate* isolate = Isolate::New(create_params);
{
v8::Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
Local<Context> context = Context::New(isolate);
Context::Scope context_scope(context);
v8::Local<v8::Value> argv[1] = {
Nan::New("OnWillPlayTrack").ToLocalChecked(), // event name
};
Nan::MakeCallback(isolate->GetCurrentContext()->Global(),"emit", 1, argv);
}
isolate->Dispose();
}
The Event1 is being called by the c++ lib, that has nothing to do with V8, and now I want to fire an event to JavaScript, back to Node (EventEmitter?). I think that the biggest problem is that most of the v8 functions now need an Isolate, and most of the examples found throughout the web are pretty old.
The Event1 code crashes at MakeCallBack with:
Thread 24 Crashed:: App
0 libnode.dylib 0x000000010a81e35b v8::String::NewFromOneByte(v8::Isolate*, unsigned char const*, v8::String::NewStringType, int) + 43
1 libnode.dylib 0x000000010a4b042f node::OneByteString(v8::Isolate*, char const*, int) + 15
2 libnode.dylib 0x000000010a4ba1b2 node::MakeCallback(node::Environment*, v8::Local<v8::Object>, char const*, int, v8::Local<v8::Value>*) + 50
3 libnode.dylib 0x000000010a4ba240 node::MakeCallback(v8::Isolate*, v8::Local<v8::Object>, char const*, int, v8::Local<v8::Value>*) + 96
4 addon.node 0x0000000110e62e1f Event1(long long) + 291 (v8.h:6721)
Any help will be greatly appreciated!
The common practice for emitting events from a Node.js C++ addon module is to wrap the C++ component with a javascript component and ship them together as a single module. The javascript component passes a callback into the c++ component. When the C++ component receives an event, it should call the javascript callback, which should in turn, use an EventEmitter
to emit
the event.
If you'd like to see an example of this in practice, I do this in my node-dvbtee
addon module: http://github.com/mkrufky/node-dvbtee
The C++ addon source is built from the src/
directory.
The javascript wrapper can be found in the lib/
directory.
Look inside lib/parser.js
to find where the function listenTables
is called with a callback function as it's parameter. Depending on the module's configured options
, that callback will either call self.push()
(for streaming) or self.emit()
to emit an event.
To avoid link rot, the javascript sample looks like this:
var _Dvbtee = require('bindings')('dvbtee.node')
var util = require('util')
var stream = require('stream')
var Parser = function (options) {
var self = this
if (!(this instanceof Parser)) {
return new Parser(options)
}
self.options = {}
if (typeof options === 'object') {
self.options.passThru = (options.hasOwnProperty('passThru') && options.passThru)
} else {
self.options.passThru = false
}
var _parser = new _Dvbtee.Parser
var _listenTables = _Dvbtee.Parser.prototype.listenTables
var listenTables = _listenTables.bind(_parser)
stream.Transform.call(this, {
objectMode: !self.options.passThru
})
self._buffer = new Buffer(0)
listenTables(function(a,b,c) {
// arguments a & b are ignored
self.emit('psip', c)
})
}
util.inherits(Parser, stream.Transform)
Parser.prototype._transform = ...
The c++ addon component would have to store the supplied callback function in a persistent handle. NAN provides a perfect structure for this: Nan::Callback
The c++ addon component code should look something like the following:
void dvbteeParser::listenTables(const Nan::FunctionCallbackInfo<v8::Value>& info) {
dvbteeParser* obj = ObjectWrap::Unwrap<dvbteeParser>(info.Holder());
int lastArg = info.Length() - 1;
if ((lastArg >= 0) && (info[lastArg]->IsFunction())) {
obj->m_tableReceiver.subscribe(info[lastArg].As<v8::Function>());
}
info.GetReturnValue().Set(info.Holder());
}
...
void TableReceiver::subscribe(const v8::Local<v8::Function> &fn) {
m_cb.SetFunction(fn);
}
... where m_cb
is a Nan::Callback
inside my TableReceiver
class. You can actually call this callback in your native addon's event generating function like so:
v8::Local<v8::Value> argv[] = {
a,
b,
c
};
m_cb.Call(3, argv);
where a, b and c are of type v8::Local<v8::Value>
. Of course, the argv
array could have any size. Each element corresponds to an argument supplied to the given callback function.
You do not need a separate v8::Isolate
to call V8 from your code, you'd only want one if you wanted multiple v8 engines
to run multiple Javascript interpreters in parallel.
I think the reason for your problem is that you're calling V8 from another thread, which is quite risky. Use an uv_acync_t
struct to signal your 'main/V8' thread from your C++ addon thread. See this thread for more details on how to do this.
Besides, MakeCallback
is not the proper way to fire off callbacks, see https://github.com/nodejs/nan/issues/284.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With