Passing JS function to Emscripten-generated code

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?

Upvotes: 21

Views: 11038

Answers (5)

Tarun Gehlaut
Tarun Gehlaut

Reputation: 1342

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.

Upvotes: 18

Boris
Boris

Reputation: 769

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".

Upvotes: 1

Infinite Internship
Infinite Internship

Reputation: 243

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.

Upvotes: 1

Daniel Baulig
Daniel Baulig

Reputation: 10989

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)

Upvotes: 18

abergmeier
abergmeier

Reputation: 14052

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.

Upvotes: 11

Related Questions