GoのコードをPerlから呼び出せるようにするgo2xsを書いてみました。
使い方
Perlから使いたい関数に以下のようにgo2xsで始まるコメントを付けておきます。
package main
//go2xs hello
func hello(str string) string {
return "Hello " + str
}
go2xsをgo getして、xsのグルーコードを作成。 その後通常のPerlモジュールと同じ手順でコンパイルします。 Go 1.5から入ったShared Libraryの機能を使っているのでGo 1.5が必要です。
go get https://github.com/shogo82148/go2xs/cli/go2xs
go2xs -name hoge hoge.go
perl Makefile.PL
make
あとは普通に呼び出すだけ。
perl -Mblib -Mhoge -e 'print hoge::hello("World")'
Hello World
制限事項
今はまだ、整数・浮動小数点型・文字列しか扱えません。
あとGoのShared Libraryを複数回読み込むことができないっぽい? (ref. https://github.com/golang/go/issues/11100 ) ので、go2xsを使ったコードを二つ以上useすると死にます。
FFI::Rawを使う方法
go2xsはGoをShared Libraryとしてコンパイルしているだけなので、go2xsを使わなくても頑張れば呼び出すことができます。 Golang で Shared Library を出力する。で紹介されているこちらのコードで試してみます。
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() {
}
ビルドしてShared Libraryを作ってみます。
build -buildmode=c-shared -o libgofib.so libgofib.go
PerlからShared Libraryを呼び出すにはFFI::Rawを使うのがお手軽のようです。
use FFI::Raw;
my $fib = FFI::Raw->new(
'libfib.so', 'fib',
FFI::Raw::int, # 戻り値
FFI::Raw::int, # 引数
);
print $format->call(32);
文字列の受け渡しをしてみる
FFI::Rawを使った方法はお手軽ですが、文字列の受け渡しをしようとすると色々と面倒です。
素朴に実装してみる
Golangのstring
はPerlでそのまま扱えないので、C.GoString
とC.CString
を使い一度C言語の文字列形式を経由してから相互変換する必要があります。
import "C"
import (
f "go/format"
)
//export format
func format(src *C.char) *C.char {
gosrc := C.GoString(src)
dst, _ := f.Source([]byte(gosrc))
return C.CString(string(dst))
}
func main() {
}
use FFI::Raw;
my $format = FFI::Raw->new(
'libgofmt.so', 'format',
FFI::Raw::str, # 戻り値
FFI::Raw::str, # 引数1
);
print $format->call(<<"EOF");
package main
import "fmt"
func main( ) {
fmt.Println("hogehoge") }
EOF
なんとなく動いてよさ気な感じがしますが、
実はこのコード、メモリーリークしてます。
C.CString
で作ったC言語の文字列はGolangの管理から外れるのでGCで回収されません。
そのため、どこかでfree
を実行して開放する必要があります。
strcpyで頑張る
Golang側でバッファを確保するとPerlに戻った時にバッファ開放ができないので、 Perl側で結果を格納するバッファを確保してみます。
package main
/*
#include <stdlib.h>
#include <string.h>
*/
import "C"
import (
f "go/format"
"unsafe"
)
//export format
func format(dst *C.char, src *C.char) {
gosrc := C.GoString(src)
godst, _ := f.Source([]byte(gosrc))
cs := C.CString(string(godst))
defer C.free(unsafe.Pointer(cs))
C.strcpy(dst, cs)
}
func main() {
}
use FFI::Raw;
my $format = FFI::Raw->new(
'libgofmt.so', 'format',
FFI::Raw::void, # 戻り値
FFI::Raw::str,
FFI::Raw::ptr,
);
while(1) {
my $hoge;
my $coderef = sub { $hoge = shift };
my $callback = FFI::Raw::callback($coderef, FFI::Raw::void, FFI::Raw::str);
$format->call(<<"EOF", $callback);
package main
import "fmt"
func main( ) {
fmt.Println("hogehoge") }
EOF
}
この方式なら作ったShared Libraryを他の言語からも呼びやすいので無難かも? ただし、まだまだ実装が不十分で、このコードはバッファオーバーランの危険があります。 しっかり実装するなら、最初の一回でバッファのサイズだけ計算、次の呼び出しで結果取得・・・のようなフローを踏む必要があります。
コールバック
結果の保存をコールバック関数の呼び出しで行えば、Golang側でfreeを実行するタイミングが分かるので、メモリーリークを防ぐことができます。 ただし、Goからは関数ポインタを呼び出すことはできないので、gcoでグルーコードを書いてあげる必要があります。
package main
/*
#include <stdlib.h>
typedef void (*callbackFunc) (const char*);
void bridge_callback(callbackFunc f, const char* str);
*/
import "C"
import (
f "go/format"
"unsafe"
)
//export format
func format(src *C.char, dstCallback unsafe.Pointer) {
gosrc := C.GoString(src)
godst, _ := f.Source([]byte(gosrc))
cs := C.CString(string(godst))
defer C.free(unsafe.Pointer(cs))
C.bridge_callback(C.callbackFunc(dstCallback), cs)
}
func main() {
}
typedef void (*callbackFunc) (const char*);
void bridge_callback(callbackFunc f, const char* str) {
f(str);
}
use FFI::Raw;
my $format = FFI::Raw->new(
'libgofmt.so', 'format',
FFI::Raw::void, # 戻り値
FFI::Raw::str, FFI::Raw::ptr,
);
while(1) {
my $hoge;
my $coderef = sub { $hoge = shift };
my $callback = FFI::Raw::callback($coderef, FFI::Raw::void, FFI::Raw::str);
$format->call(<<"EOF", $callback);
package main
import "fmt"
func main( ) {
fmt.Println("hogehoge") }
EOF
}
まとめ
GoのShared Libraryの呼び出しは数値型だけを相手にしていれば比較的簡単ですが、文字列を扱おうとすると少し面倒です。 go2xsを使うとそこら辺が簡単になります。 あんましライブラリ作ったことないですが、文字列の受け渡しをするときには、一般的にはどんな感じのインターフェースにするべきなんですかね?
GoのShared Libraryまだまだ出たばかりで、複数回読み込めなかったりと問題はありますが、 Goの機能を他の言語から呼び出せるのは便利ですね。 今後に期待です。