Xamarinでもgomobileを使いたい!
Visual Studioが好きなのでXamarinでaarを使いたいと思いました
Android Studio重いから好きじゃないの
じゃぁC#で書けよって言われそうですが、最近のC#はC++程ではないにしろ仕様が膨らんできてなんだかしんどいです
F#やIronPythonも試してはみたのですがいまいちしっくりこなかったのでgomobileを試してみることにしました
試してみたら意外と動きました
うちの環境だとOreo以上対象にしないと動かないのですが、Android Studioでも同じような状況ですし、
単なる私の知見不足なのだろうと思います
gomobile bind -target android
で普通に生成したaarを使っています
コードのサンプルは下の方にあります
ただ下でも詳しく書きますが、Go単体時のテスト・Java/Kotlin側だと正常に動いていたaarが一部Xamarinでは動きませんでした。
gomobileで生成したaarをC#側で認識させられるようになるまで
MSの公式サイトに載っている方法で概ねできます
注意点としては上記の解説では
クラスパーサーとCodegenターゲットに言及がないのですが、
それぞれjar2xml、XamarinAndroidにしないと動きませんでした
また、上記の解説では同じソリューション内でやっていましたが、私の環境(VS 2017)では別ソリューションでプロジェクトを作って、
アプリ側プロジェクトからビルドしたdllを参照するというやり方をしないとうまく行きませんでした
aarから自動生成されたコードをdllのプロジェクトでうまく認識できてないような気がします
後Androidのバージョンもアプリとdllで統一しないと不具合が起きやすいそうです
この辺はGomobileというよりはXamarin側の使い方の問題だと思います
ちょくちょくエラーが起きましたがソリューションを分けた後はリビルドをかけていけば概ね問題は起きていません
C#側でのaarのexportの認識状況
gomobileを使うとgolangのオブジェクトとJavaのオブジェクトを相互にやり取りすることができるのはご存じだと思います。
単純なサンプル
こういう単純なサンプルだととりあえず想像通りに動きます
package libgo
import "log"
func RecvString()string{
return "hogehoge"
}
func SendString(str string){
log.Println(str)
}
type Obj interface{
String()string
}
type obj struct{
}
func (o *obj)String()string{
return "fuga"
}
func NewObj()Obj{
r:=&obj{}
return r
}
func PrintObj(o Obj){
log.Println(o.String())
}
(この例では使ってませんが)intやstringはほぼそのまま使えます
interfaceについて補足しておくと
この例だとC#側から見るとObj interfaceはLibgo名前空間でIObj interfaceとして公開されます
interfaceを満たすstructならNewObj()関数を経由してC#側でも受け取れます
C#の命名規則に乗っ取ってる以外普通です(Java側だと抽象クラス扱いですが)
逆にC#側でJava.Lang.ObjectとLibgo.Objを継承したクラスを作ってやればPrintObj()関数に渡すこともできます
class Class1 : Java.Lang.Object, Libgo.Obj
{
public string String()
{
return "piyo";
}
}
この辺りまでは想像通りに動いてくれます
### 動かない例
Qiitaでもgomobileの使用方法についての記事はたくさんありますが、例えば
GoのHTTPサーバーを動かす例はXamarinでは動きませんでした
http.Server構造体をserver構造体内で保持するように書き換えると動くので、http.Server自体が使えないわけではありません
package libgo
//これなら動く
import (
"log"
"net/http"
)
type server struct {
srv http.Server
}
func newServer() *server {
s := &server{
http.Server{
Addr: "localhost:12345",
},
}
s.srv.Handler = s
return s
}
func (s *server) Start() {
s.srv.ListenAndServe()
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
log.Println(w, "Hello, World")
}
正常に動かなかったパターン
試してみて正常に動かなかったものとして
- UIスレッド内で長時間関数を動かした場合(これは当たり前のことだと思います)
- C#側で関数内でgoroutineを呼び出した場合にgorutineが終了するまでに関数が終了した場合(gorutineも死ぬ)
- グローバル変数を利用した場合
以上のようなものがありました
多少試してみた感じだと、
Go内でグローバル変数を使ってる例やC#側から見てGoのオブジェクトの参照が見えない場合にぬるぽが発生しているように見えました
GO,Java,C#を同時に利用している関係上、それぞれのGCも独立して動いている状況なのですが、
C#のGCが勝手に自分が参照を把握していないオブジェクトを回収しているような気がしないでもないです
## 動くには動くけど…
以上の通り、Xamarinでgomobileで生成したaarを動かすことはできました
ただ、Goは仕様上グローバル変数を多用する言語です
言語仕様上クラスや静的クラスがないのでもし私の考察が合ってるとするならそれなりに使い勝手は落ちますし
何よりコードの移植性が激減する(グローバル変数を全部構造体に書き直さないといけない)ので用途は限られてしまうのかなと思います
うちだとちゃんと動いたよ!ここ間違ってるせいでおかしくなってるよ!みたいなものがあればご指摘ください
よろしくお願いします