Help us understand the problem. What is going on with this article?

WebAssemblyから、Goのメソッドを呼び出す

試したこと

WebAssemblyで、Goで宣言した構造体のメソッドを呼び出します。環境は、go1.12.7 + Chromeで確認しています。

ちなみに、syscall/jsを使っているので、GOOS=js GOARCH=wasm 以外で使うことはたぶん難しいです。

構造体の定義

確認用に、Numというメンバを持ったTestという構造体を使います。メソッドは、Testを出力するPrint()と、Numの値を2倍にするTwice()Test.Numに値を足すAdd()、値を取得するGetNum()を用意します。

type Test struct {
    Num int
}

func (t *Test) Print() {
    fmt.Println(t)
}

func (t *Test) Twice()  {
    t.Num *= 2
}

func (t *Test) Add(val int) {
    t.Num += val
}

func (t *Test) GetNum() int {
    return t.Num
}

コマンドラインで実行してTest構造体を使ってみます。

package main

import (
    "fmt"
)

type Test struct {
    Num int
}

func (t *Test) Print() {
    fmt.Println(t)
}

func (t *Test) Twice()  {
    t.Num *= 2
}

func (t *Test) Add(val int) {
    t.Num += val
}


func (t *Test) GetNum() int {
    return t.Num
}

func main() {
    var test = &Test{
        Num: 1,
    }
    test.Print() // result should be &{1}
    test.Twice() 
    test.Print() // result should be &{2}
    test.Add(1)  
    test.Print() // result should be &{3}
    fmt.Println(test.GetNum()) // result should be 3
}

実行結果は↓のようになります。

&{1}
&{2}
&{3}
3

WebAssembly用にコードを修正

コードの全体です。

package main

import (
    "fmt"
    "syscall/js"
)

type Test struct {
    Num int
}

func (t *Test) Print(this js.Value, args []js.Value) interface{} {
    fmt.Println(t)
    return nil
}

func (t *Test) Twice(this js.Value, args []js.Value) interface{} {
    t.Num *= 2
    return nil
}

func registerCallbacks() {
    var test = &Test{
        Num: 1,
    }
    js.Global().Set("test", js.ValueOf(
        map[string]interface{}{
            "Print": js.FuncOf(test.Print),
            "Twice": js.FuncOf(test.Twice),
        },
    ))
}

func main() {
    c := make(chan struct{}, 0)
    registerCallbacks()
    <-c
}

コードの解説

WebAssemblyから呼び出せるように関数の定義を変更する

WebAssemblyから呼び出すときの作法に従い、引数をjs.Valueargs[]js.Value、 返り値をinterface{}にします。

func (t *Test) Print(this js.Value, args []js.Value) interface{} {
    fmt.Println(t)
    return nil
}

引数を参照する

引数の値を直接Go側で使うことはできないので、args[0].Int()を呼び出して整数型に変換します。

func (t *Test) Add(this js.Value, args []js.Value) interface{} {
    t.Num += args[0].Int()
    return nil
}

返り値を戻す

返り値はそのまま返せないので、js.ValueOf()で返します。

func (t *Test) GetNum(this js.Value, args []js.Value) interface{} {
    return js.ValueOf(t.Num)
}

構造体のメソッドのjavascriptへの登録

js.Global()を呼び出して、javascriptの空間にtest.Printtest.Twicetest.Addtest.GetNumをそれぞれ登録します。

func registerCallbacks() {
    var test = &Test{
        Num: 1,
    }
    js.Global().Set("test", js.ValueOf(
        map[string]interface{}{
            "print":  js.FuncOf(test.Print),
            "twice":  js.FuncOf(test.Twice),
            "add":    js.FuncOf(test.Add),
            "getNum": js.FuncOf(test.GetNum),
        },
    ))
}

関数の登録

関数の登録を呼び出します。main関数が終了しないように、チャネルを使って処理をブロックします。

func main() {
    c := make(chan struct{}, 0)
    registerCallbacks()
    <-c
}

テストする

以下の、htmlファイルでテストをします。

<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>Go wasm</title>
</head>

<body>
    <script src="wasm_exec.js"></script>
    <script>
        const go = new Go();
        WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then((result) => {
            go.run(result.instance);
        });
    </script>
    <script>
        function getNum(){
            console.log(test.getNum())
        }
    </script>
    <button onClick="test.print();" id="runButton" >print</button><br>
    <button onClick="test.twice()" id="runButton">twice</button><br>
    <button onClick="test.add(1)" id="runButton">add</button><br>
    <button onClick="getNum()" id="runButton">get test.Num</button><br>
</body>
</html>

WebAssemblyの実行に使う wasm_exec.js (go1.12.7用)は、公式から落とせます。

また、Goのファイルは、GOOS=js GOARCH=wasm go build -o test.wasm でビルドしておきます。

上記のhtmlファイル、wasm_exec.js, test.wasm をサーバ上に置いてアクセスすると、以下のようにボタンが表示されます。html, wasm_exec.js, test.wasm

キャプチャ.PNG

コマンドラインで確認したときと同様に、printtwiceprintaddprintget test.Num を押すと、ブラウザのコンソールに以下のように表示されました。

&{1}
&{2}
&{3}
3

構造体は直接渡せない?

構造体を直接登録するのは、試してみましたができないようでした。panic: ValueOf: invalid valueが出ます。js.ValueOf()の実装を見ると、任意の構造体は扱えない感じになっていました。

func registerCallbacks() {
    var test = &Test{
        Num: 1,
    }
    js.Global().Set("test", js.ValueOf(test))
}

ソース

githubに置いておきました。

https://github.com/neko-suki/wasm_struct

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした