将函数导出到 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` 函数未被修改,并有一个包装器来调用它。此包装器将获取指针/长度参数并将其转换为字符串切片,而返回值将被装箱为仅一个指针,然后返回给 wasm 以供通过 `__wbindgen_boxed_str_*` 函数读取。

因此,通常导出函数涉及到 JS 和 Rust 中的垫片,每一侧都将其转换为 Wasm 参数或从 Wasm 参数转换为每种语言的本机类型。`wasm-bindgen` 工具管理连接所有这些垫片,而 `#[wasm_bindgen]` 宏也负责处理 Rust 垫片。

大多数参数都有相对清晰的转换方法,但如果您有任何问题,请随时告诉我!