您现在的位置是:网站首页> Go语言

Go开发wasm资料收集

摘要

Go开发wasm资料收集


wasm相关开发与应用

Go进行wasm编程





Go进行wasm编程

wasm即webAssemble,是一种不针对特定平台的二进制格式文件。Go从1.11开始支持wasm,最初通过js.NewCallBack()注册函数,1.12开始换成了FuncOf()。

Go开发wasm需要一个go文件用于编写实现代码,编译成.wasm文件;需要一个wasm_exec.js文件,这个是Go提供的,可以从 Go 安装目录的 misc 子目录里找到,将它直接拷贝过来。它实现了和 WebAssembly 模块交互的功能;另外就是需要一个HTML文件用于加载wasm文件。当然为了工作起来,我们还要实现一个简单的HTTP服务。


golang wasm解密模块

func decode(this js.Value, args []js.Value) interface{} {

// 加密key

keys := ([]byte)("KEY_PREFIX_" + args[0].String())

buffer := make([]byte, args[1].Length())


// 从js读取

js.CopyBytesToGo(buffer, args[1])


// ^ 示例解密函数 ( a ^ b ^ b = a)

for i := range buffer {

buffer[i] = buffer[i] ^ keys[i%len(keys)]

}


// 拷贝到js

array := js.Global().Get("Uint8Array").New(len(buffer))

js.CopyBytesToJS(array, buffer)


return array

}


func main() {

done := make(chan int, 0)

js.Global().Set("decode", js.FuncOf(decode))

<-done

}


js 调用

<script src="wasm_exec.js"></script>

function decodeImage() {

var oReq = new XMLHttpRequest();

oReq.open("GET", "demo_encode.png", true);

oReq.responseType = "arraybuffer";


oReq.onload = function (oEvent) {

var arrayBuffer = oReq.response; // 注意:不是oReq.responseText

if (arrayBuffer) {

var byteArray = new Uint8Array(arrayBuffer);

var decodeData = decode('KEY_HERE', byteArray);

document.getElementById("decodeJavaImage").src = URL.createObjectURL(

new Blob([decodeData], { type: 'image/png' })

);

}

};

oReq.send(null);

 }




一、用Go编写代码并编译成wasm文件

   package main

  

   import (

       "fmt"

       "math/rand"

     "strconv"

       "syscall/js"

       "time"

  )

  

 const (

      width  = 400

      height = 400

  )

  

 // 生成 0 - 1 的随机数

  func getRandomNum() float32 {

      rand.New(rand.NewSource(time.Now().UnixNano()))

      n := float32(rand.Intn(10000))

      return n / 10000.0

  }

  

  // 生成 0 - 10 的随机数

  func getRandomNum2() float32 {

      rand.New(rand.NewSource(time.Now().UnixNano()))

      n := float32(rand.Intn(10000))

      return n / 1000.0

  }

  

  // 使用 canvas 绘制随机图

  func draw() {

      var canvas js.Value = js.

          Global().

          Get("document").

          Call("getElementById", "canvas")

  

      var context js.Value = canvas.Call("getContext", "2d")

  

      // reset

      canvas.Set("height", height)

      canvas.Set("width", width)

      context.Call("clearRect", 0, 0, width, height)

  

      // 随机绘制 50 条直线

      var clineStyle = `rgba(%d, %d, %d, 0.5)`

      for i := 0; i < 50; i++ {

          lineStyle := fmt.Sprintf(clineStyle, 155+int(getRandomNum2()*10), 155+int(getRandomNum()*100), 155+int(getRandomNum()*100))

          fmt.Println(lineStyle)

          context.Call("beginPath")

          context.Set("strokeStyle", lineStyle)

          context.Call("moveTo", getRandomNum()*width, getRandomNum()*height)

          context.Call("lineTo", getRandomNum()*width, getRandomNum()*height)

          context.Call("stroke")

      }

  

      context.Set("font", "30px Arial")

      context.Set("strokeStyle", "blue")

      for i := 0; i < 10; i++ {

          context.Call("strokeText", "hello wasm", (getRandomNum2()+1)*10+getRandomNum2()*10, (getRandomNum2()+1)*10+getRandomNum2()*50)

      }

  }

  

  func registerCallbackFunc() {

      cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {

          fmt.Println("button clicked")

  

          num1 := getElementByID("num1").Get("value").String()

          v1, err := strconv.Atoi(num1)

          if nil != err {

              fmt.Println("button clicked:", num1, err.Error())

              jsAlert().Invoke(err.Error())

              // panic(err)

              return nil

          }

  

          num2 := getElementByID("num2").Get("value").String()

          v2, err := strconv.Atoi(num2)

          if nil != err {

              fmt.Println("button clicked:", num2, err.Error())

              // panic(err)

              return nil

          }

  

          rlt := v1 + v2

          getElementByID("rlt").Set("value", rlt)

  

          return nil

      })

  

      getElementByID("compute").Call("addEventListener", "click", cb)

  }

  

  func getElementByID(id string) js.Value {

      return js.Global().Get("document").Call("getElementById", id)

  }

  

  func jsAlert() js.Value {

      return js.Global().Get("alert")

  }

 

 func main() {

     fmt.Println("Hello, Go WebAssembly!")

     draw()

     // 通过js.Global().Get()拿到全局alert函数的引用

     alert := js.Global().Get("alert")

     // 调用alert.Invoke来调用alert函数

     alert.Invoke("hello world")

     done := make(chan struct{}, 0) // 创建无缓冲通道

     registerCallbackFunc() 

     <-done    // 阻塞

/*

创建一个通道,然后在从通道读取内容,因为通道没有内容,所以会阻塞(不占用CPU)

*/

   

    

 }

