將函式匯出至 JS
好的,現在我們已經對 JS 物件及其運作方式有很好的掌握,讓我們來看看 `wasm-bindgen` 的另一個功能:匯出具有比數字更豐富類型的功能。
使用更豐富的類型匯出功能的基本概念是,Wasm 匯出實際上不會被直接呼叫。相反,產生的 `foo.js` 模組將為 Wasm 模組中的所有匯出函式提供墊片。
這裡最有趣的轉換發生在字串上,所以讓我們來看看。
#![allow(unused)] 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)] 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` 函式沒有修改,並且有一個包裝器來呼叫它。這個包裝器將採用指標/長度參數並將其轉換為字串切片,而返回值則被打包成一個指標,然後透過 `__wbindgen_boxed_str_*` 函式返回給 wasm 進行讀取。
所以一般來說,匯出函式涉及到 JS 和 Rust 中的墊片,每一側都會將 Wasm 參數轉換為每種語言的原生類型或從其轉換。`wasm-bindgen` 工具管理著連接所有這些墊片,而 `#[wasm_bindgen]` 巨集也處理了 Rust 墊片。
大多數參數都有一個相對清晰的轉換方式,但如果您有任何疑問,請告訴我!