JavaScript Interoperation

載入和匯出 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 的方法之一。

  • 設定一個明確的 JS 物件「堆」,然後加上「位址」。這讓 WASM 程式碼能間接引用 JS 物件(使用整數),並透過呼叫已匯入的 JS 函式對這些物件進行操作。

很幸運地,這個互操作過程非常適合透過一個通用的「bindgen」型式框架處理:wasm-bindgen。這個框架自動提供撰寫慣用的 Rust 函式簽章,對應到慣用的 JS 函式。

自訂區塊

自訂區塊可將具名的隨意資料嵌入到 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";
#}

這會在 WASM 檔案中加入一個名為 hello 的自訂區塊,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"
});