利用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传递全局变量