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"
});