Go wasm代码

将代码编译成Wasm文件,需要设置编译环境。我用的VsCode,用powershell设置环境变量始终不能生效,于是换成了Bash:


执行:go env 查看环境,注意GOOS和GOARCH,如果是win 系统的话,默认应该是windows和amd64,为了编译出wasm文件,需要修改如下:


export GOOS=js


export GOARCH=wasm


否则编译的时候会提示奇怪的信息(不是提示环境问题),如果还是不对,可以设置CGO:


export CGO_ENABLED=0


当然我设置的1是没问题的。


最后编译生成wasm文件:


go build -o lib.wasm main.go


-o 是编译参数,指定输出的文件。


在Go里面要引入:syscall/js


通过js.Global().Get()获取js对象,既可以获取函数、也可以获取DOM元素。类型是js.Value。


如:


js.Global().Get("alert")

js.Global().Get("document")

如果是设置元素的属性调用Set(),如果是呼叫(执行)方法,调用Call("函数名","参数")。


如:


js.Global().Get("document").Call("getElementById", id)

 

前面的代码演示了调用alert()、Input的读写、Canvas对象的操作。



二、编写HTML


<html>

      <head>

          <meta charset="utf-8">

          <script src="wasm_exec.js"></script>

          <script>

              const go = new Go();

              WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {

                  go.run(result.instance);

              });

         </script>

     </head>

     <body>

         <canvas id='canvas'></canvas></br>

         <input id="num1" type="number" />

         +

         <input id="num2" type="number" />

         =

         <input id="rlt" type="number" readonly="readonly" />

         <button id="compute">compute</button>

     </body>

 </html>

HTML文件主要是定义界面元素,引入wasm_exec.js文件,调用刚才build的lib.wasm。


三、编写一个HTTP服务

Go 内置的 HTTP 服务器支持Content-Type 为 application/wasm。

package main

  

  import (

      "flag"

      "log"

      "net/http"

  )

  

  var (

     listen = flag.String("listen", ":8087", "listen address")

     dir    = flag.String("dir", ".", "files directory to serve")

 )

 

 func main() {

     flag.Parse()

     log.Printf("listening on %q...", *listen)

     err := http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir)))

     log.Fatalln(err)

 }


这里要注意:之前为了编译wasm文件,修改了GOOS和GOARCH,现在为了运行http服务,我们必须恢复。


为了方便调试,我们可以在vscode里面新建一个终端,执行:


export GOOS=windows


export GOARCH=amd64


然后执行:


go run server.go


如果有防火墙提示网络访问,选择允许,然后会看到终端提示:


2020/03/10 09:27:12 listening on ":8087"...


这表示我们的HTTP服务启动好了。


四、测试效果

在浏览器里面输入:http://127.0.0.1:8087/

页面上还有一个计算的功能,我们输入数字,点击按钮

1.png

这就是用Go开发Wasm的基本套路了






Top