Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call an existing JavaScript function from a C++ Node.js addon - from another thread

Maybe I just don't search for the right terms but I'm stuck..

I need to call a JavaScript function from C++, very similar to what can be done using the plain C API.

Note: I don't want to pass a callback to the C++ code but I already know the name of the function to be called!

So for example I have a function like this in JavaScript:

function log_message_callback(context, message) {
  console.log(`${context}: ${message}`);
}

my_napi_module.initialize();   // <-- starts thread that would call log_message_callback

And from C++ I want to call it (btw, from a thread different from the main thread):

#include <napi.h>

void log_message_callback(char const* message) {
  // magic Napi code which would call log_message_callback in JavaScript
} 

void some_thread_fn() {
  log_message_callback("hello world");
}

Can I do this? How would I do this? And what should I have been looking for?!

like image 394
frans Avatar asked Aug 07 '19 14:08

frans


2 Answers

JavaScript functions can normally only be called from a native addon's main thread. You may get more information about it from

. https://nodejs.org/dist/latest-v11.x/docs/api/n-api.html#n_api_asynchronous_thread_safe_function_calls

The napi_call_function() can be used for calling a JavaScript function from native layer. The documentation has a code snippet for it's usage as well. https://nodejs.org/dist/latest-v11.x/docs/api/n-api.html#n_api_napi_call_function

like image 193
Satyan Avatar answered Nov 02 '22 17:11

Satyan


This is NOT an answer - just some research outcomes..

Looks like the code I have to write has to look like this - currently I don't know how to set up everything to work..

Most of the code is taken from this eventing fork.

These value have to be available - seems like I have to initialize them in the module initialization..

static v8::Persistent<v8::Context> context_;
static v8::Isolate *isolate_;

This function does both turning log_message_callback() from the JavaScript wold into a v8::Function and calling it. A more sophisticated approach would separate these steps:

extern "C" {

void log_message_callback(char const* message) {
  v8::Locker locker(isolate_);
  v8::Isolate::Scope isolate_scope(isolate_);
  v8::HandleScope handle_scope(isolate_);
  auto context = context_.Get(isolate_);
  v8::Context::Scope context_scope(context);

  v8::Persistent<v8::Function> log_message_callback_fn;

  /// this is only needed once - just for demonstration
  {
    auto global = context->Global();
    v8::Local<v8::Value> log_message_callback_def;
    if (!global->Get(
          context, v8Str(isolate_, "log_message_callback")).ToLocal(&log_message_callback_def)) {
      return;
    }

    if (!log_message_callback_def->IsFunction()) {
      return;
    }


    if (log_message_callback_def->IsFunction()) {
      auto on_update_fun = log_message_callback_def.As<v8::Function>();
      log_message_callback_fn.Reset(isolate_, on_update_fun);
    }
  }  

  v8::Local<v8::Value> args[2];
  log_message_callback_fn.Get(isolate_)->Call(context->Global(), 2, args);
}
like image 43
frans Avatar answered Nov 02 '22 15:11

frans