21

I have a piece of C++ code converted to JavaScript via Emscripten. I would like the converted C++ code to call back to the JavaScript code that calls it. Something like:

JavaScript:

function callback(message) {
    alert(message);
}

ccall("my_c_function", ..., callback);

C++:

void my_c_function(whatever_type_t *callback) {
    callback("Hello World!");
}

Is this possible somehow?

5 Answers 5

18

I believe the accepted answer is a bit outdated.

Please refer to this bullet point in the "Interacting with code" emscripten tutorial.

E.g. C:

void invoke_function_pointer(void(*f)(void)) {
  (*f)();
}

JS:

var pointer = Runtime.addFunction(function() { 
  console.log('I was called from C world!'); 
});
Module.ccall('invoke_function_pointer', 'number', ['number'], [pointer]);
Runtime.removeFunction(pointer);

This way the C-code does not need to be aware of that it is transpiled to JS and any bridges required can purely be controlled from JS.

(code hacked into message composer; may contain errors)

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

1 Comment

an important thing is that the number of function pointers which are simultaneous valid is fixed and specified by emcc ... -s RESERVED_FUNCTION_POINTERS=20 ...
18

There is a new way of achieving your requirement which is via embind.

Consider the following piece of C++ code.

#include <emscripten/bind.h>
using namespace emscripten;

void cbTest(emscripten::val cb)
{
    cb();
}

EMSCRIPTEN_BINDINGS(my_module) {
    function("cbTest", &cbTest);
}

The cbTest C++ function takes in a emscripten::val. This can be an object of any kind. For us this is a function object. This is how you will call it from JS

var cbFunc = function() {
    console.log("Hi, this is a cb");
}

Module.cbTest(cbFunc);

P.S This api is still under construction.

1 Comment

This worked for me, and does appear to be the modern way of implementing this
11

A thing that is frequently done in Emscripten is to map strong types to simple ones.

JS:

function callback(message) {
    alert(message);
}

var func_map = {
    0: callback
};

// C/C++ functions get a _ prefix added
function _invoke_callback(callback_id, text_ptr) {
    func_map[callback_id](Pointer_stringify(text_ptr));
}

ccall("my_c_function", ..., 0);

C++:

// In C/C++ you only need to declare the func signature and
// make sure C is used to prevent name mangling
extern "C" void invoke_callback(int callback_id, const char* text);

void my_c_function(int callback_id) {
    invoke_callback( callback_id, "Hello World!" );
}

And of course, you can add some glue code, so this gets very seamless.

1 Comment

can you look at this related question: stackoverflow.com/questions/33673575/…
1

I needed to write something very similar to what is described in the question. My code ended up looking like this:

C:

void call(void (*back)(char*)){
    back("Hello!");
}

JS:

function back(text){
    alert(Pointer_stringify(text));
}
var pointer = Runtime.addFunction(back);
var call = Module.cwrap('call', 'void', ['pointer']);
call(pointer);
Runtime.removeFunction(pointer);

Note that the pointer returned to the callback has to be dereferenced with Pointer_stringify.

You can find example code like this on GitHub.

2 Comments

The link provides close to no additional info.
Use UTF8ToString instead of Pointer_stringify as the latter was removed.
1

Here's what I have gathered from several posts and by looking at Emscripten bundled code:

In C++:

#include <iostream>
#include <functional>

extern "C" {
  void registerCallback(void(*back)(const char*));
  void triggerCallback(char* message); // for invoking it from JS, just for this example
}

// global
std::function<void(const char*)> gCallback;

void registerCallback(void(*back)(const char*)){
    gCallback = back;
}

void triggerCallback(char* message){
  if (gCallback) {
    gCallback(message);
  } else {
    std::cerr << "Cannot pass '"<< message <<"' to undefined callback\n";
  }
}

An important thing, which was missing in other posts, is to compile C++ with RESERVED_FUNCTION_POINTERS=... flag, e.g.:

em++ -std=c++11 -s RESERVED_FUNCTION_POINTERS=20 source.cpp -s EXPORTED_FUNCTIONS="['_registerCallback','_triggerCallback']" -o try.html

After loading try.html into a browser, you can execute the following JS code in its console:

// Register a callback function
function callback(text){ alert("In JS: "+Pointer_stringify(text)); }
var cb = Runtime.addFunction(callback);
_registerCallback(cb);

// Invoke it with some "C string"
var jsStr = "XOXOXO";
var cStr = allocate(intArrayFromString(jsStr), 'i8', ALLOC_NORMAL)
_triggerCallback(cStr);

// Free Emscripten heap and release the function pointer
_free(cStr);
Runtime.removeFunction(cb);

You should see an alert with "In JS: XOXOXO".

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.