利用WebAssembly的導入導出功能可以靈活地實現宿主JavaScript程序與加載的單個wasm模塊之間的交互,那麼如何在宿主程序與多個wasm之間傳遞和共享數據呢?這就需要使用到Global這個重要的對象了。
一、數值類型全局變量
二、將JavaScript函數設置爲全局變量
三、利用全局變量處理字符串
一、數值類型全局變量
Global全局變量支持多種值類型,包括數組(i32/i64和f32/f64)、向量和引用類型(externref和funcref)。下面的實例利用Global提供了全局計數的功能。在WebAssembly Text Format (WAT)文件app.wat中,我們從宿主JavaScript應用中導入了一個i32類型的可讀寫(mut表示可以修改)的全局變量,導入路徑爲“imports.counter”,我們將其命名爲$counter。在用於自增的導出函數increment中,我們通過執行global.get指令讀取全局變量的值,並將其加1之後,執行global.set指令對全局變量重新賦值。
(module (global $counter (import "imports" "counter") (mut i32)) (func (export "increment") (i32.add (global.get $counter) (i32.const 1)) (global.set $counter) ) )
在index.html文件中,我們在頁面中添加了一個“Increment”按鈕,並利用一個<span>顯式計算器當前的值。JavaScript腳本通過調用WebAssembly.Global構造函數將代表全局變量的Global對象創建出來後,調用WebAssembly.instantiateStreaming加載app.wat編譯生成的app.wasm模塊文件,並將此Global對象包含在導入對象中。
<html> <head></head> <body> <span id="counter">0</span> <button id="btnInc">Increment</button> <script> const globalCounter = new WebAssembly.Global({ value: "i32", mutable: true }, 0); WebAssembly .instantiateStreaming(fetch("app.wasm"), {"imports":{"counter":globalCounter}}) .then(results => { document.getElementById("btnInc").onclick = ()=>{ results.instance.exports.increment(); document.getElementById("counter").innerText = globalCounter.value; }; }); </script> </body> </html>
wasm模塊充成功導入後,我們註冊了按鈕的click事件,使之在調用導出的increment函數後,重新刷新計數器的值。如下圖所示,針對“Increment”的每次點擊都將計數器加1(源代碼)。
二、將JavaScript函數設置爲全局變量
除了四種數值類型,Global還支持兩種引用類型externref和funcref,利用externref可以將宿主應用提供的任意JavaScript對象作爲全局變量,下面的實例演示利用這種方式實現了與類似的功能。如下面的代碼片段所示,新的app.wat導入了一個類型爲externref的全局變量,對應着數組應用提供的一個用來對全局計數自增的Javascript函數。
(module (func $apply (import "imports" "apply") (param externref)) (global $increment (import "imports" "increment") externref) (func $main (call $apply (global.get $increment)) ) (start $main) )
由於JavaScript函數的引用在.wasm模塊中並不能直接執行,所以我們不得不導入一個apply函數“回傳”到宿主應用中執行。我們修改的應用用來統計導入的wasm模塊的數量,所以我們在入口函數$main中利用apply調用了全局變量$increment引用的函數。
在index.html,我們在頁面中添加了一個“Load”按鈕來加載app.wat編譯生成的app.wasm模塊。JavaScript腳本利用counter變量表示加載的wasm模塊數量,並通過調用WebAssembly.Global構造函數創建了rexternref類型的全局變量,其值爲一個對counter自增的函數。
<html> <head></head> <body> <p>There are totally <span id="counter" style= "color: read”>0</span> wasm modules loaded. </p> <button id="btnLoad">Load</button> <script> var counter = 0; const globalIncrement = new WebAssembly.Global({ value: "externref"}, ()=>counter++); var apply = func => func(); document.getElementById("btnLoad").onclick = ()=>{ WebAssembly .instantiateStreaming(fetch("app.wasm"), {"imports":{"increment":globalIncrement,"apply": apply }}) .then(_=>{ document.getElementById("counter").innerText = counter; }) }; </script> </body> </html>
我們將這個Global對象包含到導入的對象中,並在導入成功後刷新顯式的計數器,所以程序運行後將會顯式當前加載的wasm模塊數量(源代碼)。
三、利用全局變量處理字符串
WebAssembly目前並沒有提供針對字符串類型的直接支持,而是單純地將其作爲字節序列看到。目前字符串在宿主程序與wasm模塊之間的傳遞只有通過Memory來實現。由於Javascript具有處理字符串的能力,wasm模塊可以將字符串作爲externref回傳到宿主程序進行處理。在接下來演示的程序中,我們在app.wat中定義一個“字符類型(實際上是externref類型)”的全局變量,導出的greet函數通過調用導入的print函數將其輸出。
(module (func $print (import "imports" "print") (param externref)) (global $message (import "imports" "message") (mut externref)) (func (export "greet") (call $print (global.get $message)) ) )
在index.html中,我們在頁面上放置了三個按鈕,讓它們在命名爲“greet”的<div>中分別顯示“Good Morning”、“Good Afternoon”和“Good Evening”三條問候語。具體的問候語通過函數print輸出,它的參數就是代表輸出文本的字符串。
<html> <head></head> <body> <div id="greet"></div> <button id="btnMorning">Morning</button> <button id="btnAfternoon">Afternoon</button> <button id="btnEvening">Evening</button> <script> var print = (msg) => { console.log(msg); document.getElementById("greet").innerText = msg; } const globalMsg = new WebAssembly.Global({ value: "externref", mutable: true }, "Good Morning!"); console.log(globalMsg.value); WebAssembly .instantiateStreaming(fetch("app.wasm"), {"imports":{"message":globalMsg, "print":print}}) .then(results => { var greet = results.instance.exports.greet; console.log(greet); document.getElementById("btnMorning").onclick = ()=>{ globalMsg.value = "Good Morning!"; greet(); }; document.getElementById("btnAfternoon").onclick = ()=>{ globalMsg.value = "Good Afternoon!"; greet(); }; document.getElementById("btnEvening").onclick = ()=>{ globalMsg.value = "Good Evening!"; greet(); }; }); </script> </body> </html>
我們定義了一個externref類型的Global對象來引用帶輸出的問候語文本,並在加載app.wasm木塊使將其包含到導入對象中。三個按鈕的click事件處理程序通過調用導出的greet函數輸出對於的問候語,但是在調用此函數之前會對Global對象進行相應的賦值(源代碼)。
WebAssembly入門筆記[1]:與JavaScript的交互
WebAssembly入門筆記[2]:利用Memory傳遞字節數據
WebAssembly入門筆記[3]:利用Table傳遞引用
WebAssembly入門筆記[4]:利用Global傳遞全局變量