JavaScript 交互操作
导入和导出 JS 函数
从 Rust 端
在 JS 宿主环境中使用 wasm 时,从 Rust 端导入和导出函数非常简单:它与 C 非常相似。
WebAssembly 模块声明一系列导入,每个导入都有一个模块名称和一个导入名称。extern { ... }
块的模块名称可以使用 #[link(wasm_import_module)]
指定,目前默认值为 "env"。
导出只有一个名称。除了任何 extern
函数之外,WebAssembly 实例的默认线性内存也会被导出为 "memory"。
# #![allow(unused_variables)] #fn main() { // 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 模块。它必须使用线性内存进行实例化,并且必须有一组与预期导入匹配的 JS 函数。实例化的详细信息可以在 MDN 上找到。
生成的 ES6 模块将包含从 Rust 导出的所有函数,现在可以作为 JS 函数使用。
这里 是一个非常简单的示例,展示了整个设置的运行过程。
超越数值
在 JS 中使用 wasm 时,wasm 模块的内存和 JS 内存之间存在明显的分割。
-
每个 wasm 模块都有一个线性内存(在本文件开头描述),它在实例化期间初始化。JS 代码可以自由地读写此内存。
-
相反,wasm 代码无法直接访问 JS 对象。
因此,复杂的交互操作主要通过两种方式实现:
-
将二进制数据复制进或复制出 wasm 内存。例如,这是一种向 Rust 端提供拥有权的
String
的方法。 -
设置一个显式的 JS 对象“堆”,然后为这些对象分配“地址”。这允许 wasm 代码间接地(使用整数)引用 JS 对象,并通过调用导入的 JS 函数来操作这些对象。
幸运的是,这种交互操作故事非常适合通过通用的“bindgen”风格框架来处理:wasm-bindgen。该框架使编写映射到 JS 函数的惯用 Rust 函数签名成为可能,并且可以自动完成。
自定义段
自定义段允许将命名任意数据嵌入到 wasm 模块中。段数据在编译时设置,并直接从 wasm 模块读取,它不能在运行时修改。
在 Rust 中,自定义段是使用 #[link_section]
属性公开的静态数组 ([T; size]
)
# #![allow(unused_variables)] #fn main() { #[link_section = "hello"] pub static SECTION: [u8; 24] = *b"This is a custom section"; #}
这将一个名为 hello
的自定义段添加到 wasm 文件中,Rust 变量名 SECTION
是任意的,更改它不会改变行为。这里的内容是文本字节,但可以是任何任意数据。
可以使用 JS 端的 WebAssembly.Module.customSections
函数读取自定义段,它接受 wasm 模块和段名称作为参数,并返回一个 ArrayBuffer
数组。可以使用相同的名称指定多个段,在这种情况下,它们都将出现在此数组中。
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); // -> "This is a custom section"
});