go言語はビルド時にbuildmode=c-archive
を指定すると、静的ライブラリを生成することができます。
$ go build -buildmode=c-archive main.go
$ ls
main.a main.go main.h
今回はこれをmrubyに組み込んでみたいと思います。
mruby-mrbgem-template で mrbgemのひな型を作成
以下のコマンドでひな型を作成し、作成したディレクトリに移動します。
$ git clone https://github.com/matsumotory/mruby-mrbgem-template
$ cd mruby-mrbgem-template
$ rake
$ cd mruby-example
go側の実装
golang の Windows 版が buildmode=c-archive をサポートした。 を参考に作成します。
package main
import (
"C"
"fmt"
)
//export PrintLine
func PrintLine(text *C.char) {
fmt.Println(C.GoString(text))
}
func main() {
}
今回は単純に関数の引数で渡された文字列をfmt.Println()
で表示し、文字列をそのまま返す。というだけの関数PrintLine
を作成しています。ファイルはmruby-example
の下にgo
というディレクトリを作成し、その中に置きました。
go
ディレクトリに移動し、以下のようにビルドします。
$ go build -buildmode=c-archive -o libmain.a main.go
mruby(c)側の実装
基本的な構造はmruby-mrbgem-template
が作ってくれているので、ひな型mrbgem
の中のsrc
ディレクトリに移動し、mrb_example.c
を編集していきます。
今回は単純にgo側で定義した関数を呼び出して、戻り値で戻ってきた値を返して、mrubyスクリプトとgoの関数をブリッジする関数を足します。
static mrb_value mrb_example_hi(mrb_state *mrb, mrb_value self)
{
return mrb_str_new_cstr(mrb, "hi!!");
}
// 今回はこの部分の関数を定義
static mrb_value mrb_example_hi2(mrb_state *mrb, mrb_value self)
{
extern char* PrintLine(char*);
char *ret;
ret = PrintLine("hi!! from go");
return mrb_str_new_cstr(mrb, ret);
}
void mrb_mruby_example_gem_init(mrb_state *mrb)
{
struct RClass *example;
example = mrb_define_class(mrb, "Example", mrb->object_class);
mrb_define_method(mrb, example, "initialize", mrb_example_init, MRB_ARGS_REQ(1));
mrb_define_method(mrb, example, "hello", mrb_example_hello, MRB_ARGS_NONE());
mrb_define_class_method(mrb, example, "hi", mrb_example_hi, MRB_ARGS_NONE());
mrb_define_class_method(mrb, example, "hi2", mrb_example_hi2, MRB_ARGS_NONE()); // あと、この部分の忘れずに追加
DONE;
}
mrbgem.rake の編集
コードはできたので、次にビルドする為の設定を書いていきます。まずはmrbgem.rake
にgoのライブラリをリンクしましょう。私の環境がWindows
なので、余分にライブラリを追加していますが、mac
ならmain
のみで大丈夫なはずです。
MRuby::Gem::Specification.new('mruby-example') do |spec|
spec.license = 'MIT'
spec.authors = 'your-name'
# この2行を追加
spec.linker.libraries << %w(main ws2_32 winmm)
spec.linker.library_paths << File.join(__dir__, "go")
end
.travis_build_config.rb の編集
mac
の人は不要かもしれません。私の環境では以下の設定が必要でした。
MRuby::Build.new do |conf|
toolchain :gcc
conf.gembox 'default'
conf.gem File.expand_path(File.dirname(__FILE__))
conf.enable_test
conf.cc do |cc|
cc.command = 'x86_64-w64-mingw32-gcc'
end
conf.linker do |linker|
linker.command = 'x86_64-w64-mingw32-gcc'
end
conf.archiver do |archiver|
archiver.command = 'x86_64-w64-mingw32-gcc-ar'
end
end
ビルド
mruby-mrbgem-template
で作成したひな型mrbgem
にはRakefileがついていて、コマンド一発でビルドしてくれます。
$ rake
実行
ビルドが成功すると、./mruby/bin
の下にmruby
の実行ファイルができています。試しに以下のスクリプトを実行してみましょう。
puts Example.hi
puts Example.hi2
$ ./mruby/bin/mruby test.rb
hi!!
hi!! from go
hi!! from go
無事にgo側でfmt.Println()
した内容と戻り値をputs
した内容の両方が表示されていますね。
まとめ
mrubyを使って、goの関数の呼び出しと戻り値を使ってのmrubyスクリプトの実行を行いました。
go特有の並列処理をうまく組み合わせることができれば、もっと面白いことができるかもしれません。
また、C言語に限らず、mrubyを拡張する一つの方法になるのかもしれませんし、
逆にgo言語をmrubyで拡張していくことができるかもしれませんね。
今後の課題
もし、mrbgemの一部としてgoのソースを含めるのであれば、静的ライブラリのビルドも自動でできるようにしていきたい