當我們在一個Web應用中使用WebAssembly,最終的目的要麼是執行wasm模塊的入口程序(通過start指令指定的函數),要麼是調用其導出的函數,這一切的前提需要創建一個通過WebAssembly.Instance對象表示的wasm模塊實例(源代碼)。
一、wasm模塊實例化總體流程
二、利用WebAssembly.Module創建實例
三、通過字節內容創建創建實例
四、利用XMLHttpRequest加載wasm模塊
五、極簡編程方式
一、wasm模塊實例化總體流程
雖然編程模式多種多樣,但是wasm模塊的實例化總體採用如下的流程:
- 步驟一:下載wasm模塊文件;
- 步驟二:解析文件並創建通過WebAssembly.Module類型表示的wasm模塊;
- 步驟三:根據wasm模塊,結合提供的導入對象,創建通過WebAssembly.Instance類型表示的模塊實例。
二、利用WebAssembly.Module創建實例
我們照例通過一個簡單的實例來演示針對wasm模塊加載和模塊實例創建的各種編程模式。我們首先利用WebAssembly Text Format(WAT)形式定義如下一個wasm程序,定義的文件名爲app.wat。如代碼所示,我們定義了一個用於輸出指定浮點數(i64)絕對值的導出函數absolute。絕對值通過f64.abs指令計算,具體得輸出則通過導入的print函數完成。
(module (func $print (import "imports" "print") (param $op f64) (param $result f64)) (func (export "absolute") (param $op f64) (local.get $op) (f64.abs (local.get $op)) (call $print) ) )
我們通過指定wat2wasm (源代碼壓縮包種提供了對應的.exe)命令(wat2wasm app.wat –o app.wasm)編譯app.wat並生成app.wasm後,定義如下這個index.html頁面,作爲宿主程序的JavaScript腳本完全按照上面所示的步驟完成了針對wasm模塊實例的創建。
<html> <head></head> <body> <div id="container"></div> <script> var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; fetch("app.wasm") .then((response) => response.arrayBuffer()) .then(bytes => { var module = new WebAssembly.Module(bytes); var instance = new WebAssembly.Instance(module, {"imports":{"print": print}}); instance.exports.absolute(-3.14); }) </script> </body> </html>
具體來說,我們調用fetch函數將app.wasm文件下載下來後,我們將獲得的字節內容作爲參數調用構建函數創建了一個WebAssembly.Module對象。然後將這個Module對象和創建的導入對象({"imports":{"print": print}})作爲參數調用構造函數創建了一個WebAssembly.Instance對象,該對象正是我們需要的wasm模塊實例。我們從模塊實例中提取並執行導出的absolute函數。導入的print函數會將絕對值計算表達式以如下的形式輸出到頁面中。
除了調用構造函數以同步(阻塞)的方式根據WebAssembly.Module對象創建WebAssembly.Instance對象外,我們還可以調用WebAssembly.instantiate靜態方法以異步的方式“激活”wasm模塊實例,它返回一個Promise<WebAssembly.Instance>對象。
var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; fetch("app.wasm") .then((response) => response.arrayBuffer()) .then(bytes => { var module = new WebAssembly.Module(bytes); return WebAssembly.instantiate(module, { "imports": { "print": print } }); }) .then(instance => instance.exports.absolute(-3.14));
三、通過字節內容創建創建實例
靜態方法WebAssembly.instantiate還提供了另一個重載,我們可以直接指定下載wasm模塊文件得到的字節內容作爲參數。這個重載返回一個Promise<WebAssembly.WebAssemblyInstantiatedSource>對象,WebAssemblyInstantiatedSource對象的instance屬性返回的正是我們需要的wasm模塊實例。
var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; fetch("app.wasm") .then((response) => response.arrayBuffer()) .then(bytes => WebAssembly.instantiate(bytes, {"imports":{"print": print}})) .then(result =>result.instance.exports.absolute(-3.14));
四、利用XMLHttpRequest加載wasm模塊
fetch函數是我們推薦的用於下載wasm模塊文件的方式,不過我們一定義要使用傳統的XMLHttpRequest對象也未嘗不可。上面的三種激活wasm模塊實例的方式可以採用如下的形式來實現。
var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; const request = new XMLHttpRequest(); request.open("GET", "app.wasm"); request.responseType = "arraybuffer"; request.send(); request.onload = () => { var bytes = request.response; var module = new WebAssembly.Module(bytes); var instance = new WebAssembly.Instance(module, {"imports":{"print": print}}); instance.exports.absolute(-3.14); };
上面演示的利用創建的WebAssembly.Module對象和導入對象調用構造函數創建WebAssembly.Instance的同步形式。下面則是將二者作爲參數調用靜態方式WebAssembly.instantiate以異步方式激活wasm模塊實例的方式。
var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; const request = new XMLHttpRequest(); request.open("GET", "app.wasm"); request.responseType = "arraybuffer"; request.send(); request.onload = () => { var bytes = request.response; WebAssembly .instantiate(request.response, {"imports":{"print": print}}) .then(result => result.instance.exports.absolute(-3.14)); };
下面演示WebAssembly.instantiate靜態方法的另一個重載。
var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; const request = new XMLHttpRequest(); request.open("GET", "app.wasm"); request.responseType = "arraybuffer"; request.send(); request.onload = () => { var bytes = request.response; WebAssembly .instantiate(request.response, {"imports":{"print": print}}) .then(result => result.instance.exports.absolute(-3.14)); };
五、極簡編程方式
其實我們有“一步到位”的方式,那就是按照如下的形式執行靜態方法WebAssembly.instantiateStreaming。該方法的第一個參數用於提供下載.wasm模塊文件的PromiseLike<Response>對象,第二個參數則用於指定導入對象。該方法同樣返回一個Promise<WebAssembly.WebAssemblyInstantiatedSource>對象,WebAssemblyInstantiatedSource的instance屬性返回的正是我們所需的wasm模塊實例。
var print = (op, result) => document.getElementById("container").innerText = `abs(${op}) = ${result}`; WebAssembly .instantiateStreaming(fetch("app.wasm"), {"imports":{"print": print}}) .then(result => result.instance.exports.absolute(-3.14))