10

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

5
  • Are you ok with setting it one time? like my_napi_module.callback_func = xyz? Commented Aug 12, 2019 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 :) Commented Aug 12, 2019 at 14:32
  • 1
    Look at these two threads then stackoverflow.com/questions/29165155/… and stackoverflow.com/questions/46559457/…. 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 Commented Aug 12, 2019 at 14:38
  • any update on the links I posted? Commented Aug 17, 2019 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. Commented Aug 19, 2019 at 6:08

2 Answers 2

7
+50

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

Sign up to request clarification or add additional context in comments.

1 Comment

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)?
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);
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.