与 JavaScript 交互
导入和导出 JS 函数
从 Rust
在 JS 主机中使用 wasm 时,从 Rust 端导入和导出函数非常简单:其工作原理与C非常相似。
WebAssembly 模块声明一系列导入,每个都有一个 模块名(module name) 和一个 导入名(import name)。
模块名是一个 extern { ... }
的块,可以使用指定 #[link(wasm_import_module)]
,目前默认为 “env”。
导出只有一个名称。除了任何 extern
函数 WebAssembly 实例的默认线性内存导出为 “内存”。
// import a JS function called `foo` from the module `mod`
#[link(wasm_import_module = "mod")]
extern { fn foo(); }
// export a Rust function called `bar`
#[no_mangle]
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
属性:
#[link_section = "hello"]
pub static SECTION: [u8; 24] = *b"This is a custom section";
这会在 wasm 文件中添加一个名为 hello
的自定义节,变量名 SECTION
是任意的,改变它不会改变行为。
这里的内容是文本字节,但可以是任意数据。
可以在 JS 端使用 WebAssembly.Module.customSections
函数,它接受一个 wasm 模块和 section 名作为参数,并返回一个 ArrayBuffer
。
可以使用相同的名称指定多个 section,在这种情况下,它们都将显示在此数组中。
WebAssembly.compileStreaming(fetch("sections.wasm"))
.then(mod => {
const sections = WebAssembly.Module.customSections(mod, "hello");
const decoder = new TextDecoder();
const text = decoder.decode(sections[0]);
console.log(text); // 这是一个自定义部分
});