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()`を用意します。

```golang

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`構造体を使ってみます。

```golang

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
}

```

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

```sh

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

# WebAssembly用にコードを修正
コードの全体です。

```golang

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.Value`と`args[]js.Value`、 返り値を`interface{}`にします。

```golang

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

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

```golang

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

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

```golang

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

### 構造体のメソッドのjavascriptへの登録
`js.Global()`を呼び出して、javascriptの空間に`test.Print`、`test.Twice`、`test.Add`、`test.GetNum`をそれぞれ登録します。

```golang
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関数が終了しないように、チャネルを使って処理をブロックします。

```golang

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

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

```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用)](https://raw.githubusercontent.com/golang/go/go1.12.7/misc/wasm/wasm_exec.js)は、公式から落とせます。

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

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


<img width="110" alt="キャプチャ.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/302126/76021f71-22b6-2d59-4251-3fbc7b25004b.png">

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

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

## 構造体は直接渡せない？

構造体を直接登録するのは、試してみましたができないようでした。`panic: ValueOf: invalid value`が出ます。[js.ValueOf()の実装](https://golang.org/src/syscall/js/js.go?s=3627:3660#L109)を見ると、任意の構造体は扱えない感じになっていました。

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

# ソース
githubに置いておきました。

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