WebAssembly 和 JavaScript 的 String 类型数据交换
前言
我们知道 WASM 和 HOST 环境(Browser)是通过线型内存共享空间的,所以本质上交换数据就是在这段共享内存中存和取数据,以及对数据如何编码。
这里又涉及到对应的 WASM 产物是如何在内存中存放数据的,例如下面的案例是在内存空间直接寻址提取和存放数据的
不同的数据结构(Array,Object,Struct)有自己的内存空间定义,所以我只针对 String 做 Case
字符串类型
wasm 到 js
index.c: 其中定义 get_str_from_wasm 方法,返回字符串指针
// wasm string pointer
char * get_str_from_wasm(void) {
return (char *)"Hello, From WASM";
}
Makefile: 编译
main:
emcc \
index.c \
-s ERROR_ON_UNDEFINED_SYMBOLS=0 \
-s EXPORTED_FUNCTIONS="[_malloc,_free,_get_str_from_js]" \
--no-entry \
-o \
./index.wasm
clean:
rm -rfv *.wasm
index.html: 加载 wasm,通过1byte(8Bit)去取内存数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shared</title>
</head>
<script type="text/javascript">
(async function () {
function AsciiToString(ptr, heapu8) {
let str = '';
while (1) {
let ch = heapu8[ptr++];
if (!ch) return str;
str += String.fromCharCode(ch);
}
}
const resp = await fetch(`./index.wasm?t=${Date.now()}`);
const bytes = await resp.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: {},
});
// 结构化数据
const HEAP8 = new Int8Array(instance.exports.memory.buffer);
// get string from wasm
const wasm_str_ptr = instance.exports.get_str_from_wasm();
const wasm_str = AsciiToString(wasm_str_ptr, HEAP8);
console.log(wasm_str); //Hello, From WASM
})().catch(err => console.error(err));
</script>
</body>
</html>
我只用 ascii 码的形式去编码数据和解码数据,其实如果涉及到 UTF-8 类型数据,需要用 Int32Array 去取数据解码。
js 到 wasm
index.c: 定义 set_str_from_js 来设置字符串,get_str_from_js 来去字符串指针
char * js_str;
// set string pointer
void set_str_from_js(char * str) {
js_str = str;
}
// return string pointer
char * get_str_from_js() {
return js_str;
}
Makefile
main:
emcc \
index.c \
-s ERROR_ON_UNDEFINED_SYMBOLS=0 \
-s EXPORTED_FUNCTIONS="[_malloc,_free,_get_str_from_wasm,_set_str_from_js]" \
--no-entry \
-o \
./index.wasm
clean:
rm -rfv *.wasm
index.html 因为
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shared</title>
</head>
<script type="text/javascript">
(async function () {
function AsciiToString(ptr, heapu8) {
let str = '';
while (1) {
let ch = heapu8[ptr++];
if (!ch) return str;
str += String.fromCharCode(ch);
}
}
const resp = await fetch(`./index.wasm?t=${Date.now()}`);
const bytes = await resp.arrayBuffer();
const { instance } = await WebAssembly.instantiate(bytes, {
env: {},
});
const str = "Hello, From JS";
const str_ptr = instance.exports.malloc(str.length);
const HEAP8 = new Int8Array(instance.exports.memory.buffer);
// 1 byte 1 byte 的形式去内存中设置值,直接修改 wasm 内存数据
str.split('').forEach((char, index) => {
HEAP8[(str_ptr + index) >> 0] = char.charCodeAt(0);
});
// 传递指针
instance.exports.set_str_from_js(str_ptr);
// 获取值
const str_ptr_from_wasm = instance.exports.get_str_from_js();
const str_from_wasm = AsciiToString(str_ptr_from_wasm, HEAP8);
console.log(str_from_wasm); // "Hello, From JS"
})().catch(err => console.error(err));
</script>
</body>
</html>
可以看出,string 是不能直接通过 API 形式传递,需要开辟内存空间,往对应的内存空间写数据,再把 pointer 传入到 wasm 方法去。
其他类型数据
其他高级类型数据和对应的存储结构有关,可以通过高阶的 wrap 形式来做传递,本质上和 String 的传递类似。