Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send events from nodejs addon to javascript

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!

like image 869
David Rego Avatar asked Dec 23 '15 19:12

David Rego


2 Answers

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.

like image 90
mkrufky Avatar answered Sep 20 '22 23:09

mkrufky


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.

like image 33
ekarak Avatar answered Sep 20 '22 23:09

ekarak