LoginSignup
2
1

More than 3 years have passed since last update.

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

Posted at

https://qiita.com/neko-suki/items/7fb822b9adfa6f1c12eb の続きです。

今回は、以下の2点を扱います。

  • Goで定義した構造体の定義は変更せずに、WebAssemblyからGoで定義した構造体のメソッドを呼ぶ
  • 構造体のメソッドの引数に、同じ構造体の別のインスタンスを渡す

両者とももっと賢く書く方法がある気がしますが、自分で調べた範囲では見つけられませんでした。

Goで定義した構造体の定義は変更せずに、WebAssemblyからGoで定義した構造体のメソッドを呼ぶ

特にいいやり方が思いつかなかったので、ラッパーを書きました。

構造体の定義

構造体の定義は元のままです。これをWebAssemblyから使えるようにします。元の構造体事態は何らかの事情で変更したくない場合を想定しています。


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
}

WebAssembly用の実装

WebAssembly側から呼び出せるようにラッパー関数をそれぞれ書きました。


package main

import (
    "syscall/js"
)

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

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

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

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

func registerCallbacks() {
    var test = &Test{
        Num: 1,
    }
    js.Global().Set("test", js.ValueOf(
        map[string]interface{}{
            "print":  js.FuncOf(test.PrintWrapper),
            "twice":  js.FuncOf(test.TwiceWrapper),
            "add":    js.FuncOf(test.AddWrapper),
            "getNum": js.FuncOf(test.GetNumWrapper),
        },
    ))
}

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

構造体のメソッドの引数に、同じ構造体の別のインスタンスを渡す

例えば、以下のように同じ構造体の別のインスタンスを受け取って内部で利用する場合を考えます。


func (t *Test) AddAnotherTest(t2 *Test) {
    t.Num += t2.Num
}

WebAssemblyの実装

まずは、関数登録部分です。構造体自信のポインタを取得するための関数 _ptrを追加しています。これによって、登録した構造体のインスタンスがjavascript側から引数として渡されたときにポインタを取得できるようになります。_ptrに直接ポインタの値を代入しないのはjavascript側で値を改変されないようにするためです。(プライベートメンバとして登録できれば必要ないはずですが、今回はそこまで調べていないです。)

func registerCallbacks() {
    var test = &Test{
        Num: 1,
    }
    js.Global().Set("test", js.ValueOf(
        map[string]interface{}{
            "_ptr":           js.FuncOf(test.getPtr),
            "print":          js.FuncOf(test.PrintWrapper),
            "twice":          js.FuncOf(test.TwiceWrapper),
            "add":            js.FuncOf(test.AddWrapper),
            "getNum":         js.FuncOf(test.GetNumWrapper),
            "addAnotherTest": js.FuncOf(test.AddAnotherTestWrapper),
        },
    ))

    var test2 = &Test{
        Num: 2,
    }
    js.Global().Set("test2", js.ValueOf(
        map[string]interface{}{
            "_ptr":           js.FuncOf(test2.getPtr),
            "print":          js.FuncOf(test2.PrintWrapper),
            "twice":          js.FuncOf(test2.TwiceWrapper),
            "add":            js.FuncOf(test2.AddWrapper),
            "getNum":         js.FuncOf(test2.GetNumWrapper),
            "addAnotherTest": js.FuncOf(test2.AddAnotherTestWrapper),
        },
    ),
    )
}

次にポインタ取得用関数の実装です。unsafe.Pointerjs.ValueOf()js.Value型に変換して、javascript側に渡します。


func (t *Test) getPtr(this js.Value, args []js.Value) interface{} {
    return js.ValueOf(unsafe.Pointer(t))
}

最後に、構造体を受け取って処理する部分です。javascript側からtest.AddAnotherTest(test2)という形で呼ばれる想定です。

まず、args[0].Get('_ptr')test2._ptr関数を呼び出しして、test2のポインタを取得します。ただしこの段階ではjs.Value型なので変換が必要です。unsafe.Pointerはfloat64で実装されているようなので、.Float()を呼び出します。その結果をunsafe.Pointer型に変換して、最後にTestのポインタに変換します。


func (t *Test) AddAnotherTestWrapper(this js.Value, args []js.Value) interface{} {
    ptrJSValue := args[0].Call("_ptr")
    t2 := (*Test)(unsafe.Pointer(ptrJSValue.Float()))
    t.AddAnotherTest(t2)
    return nil
}

テスト用のhtml

テスト用に使うhtmlです。addAnotherTest() というのが追加されています。例えば、twice, addAnotherTest, printという順番でボタンを押すと、consoleに&{4}が表示されます。

<!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())
        }
        function addAnotherTest(){
            console.log(test2)
            test.AddAnotherTest(test2)
        }
    </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>
    <button onClick="addAnotherTest()" id="runButton"> addAnotherTest</button><br>

</body>
</html>

ソース

下に置いてあります。
https://github.com/neko-suki/wasm_struct

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1