9

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?!

frans
  • 8,868
  • 11
  • 58
  • 132
  • Are you ok with setting it one time? like `my_napi_module.callback_func = xyz`? – Tarun Lalwani Aug 12 '19 at 14:29
  • Would be a workaround for me - since the solution I'm working on has to work with emscripten and N-API at some time. But setting the callback from JS would at least be a good starting point :) – frans Aug 12 '19 at 14:32
  • 1
    Look at these two threads then https://stackoverflow.com/questions/29165155/calling-javascript-from-c-with-node-js and https://stackoverflow.com/questions/46559457/v8-extracting-a-global-object-from-nodejs-in-c. So basically you will use the second article to extract the function from the global object and then use the first approach to actually call the function. This gets both the things done as to what you want – Tarun Lalwani Aug 12 '19 at 14:38
  • any update on the links I posted? – Tarun Lalwani Aug 17 '19 at 18:35
  • They're indeed helpful since it seems to be an actually working examples. My problem is still that I have a thread which produces data I want to 'send' to the JavaScript world and I haven't found a way yet to get into the main thread apart from calling a C++ function from JavaScript. In this case I'm polling anyway and I don't need to call a JavaScript function anymore. Maybe this is the better approach in this case since calling JavaScript functions is much better supported by frameworks like Emscripten and N-API and I can keep my code generic. – frans Aug 19 '19 at 06:08

2 Answers2

7

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

Satyan
  • 1,346
  • 8
  • 15
  • Currently (Node v14 and v16), that works perfectly well, as long as function is declared like that: `global.myFunc = function (args) { (...) }`. However, when I declare it as usual: `function myFunc(args) { (...) }`, upon calling `napi_get_named_property(env, global, "myFunc", &my_func)`, `napi_typeof(my_func)` returns `undefined`. Out of curiosity: is there a way to call normal functions from a module, except passing them as parameters _(which also works just fine)_? – Nik Nov 02 '21 at 23:05
4

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);
}
frans
  • 8,868
  • 11
  • 58
  • 132