Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling a C style function pointer in a WebAssembly from JavaScript

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?

like image 499
Casper Beyer Avatar asked Jul 29 '17 09:07

Casper Beyer


People also ask

Can WebAssembly call JavaScript?

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.

Will WebAssembly access Dom?

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.

What is module Ccall?

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.

How do I run a WebAssembly code?

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.


1 Answers

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>
like image 95
Ghillie Avatar answered Sep 28 '22 00:09

Ghillie