go-mrubyでmrubyをC共有ライブラリに突っ込む

Go1.5から、C共有ライブラリを生成するオプションが追加されたらしい。

そこで、Goにmrubyを組み込んでC共有ライブラリを作成し、使ってみた。 環境は、

まずは、go-mrubyをダウンロード。

go get -d github.com/mitchellh/go-mruby

続いて、リポジトリのreadme通りmrubyをビルド。(ここで独自にmrbgemを組み込みたい場合は、makeしないで自前でビルドしたほうがよい)

cd $GOPATH/src/github.com/mitchellh/go-mruby
make

次に、試しに以下のようなコードを任意の場所に用意。 LoadString()で、引数の文字列をmrubyのコードとして実行し戻り値を受け取っている。

// example.go
package main                                                                                                                                                                                                                      
                                                                                                                                                                                                                                  
import ( 
  "C"                                                                                                                                                                                                                     
  "github.com/mitchellh/go-mruby"                                                                                                                                                                                                 
)                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                  
//export example                                                                                                                                                                                                                      
func example() {                                                                                                                                                                                                                      
  mrb := mruby.NewMrb()                                                                                                                                                                                                           
  defer mrb.Close()                                                                                                                                                                                                               
                                                                                                                                                                                                                                  
  _, err := mrb.LoadString(`5.times {|num| puts "*" * num}`)                                                                                                                                                                      
  if err != nil {                                                                                                                                                                                                                 
  ¦ panic(err.Error())                                                                                                                                                                                                            
  }                                                                                                                                                                                                                               
}                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                  
func main() {                                                                                                                                                                                                                     
} 

上記example.goと同ディレクトリに、最初に生成しておいた静的ライブラリ(libmruby.a)を置くことも忘れない。

cp -p $GOPATH/src/github.com/mitchellh/go-mruby/libmruby.a /path/to/example

最後に、以下でsoが生成されるぞお!

go build -buildmode=c-shared -o example.so example.go

と思ったら、エラーが発生した。

# command-line-arguments
/path/to/linker: running clang failed: exit status 1
clang: error: no such file or directory: 'libmruby.a'

いろいろ試してみたけど、これは、 go-mrubyのmruby.go内で、cgoのインポート時にLDFLAGSに指定しているlibmruby.aが見つからないと言っているので、

とりあえず、以下のようにmruby.go自体を書き換えてしまう。

@@ -3,7 +3,7 @@ package mruby
 import "unsafe"
 
 // #cgo CFLAGS: -Ivendor/mruby/include
-// #cgo LDFLAGS: libmruby.a -lm
+// #cgo LDFLAGS: /full/path/to/libmruby.a -lm
 // #include <stdlib.h>
 // #include "gomruby.h"
 import "C"

再度ビルドして、example.soが生成された!

example()がエクスポートされているかを確認する。よさそう。

$ nm example.so | grep -i example                    
0000000000071a90 T __cgoexp_a4c9f6073c4b_example
00000000000026e0 T _example
0000000000071ad0 t main.example
00000000001fc400 s main.example.f

ということで、例によってpythonのREPLでexample()を実行してみる。出た!

>>> import ctypes
>>> lib = ctypes.CDLL("./example.so")
>>> lib.example()

*
**
***
****
1



以上、非常に手軽にgoでC共有ライブラリを書けて、実は中身はmrubyでした!! ということができました。

次に、LoadString()でmrubyを読みこむ部分を、ファイルから読めるようにすると良いかなと思う。 そうすればリビルドすることなく挙動をコントロールできるという恩恵が得られます。

それは次回のエントリにしよう。。。