将函数导出到 JS
现在我们已经对 JS 对象及其工作原理有了很好的理解,让我们来看看 `wasm-bindgen` 的另一个功能:导出比仅仅数字更丰富的类型的功能。
导出具有更丰富类型的功能的基本思想是,wasm 导出实际上不会被直接调用。相反,生成的 `foo.js` 模块将为 wasm 模块中的所有导出函数提供垫片。
这里最有趣的转换发生在字符串上,让我们来看看它。
# #![allow(unused_variables)] #fn main() { #[wasm_bindgen] pub fn greet(a: &str) -> String { format!("Hello, {}!", a) } #}
在这里,我们希望定义一个看起来像这样的 ES 模块
// foo.d.ts
export function greet(a: string): string;
为了了解发生了什么,让我们看看生成的垫片
import * as wasm from './foo_bg';
function passStringToWasm(arg) {
const buf = new TextEncoder('utf-8').encode(arg);
const len = buf.length;
const ptr = wasm.__wbindgen_malloc(len, 1);
let array = new Uint8Array(wasm.memory.buffer);
array.set(buf, ptr);
return [ptr, len];
}
function getStringFromWasm(ptr, len) {
const mem = new Uint8Array(wasm.memory.buffer);
const slice = mem.slice(ptr, ptr + len);
const ret = new TextDecoder('utf-8').decode(slice);
return ret;
}
export function greet(arg0) {
const [ptr0, len0] = passStringToWasm(arg0);
try {
const ret = wasm.greet(ptr0, len0);
const ptr = wasm.__wbindgen_boxed_str_ptr(ret);
const len = wasm.__wbindgen_boxed_str_len(ret);
const realRet = getStringFromWasm(ptr, len);
wasm.__wbindgen_boxed_str_free(ret);
return realRet;
} finally {
wasm.__wbindgen_free(ptr0, len0, 1);
}
}
哇,这可真不少!不过如果我们仔细观察,就可以大致了解发生了什么
-
字符串通过两个参数传递给 wasm:指针和长度。现在我们必须将字符串复制到 wasm 堆上,这意味着我们将使用 `TextEncoder` 来进行实际的编码。完成后,我们使用 `wasm-bindgen` 中的内部函数为字符串分配空间,然后在稍后将该指针/长度传递给 wasm。
-
从 wasm 返回字符串有点棘手,因为我们需要返回一个指针/长度对,但 wasm 目前只支持一个返回值(多个返回值 正在标准化)。为了解决这个问题,我们实际上返回一个指向指针/长度对的指针,然后使用函数来访问各个字段。
-
一些清理工作最终发生在 wasm 中。`__wbindgen_boxed_str_free` 函数用于在 `greet` 的返回值被解码到 JS 堆上(使用 `TextDecoder`)后释放它。然后,`__wbindgen_free` 用于释放我们在函数调用完成后为传递字符串参数而分配的空间。
接下来,让我们看看 Rust 方面的内容。在这里,我们将看到一个基本上是缩写或“简化”的版本,意思是这是它编译后的结果
# #![allow(unused_variables)] #fn main() { pub extern "C" fn greet(a: &str) -> String { format!("Hello, {}!", a) } #[export_name = "greet"] pub extern "C" fn __wasm_bindgen_generated_greet( arg0_ptr: *const u8, arg0_len: usize, ) -> *mut String { let arg0 = unsafe { let slice = ::std::slice::from_raw_parts(arg0_ptr, arg0_len); ::std::str::from_utf8_unchecked(slice) }; let _ret = greet(arg0); Box::into_raw(Box::new(_ret)) } #}
在这里,我们可以再次看到我们的 `greet` 函数没有修改,并且有一个包装器来调用它。这个包装器将接收指针/长度参数并将其转换为字符串切片,而返回值将被装箱成一个指针,然后返回到 was 中,以便通过 `__wbindgen_boxed_str_*` 函数进行读取。
因此,一般来说,导出函数涉及在 JS 和 Rust 中都使用垫片,每一方都将 wasm 参数转换为各自语言的本机类型。`wasm-bindgen` 工具负责连接所有这些垫片,而 `#[wasm_bindgen]` 宏负责 Rust 垫片。
大多数参数都有一个相对清晰的转换方式,但如果您有任何问题,请告诉我!