与 JavaScript 交互

导入和导出 JS 函数

从 Rust

在 JS 主机中使用 wasm 时,从 Rust 端导入和导出函数非常简单:其工作原理与C非常相似。

WebAssembly 模块声明一系列导入,每个都有一个 模块名(module name) 和一个 导入名(import name)。 模块名是一个 extern { ... } 的块,可以使用指定 #[link(wasm_import_module)],目前默认为 “env”。

导出只有一个名称。除了任何 extern 函数 WebAssembly 实例的默认线性内存导出为 “内存”。

  1. // import a JS function called `foo` from the module `mod`
  2. #[link(wasm_import_module = "mod")]
  3. extern { fn foo(); }
  4. // export a Rust function called `bar`
  5. #[no_mangle]
  6. pub extern fn bar() { /* ... */ }

由于 wasm 的值类型有限,这些函数必须只对基元数字类型进行操作。

从 JS

在 JS 中,wasm 二进制文件变成 ES6 module。 它必须用线性内存实例化,并且有一组与预期导入匹配的 JS 函数。 实例化的详细信息可以在 MDN 上找到。

生成的 ES6 module 将包含从 Rust 导出的所有函数,现在可以作为 JS 函数使用。

这里 是一个非常简单的例子,说明了整个设置的作用。

超越数字

在 JS 中使用 wasm 时,wasm 模块的内存和 JS 内存之间存在明显的分歧:

  • 每个 wasm 模块都有一个线性内存(如本文顶部所述),在实例化期间初始化。JS代码可以自由地读写这个内存。
  • 相比之下,wasm 代码没有对 JS 对象的直接访问。

因此,复杂的互操作主要以两种方式发生:

  • 将二进制数据复制或输出到 wasm 内存。 例如,这是一种 String 提供所有权的到 Rust 的方式
  • 设置 JS 对象的显式“堆”,然后给这些对象指定“地址”。这允许 wasm 代码间接引用 JS 对象(使用整数),并通过调用导入的 JS 函数对这些对象进行操作。

幸运的是,这个交互是非常适合通过 “bindgen” 的式框架进行处理:wasm-bindgen 。 该框架使编写自动映射到惯用 JS 函数的惯用 Rust 函数签名成为可能。

自定义节

自定义节允许将命名的任意数据嵌入到 wasm 模块中。 节数据是在编译时设置的,直接从 wasm 模块读取,不能在运行时修改。

在 Rust 中,自定义部分是静态数组([T; size])暴露在 #[link_section 属性:

  1. #[link_section = "hello"]
  2. pub static SECTION: [u8; 24] = *b"This is a custom section";

这会在 wasm 文件中添加一个名为 hello 的自定义节,变量名 SECTION 是任意的,改变它不会改变行为。 这里的内容是文本字节,但可以是任意数据。

可以在 JS 端使用 WebAssembly.Module.customSections 函数,它接受一个 wasm 模块和 section 名作为参数,并返回一个 ArrayBuffer。 可以使用相同的名称指定多个 section,在这种情况下,它们都将显示在此数组中。

  1. WebAssembly.compileStreaming(fetch("sections.wasm"))
  2. .then(mod => {
  3. const sections = WebAssembly.Module.customSections(mod, "hello");
  4. const decoder = new TextDecoder();
  5. const text = decoder.decode(sections[0]);
  6. console.log(text); // 这是一个自定义部分
  7. });