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 的传递类似。

posted @ 2022-02-12 22:58  buzzjan  阅读(700)  评论(0编辑  收藏  举报