Reputation: 80
I have some callback functions in c++ I would want to recreate in javascript after compiling with emscripten.
Anyone knows how to call those using ccall or cwrap?
Thank you!
Upvotes: 0
Views: 1278
Reputation: 2006
The technique I used was to convert a pointer to an unsigned int (technically, a uint32_t), which I then passed to JS. When I was ready to do the callback, I passed the value back to C++, converted it back to a function pointer, and then called the associated function.
A lot of this can be automated; for example you can setup your callback class to have a base class that all of the pointers are converted to before a virtual function is called that redirects to the correct derived class (in my case, I use this to handle parameter and return types).
I'm happy to help you out with some code. I'm working a project called Empirical which will be a header-only library mostly geared toward porting scientific software to the web. It's still under active and early development, BUT you might find some pieces useful.
https://github.com/mercere99/Empirical
emp::JSWrap()
is a function that takes in an std::function object and returns a uint32_t
. You can find its definition here: https://github.com/mercere99/Empirical/blob/master/emtools/JSWrap.h It should correctly handle basic parameter and return types and std::string
, but I'm working to expand that. If you're only grabing some files out of the project, note that JSWrap does include a few others, but not too many.
The other relevant file that you need to worry about is library_emp.js (https://github.com/mercere99/Empirical/blob/master/emtools/library_emp.js), which defines emp.Callback()
, which can be used on the JS side.
To integrate both of these files into your program, you need to:
#include "JSWrap.h"
and #include "init.h"
(possibly with additional path information)--js-library ../../emtools/library_emp.js
emp::Initialize();
on the C++ side.uint32_t fun_id = emp::JSWrap(FunctionToBeWrapped);
It will return a function ID value that you can pass to JS. Alternatively, you can call uint32_t fun_id = emp::JSWrap(FunctionToBeWrapped, "JS_Function_Name");
and it will create a JS function for you with the specified name.emp.Callback(id, parameters)
-or- you can use the provided name if you used one emp.JS_Function_Name(parameters...)
and it will call back the original function. Any return value will be passed back.Let me know if this helps! There is also some documentation at the top of JSWrap.h, and test files at https://github.com/mercere99/Empirical/tree/master/UTests/emtools (including a Makefile, a code file called JSWrap.cc, and an HTML file called JSWrap.html).
Edit: Below is example code that sends the pointer of a function object to JS, and then calls it back from JS.
#include <emscripten.h>
#include <functional>
// A couple of possible callbacks, all with the same signature.
double Times2(double val) { return val * 2; }
double Plus7(double val) { return val + 7; }
// A function callback from JS that takes a callback id and an arg and does the callback.
extern "C" {
double Callback_dd(uint32_t cb_id, double val) {
auto * fun_ptr = reinterpret_cast<std::function<double(double)>*>(cb_id);
return (*fun_ptr)(val);
}
}
int main() {
// Pick the function you want to run
auto fun = std::function<double(double)>(Times2);
// auto fun = std::function<double(double)>(Plus7);
// Convert a function pointer to a uint32_t.
// Note double casting to first convert it to a number and then reduce it to 32-bits.
// Using reintepret_cast would be better, but looked confusing with the double cast.
uint32_t cb_id = (uint32_t) (long long) &fun;
// The following code passed the callback ID to JavaScript. The JS code then uses the
// ID to call back the original function.
EM_ASM_ARGS({
Callback_dd = Module.cwrap('Callback_dd', 'number', ['number']);
var x = 12.5;
alert('Result: fun(' + x + ') = ' + Callback_dd($0, x));
}, cb_id);
}
Note that if you are going to do a callback after main() has ended, you need to make sure that the functions you are calling will persist.
To compile this code, put it in a file (let's call it callback_test.cc) and then from the command line run:
em++ -s EXPORTED_FUNCTIONS="['_Callback_dd', '_main']" -std=c++11 callback_test.cc -o callback_test.html
You should now be able to open callback_test.html in your web browser and it will call from JS whichever C++ function pointer you passed to it.
In this case you need to know the function signature ahead of time, but as I mentioned above you can use a more elaborate callback that can remember the signature.
Upvotes: 2