試したこと
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.Value
とargs[]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.Print
、test.Twice
、test.Add
、test.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
コマンドラインで確認したときと同様に、print
、twice
、 print
、 add
、 print
、 get 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に置いておきました。