如何从 C/C++ 访问 WebAssembly 线性内存

How to access WebAssembly linear memory from C/C++

我正在编写一个小型 C 程序,旨在在网络浏览器中编译为带有 emcc 和 运行 的 wasm。因为 wasm 导出函数只能接受简单的数值作为参数输入和 return 值,我需要在 JavaScript API 和编译的 WebAssembly 代码之间共享内存,以便访问更复杂的数据类型如字符串或 char 数组。问题是我一辈子都弄不明白如何从我的 C 程序内部访问 WebAssembly linear memory

我的最终目标是能够在我的 C 程序中读取在 JavaScript 中初始化的字符串,然后在网络浏览器的 JavaScript代码。

这是我正在尝试做的一个基本示例:

main.js

const importObject = {
  'env': {
    'memoryBase': 0,
    'tableBase': 0,
    'memory': new WebAssembly.Memory({initial: 256}),
    'table': new WebAssembly.Table({initial: 0, element: 'anyfunc'})
  }
}

// using the fetchAndInstantiate util function from
// https://github.com/mdn/webassembly-examples/blob/master/wasm-utils.js
fetchAndInstantiate('example.wasm', importObject).then(instance => {

      // call the compiled webassembly main function
      instance.exports._main()
      console.log(importObject.env.memory)
})

example.c

int main() {
    // somehow access importObject.env.memory 
    // so that I can write a string to it
    return 0;
}

让我走到了那里,但是,我仍然不明白如何从我的 C 代码中的 WebAssembly 内存缓冲区 read/write。

您需要做的是在 WebAssembly 模块中传达 C 和 JavaScript 代码都读取/写入的位置。

这是一个向数组中的每个元素添加一个数字的简单示例。这是 C 代码:

const int SIZE = 10;
int data[SIZE];

void add(int value) { 
  for (int i=0; i<SIZE; i++) {
    data[i] = data[i] + value;
  }
}

int* getData() {
  return &data[0];
}

上面代码中重要的是 int* getData() 函数,它 return 是对 data 数组开头的引用。当编译为 WebAssembly 时,这将 return 一个整数,它是 data 数组在模块线性内存中的位置。

这是一个如何使用它的例子:

var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, wasmImports);

// obtain the offset to the array
var offset = wasmInstance.exports.getData();

// create a view on the memory that points to this array
var linearMemory = new Uint32Array(wasmInstance.exports.memory.buffer, offset, 10);

// populate with some data
for (var i = 0; i < linearMemory.length; i++) {
  linearMemory[i] = i;
}

// mutate the array within the WebAssembly module
wasmInstance.exports.add(10);

// log the results
for (var i = 0; i < linearMemory.length; i++) {
  log(linearMemory[i]);
}

您可以在此 WASM fiddle 中查看完整示例。

有两种相反的方法:

  1. 将所有数据元素声明为全局变量,并将辅助函数添加到每个的 return 起始地址。
  2. 不要使用全局变量,在 JS 中分配所需的内存,计算偏移量并将这些偏移量传递给调用的函数。在这种情况下,可用内存将从 0(零)开始。

(1) 适用于简单的事情。 (2) 适用于你的数据量未知的情况。

WASM 对象有一个 属性(我还没有看到记录),它存储指向每个变量和数组开头的指针。

鉴于此 C:

int myArray[100];

int main(){
    // Fill the array with data so we can see it
    for(int i = 0; i < 100; i ++){
        myArray[i] = 100 - i;
    }
    
    return 1;
}

您可以像这样访问数组的完整数据:

// Load WASM
fetch('script.wasm',{headers:{'Content-Type':'application/wasm'}})
.then(response => response.arrayBuffer())
.then(bits => WebAssembly.instantiate(bits))
.then(obj => {
    // We pull back 
    var sharedArray = new Int32Array(
        obj.instance.exports.memory.buffer, // WASM's memory
        obj.instance.exports.myArray.value, // myArray's pointer
        100                                 // The array's length
    );

    obj.instance.exports.main();

    console.log(sharedArray);
});

已在 Chromium 和 Firefox 中测试。您不必使用 Emscripten 即可工作。