如何从 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 中查看完整示例。
有两种相反的方法:
- 将所有数据元素声明为全局变量,并将辅助函数添加到每个的 return 起始地址。
- 不要使用全局变量,在 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 即可工作。
我正在编写一个小型 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;
}
您需要做的是在 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 中查看完整示例。
有两种相反的方法:
- 将所有数据元素声明为全局变量,并将辅助函数添加到每个的 return 起始地址。
- 不要使用全局变量,在 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 即可工作。