Is there any way to get access to function pointers living inside a WebAssembly module?
For example, given the following "module" compiled to WebAssembly:
extern void set_callback(void (*callback)(void *arg), void *arg);
static void callback(void *arg)
{
/* ... */
}
int main() {
set_callback(&callback, 0);
return 0;
}
Can an implementation of do_callback
in JavaScript invoke the callback without having to rely on an intermediary C function export to do the actual function call?
var instance = new WebAssembly.Instance(module, {
memory: /* ... */
env: {
set_callback: function set_callback(callbackptr, argptr) {
// We only got the pointer, is there any
},
},
});
By intermediary function export, I mean that I could add an internal function with public visibility.
do_callback(void (*callback)(void *arg), void *arg)
{
callback();
}
Then the JavaScript set_callback
function can call the function pointer via the delegate do_callback
function.
function set_callback(callbackptr, argptr) {
instance.exports.do_callback(callbackptr, argptr);
}
But, it's preferable to do this without having to go through that explicit indirection, is it possible, with function tables maybe?
You can modify a WebAssembly module so it can talk to the JavaScript code directly. External functions can be defined in your C or C++ code using the extern keyword. You can add your own JavaScript code to Emscripten's generated JavaScript file by adding it to the LibraryManager.
By itself, WebAssembly cannot currently directly access the DOM; it can only call JavaScript, passing in integer and floating point primitive data types. Thus, to access any Web API, WebAssembly needs to call out to JavaScript, which then makes the Web API call.
Module.ccall() calls a compiled C function from JavaScript and returns the result of that function. The function signature for Module.ccall() is as follows: ccall(ident, returnType, argTypes, args, opts) You must specify a type name for the returnType and argTypes parameters.
To use WebAssembly in JavaScript, you first need to pull your module into memory before compilation/instantiation. This article provides a reference for the different mechanisms that can be used to fetch WebAssembly bytecode, as well as how to compile/instantiate then run it.
You can invoke function pointers from Javascript.
Function pointers are stored in a Table. When a function pointer is passed to Javascript you are receiving the integer index into the table for that function pointer. Pass that index to Table.prototype.get()
and you can call the function.
...
set_callback: function set_callback(callbackptr, argptr) {
tbl.get(callbackptr)(argptr);
},
...
You can read more about this on this MDN page under the Tables section: https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API#Tables
Edit: Here is the minimal example I used to test this.
The first file is fptr.c
compiled with emcc fptr.c -Os -s WASM=1 -s SIDE_MODULE=1 -o fptr.wasm
typedef int (*fptr_type)(void);
extern void pass_fptr_to_js(fptr_type fptr);
static int callback_0(void)
{
return 26;
}
static int callback_1(void)
{
return 42;
}
void run_test()
{
pass_fptr_to_js(callback_0);
pass_fptr_to_js(callback_1);
}
And here is fptr.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebAssembly Experiment</title>
</head>
<body>
<h3>Check the console.</h3>
<script type="text/javascript">
fetch('fptr.wasm').then(function(response) {
response.arrayBuffer().then(function(buffer) {
WebAssembly.compile(buffer).then(function(module) {
var imports = {};
imports.env = {};
imports.env.memoryBase = 0;
imports.env.memory = new WebAssembly.Memory({ initial: 256 });
imports.env.tableBase = 0;
imports.env.table = new WebAssembly.Table({ initial: 4, element: 'anyfunc' });
imports.env["abort"] = function() {
console.error("ABORT");
};
imports.env["_pass_fptr_to_js"] = function(fptr) {
console.log("table index: " + fptr + ", return value: " + imports.env.table.get(fptr)());
};
WebAssembly.instantiate(module, imports).then(function(instance) {
instance.exports["__post_instantiate"]();
instance.exports["_run_test"]();
});
});
});
});
</script>
</body>
</html>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With