添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

本文讨论如何在编译为 WebAssembly 模块后的 C/C++ 程序和 js 之间进行数据交换。本质上 js WebAssembly 共享相同的线性内存,这意味着 js WebAssembly 可以在同一内存位置读写数据。

js 读取 c/c++ 全局变量

编译后全局变量已经分配好内存,所以可以通过共享线性内存的偏移进行读写。

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
int g_int = 52;
double g_double = 3.1415926;
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_KEEPALIVE
#endif
int* get_int_ptr() {
    return &g_int;
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_KEEPALIVE
#endif
double* get_double_ptr() {
    return &g_double;
#ifdef __cplusplus
#endif

上面的代码定义了两个全局变量,同时定义了两个函数分别取出相应的地址。由于有全局变量, js 需要在示例化模块时分配内存:

const memory = new WebAssembly.Memory({initial: 1});

有个小细节需要注意,内存地址是按字节做基本单位来分配的, js 根据不同的类型数组,取到的内存地址要做不同的换算,比如按 Int32Array 读取共享线性内存时,取到的变量地址要除以4,因为 Int32Array ArrayBuffer 转成了元素占用4个字节的数组。如果用 Uint8Array 则不用做换算:

// 每个元素占1个字节
let buffer = new Uint8Array(res.instance.exports.memory.buffer);
console.log('buffer-value::', buffer[exports.get_int_ptr()]);
// 每个元素占4个字节
let uint32 = new Uint32Array(memory.buffer);
console.log('g_int::', uint32[exports.get_int_ptr() >> 2]);

这里我就用 Int32Array 做示例:

 const memory = new WebAssembly.Memory({initial: 1});
    WebAssembly.instantiateStreaming(fetch("test.wasm"), {
      env: {
        memory: memory,
        segfault: function() {
          // 处理segfault事件
        alignfault: function() {
          // 处理alignfault事件
      wasi_snapshot_preview1: {
        fd_write: function() {
          // 处理fd_write事件
    }).then(res => {
      console.log('res::', res);
      const exports = res.instance.exports
      const memory = exports.memory;
      const HEAP32 = new Int32Array(memory.buffer);
      const HEAPF64 = new Float64Array(memory.buffer);
      const int_ptr = exports.get_int_ptr();
      let int_value = HEAP32[int_ptr >> 2]
      console.log('int_value::', int_value);
      const double_ptr = exports.get_double_ptr();
      let double_value = HEAPF64[double_ptr >> 3];
      console.log('double_value::', double_value);
      console.log('int_value + double_value = ', int_value + double_value);
      // 修改值
      HEAP32[int_ptr >> 2] = 100;
      HEAPF64[double_ptr >> 3] = 3.14;
      int_value = HEAP32[int_ptr >> 2];
      double_value = HEAPF64[double_ptr >> 3];
      console.log('int_value + double_value = ', int_value + double_value);

c/c++ 读取 js 分配的内存

要在 js 分配内存, 那么 emcc 编译代码时不能带上 SIDE_MODULE 配置,编译时需要加上 -s EXPORTED_FUNCTIONS="['_malloc', '_free']"

下面是一段从 js 读取的数组,返回求和的函数:

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
// #include <stdlib.h>
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_KEEPALIVE
#endif
int sum(int* ptr, int length) {
    int total = 0;
    for(int i = 0; i < length; i++){
        total += ptr[i];
    return total;

js通过malloc分配内存时,默认也是按一个字节分组,所以也要做对应的换算,代码如下:

const memory = new WebAssembly.Memory({initial: 1});
        const table = new WebAssembly.Table({
            initial: 0,
            maximum: 0,
            element: 'anyfunc' // 表示可以存储任何函数类型
        WebAssembly.instantiateStreaming(fetch('sum.wasm'), {
            env: {
                memory: memory,
                __table_base: 1024,
                __memory_base: 1024,
                __indirect_function_table: table,
        }).then(res => {
            console.log('res::', res);
            const exports = res.instance.exports;
            const memory = exports.memory;
            const HEAP32 = new Int32Array(memory.buffer);
            const count = 50;
            const ptr = exports.malloc(count * 4);
            console.log('ptr::', ptr);
            for(let i = 0; i < count; i++) {
                HEAP32[(ptr >> 2) + i] = i + 1;
            for(let i = 0; i < count; i++) {
                console.log(HEAP32[(ptr >> 2) + i]);
            console.log('sum::', exports.sum(ptr, count));
            exports.free(ptr);

总的来说,本文为希望在Web环境中利用C/C++的强大功能并启用与js通信的开发者提供了有用的指南。