WebAssembly入門筆記[4]:利用Global傳遞全局變量

利用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(源代碼)。

image

二、將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模塊數量(源代碼)。

image

三、利用全局變量處理字符串

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對象進行相應的賦值(源代碼)。

image

WebAssembly入門筆記[1]:與JavaScript的交互
WebAssembly入門筆記[2]:利用Memory傳遞字節數據
WebAssembly入門筆記[3]:利用Table傳遞引用
WebAssembly入門筆記[4]:利用Global傳遞全局變量

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章