本文讨论如何在编译为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
通信的开发者提供了有用的指南。