Go 1.4 で android に対応していたのでもしかしてそろそろできるんじゃないかなぁとか思いながら github の go レポジトリを眺めていたら Go 1.5 から buildmode なんていうオプションが追加されていて、そこに c-shared なるものががが。
これは間違いないだろうと試してみました。
(ちなみに試すためには Go 1.5 が必要なので git clone して Go 1.4 でビルドしてください。本記事では Go 1.5 の準備は割愛します。)
サンプルコードは gist にも上げておきました。
package main
import (
"C"
"log"
)
//export fib
func fib(n int) int {
if (n < 2) { return n }
return fib(n - 2) + fib(n - 1)
}
func init() {
log.Println("Loaded!!")
}
func main() {
}
内容は単純なフィボナッチです。
ポイントは下記の4点です。
- パッケージは main でないと生成できない。
- main関数は実行されないが宣言する必要がある。
- エクスポートしたい関数にコメントで「//export funcname をつける。」(cgoと同じ)
- init は ライブラリロード時に実行される。
後は buildmode=c-shared を付けてビルドするだけ!
(Go 1.5 は即席で用意しただけなので、GOROOTをビルド時に設定してます。)
GOROOT=$PWD/go ./go/bin/go build -buildmode=c-shared -o libgofib.so libgofib.go
相変わらずのでかいバイナリが出力されればビルドできてます。
$ ll libgofib.so
-rwxrwxr-x 1 yanolab yanolab 3.2M 5月 21 09:46 libgofib.so*
$ file libgofib.so
libgofib.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=acc56aca625aa6e9bf7bfc9d7b1c8e377ff9df0b, not stripped
サイズが大きかったのでまさか Shared Library まで他ライブラリに依存していないのか!と思いましたがそんなことはありませんでした。
$ ldd libgofib.so
linux-vdso.so.1 => (0x00007ffc0d6f7000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fc878e8a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc878ac5000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc879563000)
まぁ、しょうが無いですよね。うん。
せっかくなので、このライブラリを実行するところまで確認してみます。
まずは fib がちゃんとエクスポートされているかどうか。
$ nm -D libgofib.so | grep fib
00000000000d9eb0 T _cgoexp_28da6a1ce835_fib
00000000000d9a50 T fib
バッチリですね。
次は実際にこの関数を呼び出してみます。
ライブラリの関数を簡単に実行する方法としてはあれしかないです。そう、僕らの python 先生。
おもむろに python インタプリタを起動して下記のコードを打ち込みます。
>>> import ctypes
>>> lib = ctypes.CDLL("./libgofib.so")
>>> 2015/05/21 09:56:21 loaded!!
>>> lib.fib(32)
2178309
バッチリ使えてますね。
プラットフォームが linux/{arm,amd64}, android/arm に限定されてたりしますが、チャネルも goroutine も使えるので、中身をバリバリの Go で書いて簡単な C API を提供するとか個人的にはすごくポイント高いです。
今後の展開にも大いに期待したいです。
P.S. 2015年5月21日現在、Go 1.5 はリリースされていないので、リリース時にどうなるかはわかりませんので悪しからず。