从JavaScript调用WebAssembly中的C样式函数指针

4szc88ey  于 2023-01-11  发布在  Java
关注(0)|答案(3)|浏览(116)

有没有办法访问WebAssembly模块中的函数指针?
例如,假定以下“模块”编译为WebAssembly:

extern void set_callback(void (*callback)(void *arg), void *arg);

static void callback(void *arg)
{
  /* ... */
}

int main() {
  set_callback(&callback, 0);
  return 0;
}

JavaScript中do_callback的实现是否可以调用回调,而不必依赖中间的C函数导出来执行实际的函数调用?

var instance = new WebAssembly.Instance(module, {
  memory: /* ... */
  env: {
    set_callback: function set_callback(callbackptr, argptr) {
      // We only got the pointer, is there any  
    },
  },
});

通过中间函数导出,我的意思是我可以添加一个具有公共可见性的内部函数。

do_callback(void (*callback)(void *arg), void *arg)
{
  callback();
}

然后JavaScript set_callback函数可以通过委托do_callback函数调用函数指针。

function set_callback(callbackptr, argptr) {
  instance.exports.do_callback(callbackptr, argptr);
}

但是,最好不要通过显式的间接方式来完成,有可能吗,也许用函数表?

g6ll5ycj

g6ll5ycj1#

您可以从Javascript调用函数指针。
函数指针存储在表中。当函数指针传递到Javascript时,您将接收到该函数指针在表中的整数索引。将该索引传递到Table.prototype.get(),您就可以调用该函数。

...

set_callback: function set_callback(callbackptr, argptr) {
  tbl.get(callbackptr)(argptr);
},

...

您可以在MDN页面的表格部分阅读更多信息:https://developer.mozilla.org/en-US/docs/WebAssembly/Using_the_JavaScript_API#Tables

**编辑:**下面是我用来测试这一点的最小示例。

第一个文件是使用emcc fptr.c -Os -s WASM=1 -s SIDE_MODULE=1 -o fptr.wasm编译的fptr.c

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);
}

这里是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>
ha5z0ras

ha5z0ras2#

出于某种原因,表格方法对我不起作用,所以我反其道而行之:我用源语言(D)实现了一个函数表。
这样,我可以通过传递句柄到javascript来使它工作:

__gshared ubyte* function(ubyte* args)[] _annonymousFunctionTable;
extern(C) size_t _getFuncAddress(ubyte* fn);
export extern(C) ubyte* __callDFunction(size_t addr, ubyte* args)
{
return _annonymousFunctionTable[addr](args);
}

这里我没有直接调用_getFuncAddress,因为还有另一种方法可以发送我为D编写的函数,用于参数计数和类型检查。
这样,我就可以调用任何函数,只要有多少参数,而不依赖于wasm的实现。

6ie5vjzr

6ie5vjzr3#

当时的问题是,Clang基本上没有实现call_indirect,并且在代码生成期间实际上没有用任何东西填充函数表。
已在LLVM的当前版本中得到解决。

相关问题