• 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服务。

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

      1 package main
      2 
      3 import (
      4     "fmt"
      5     "math/rand"
      6     "strconv"
      7     "syscall/js"
      8     "time"
      9 )
     10 
     11 const (
     12     width  = 400
     13     height = 400
     14 )
     15 
     16 // 生成 0 - 1 的随机数
     17 func getRandomNum() float32 {
     18     rand.New(rand.NewSource(time.Now().UnixNano()))
     19     n := float32(rand.Intn(10000))
     20     return n / 10000.0
     21 }
     22 
     23 // 生成 0 - 10 的随机数
     24 func getRandomNum2() float32 {
     25     rand.New(rand.NewSource(time.Now().UnixNano()))
     26     n := float32(rand.Intn(10000))
     27     return n / 1000.0
     28 }
     29 
     30 // 使用 canvas 绘制随机图
     31 func draw() {
     32     var canvas js.Value = js.
     33         Global().
     34         Get("document").
     35         Call("getElementById", "canvas")
     36 
     37     var context js.Value = canvas.Call("getContext", "2d")
     38 
     39     // reset
     40     canvas.Set("height", height)
     41     canvas.Set("width", width)
     42     context.Call("clearRect", 0, 0, width, height)
     43 
     44     // 随机绘制 50 条直线
     45     var clineStyle = `rgba(%d, %d, %d, 0.5)`
     46     for i := 0; i < 50; i++ {
     47         lineStyle := fmt.Sprintf(clineStyle, 155+int(getRandomNum2()*10), 155+int(getRandomNum()*100), 155+int(getRandomNum()*100))
     48         fmt.Println(lineStyle)
     49         context.Call("beginPath")
     50         context.Set("strokeStyle", lineStyle)
     51         context.Call("moveTo", getRandomNum()*width, getRandomNum()*height)
     52         context.Call("lineTo", getRandomNum()*width, getRandomNum()*height)
     53         context.Call("stroke")
     54     }
     55 
     56     context.Set("font", "30px Arial")
     57     context.Set("strokeStyle", "blue")
     58     for i := 0; i < 10; i++ {
     59         context.Call("strokeText", "hello wasm", (getRandomNum2()+1)*10+getRandomNum2()*10, (getRandomNum2()+1)*10+getRandomNum2()*50)
     60     }
     61 }
     62 
     63 func registerCallbackFunc() {
     64     cb := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
     65         fmt.Println("button clicked")
     66 
     67         num1 := getElementByID("num1").Get("value").String()
     68         v1, err := strconv.Atoi(num1)
     69         if nil != err {
     70             fmt.Println("button clicked:", num1, err.Error())
     71             jsAlert().Invoke(err.Error())
     72             // panic(err)
     73             return nil
     74         }
     75 
     76         num2 := getElementByID("num2").Get("value").String()
     77         v2, err := strconv.Atoi(num2)
     78         if nil != err {
     79             fmt.Println("button clicked:", num2, err.Error())
     80             // panic(err)
     81             return nil
     82         }
     83 
     84         rlt := v1 + v2
     85         getElementByID("rlt").Set("value", rlt)
     86 
     87         return nil
     88     })
     89 
     90     getElementByID("compute").Call("addEventListener", "click", cb)
     91 }
     92 
     93 func getElementByID(id string) js.Value {
     94     return js.Global().Get("document").Call("getElementById", id)
     95 }
     96 
     97 func jsAlert() js.Value {
     98     return js.Global().Get("alert")
     99 }
    100 
    101 func main() {
    102     fmt.Println("Hello, Go WebAssembly!")
    103     draw()
    104     // 通过js.Global().Get()拿到全局alert函数的引用
    105     alert := js.Global().Get("alert")
    106     // 调用alert.Invoke来调用alert函数
    107     alert.Invoke("hello world")
    108 
    109     registerCallbackFunc()
    110 }
    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

     1 <html>
     2     <head>
     3         <meta charset="utf-8">
     4         <script src="wasm_exec.js"></script>
     5         <script>
     6             const go = new Go();
     7             WebAssembly.instantiateStreaming(fetch("lib.wasm"), go.importObject).then((result) => {
     8                 go.run(result.instance);
     9             });
    10         </script>
    11     </head>
    12     <body>
    13         <canvas id='canvas'></canvas></br>
    14         <input id="num1" type="number" />
    15         +
    16         <input id="num2" type="number" />
    17         =
    18         <input id="rlt" type="number" readonly="readonly" />
    19         <button id="compute">compute</button>
    20     </body>
    21 </html>
    index.html

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

    三、编写一个HTTP服务

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

     1 package main
     2 
     3 import (
     4     "flag"
     5     "log"
     6     "net/http"
     7 )
     8 
     9 var (
    10     listen = flag.String("listen", ":8087", "listen address")
    11     dir    = flag.String("dir", ".", "files directory to serve")
    12 )
    13 
    14 func main() {
    15     flag.Parse()
    16     log.Printf("listening on %q...", *listen)
    17     err := http.ListenAndServe(*listen, http.FileServer(http.Dir(*dir)))
    18     log.Fatalln(err)
    19 }
    HTTP服务代码

    这里要注意:之前为了编译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/

    可以看到页面弹出了对话框:

    然后出现了我们绘制的内容:

    在浏览器调试器里面看到输出内容:

     页面上还有一个计算的功能,我们输入数字,点击按钮,发现没有反应,看调试器可以看见错误:

    信息提示很明确,回头看我们的Go代码,main()函数在执行了registerCallbackFunc()就结束退出了,

    这个时候再去调用肯定是失败的,所以我们要让程序不能退出:

     1 func main() {
     2     fmt.Println("Hello, Go WebAssembly!")
     3     draw()
     4     // 通过js.Global().Get()拿到全局alert函数的引用
     5     alert := js.Global().Get("alert")
     6     // 调用alert.Invoke来调用alert函数
     7     alert.Invoke("hello world")
     8     done := make(chan struct{}, 0) // 创建无缓冲通道
     9 
    10     registerCallbackFunc()
    11     <-done    // 阻塞
    12 }

    在第8行创建一个通道,然后在11行从通道读取内容,因为通道没有内容,所以会阻塞。

    然后重新编译wasm文件,刷新网页,可以看到预期达到了:

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

  • 相关阅读:
    Excel生成二维折线图详细教程 TheChosen
    sql语句截取字段中某一符号前几位的方法? TheChosen
    python快速下载包的镜像源? TheChosen
    Android 超大图长图浏览库 SubsamplingScaleImageView 源码解析
    记录线上APP一个排序比较引发的崩溃 Comparison method violates its general contract!
    Android 内存泄漏检测工具 LeakCanary(Kotlin版)的实现原理
    改数组长度
    枚举数组所有组合
    Ubuntu20.04启动后光标一直闪烁
    harbor镜像仓库清理操作
  • 原文地址:https://www.cnblogs.com/codingnote/p/12453872.html
Copyright © 2020-2023  润新知