mrubyからc経由でgoの関数を呼ぶ

go言語はビルド時に`buildmode=c-archive`を指定すると、静的ライブラリを生成することができます。

```bash
$ go build -buildmode=c-archive main.go
$ ls
main.a main.go main.h
```

今回はこれをmrubyに組み込んでみたいと思います。

# mruby-mrbgem-template で mrbgemのひな型を作成

以下のコマンドでひな型を作成し、作成したディレクトリに移動します。

```bash
$ git clone https://github.com/matsumotory/mruby-mrbgem-template
$ cd mruby-mrbgem-template
$ rake
$ cd mruby-example
```

# go側の実装

[golang の Windows 版が buildmode=c-archive をサポートした。](https://mattn.kaoriya.net/software/lang/go/20160405114638.htm) を参考に作成します。

```go:main.go
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`ディレクトリに移動し、以下のようにビルドします。

```bash
$ go build -buildmode=c-archive -o libmain.a main.go
```

# mruby(c)側の実装

基本的な構造は`mruby-mrbgem-template`が作ってくれているので、ひな型`mrbgem`の中の`src`ディレクトリに移動し、`mrb_example.c`を編集していきます。

今回は単純にgo側で定義した関数を呼び出して、戻り値で戻ってきた値を返して、mrubyスクリプトとgoの関数をブリッジする関数を足します。

```c:mrb_exmaple.c
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`のみで大丈夫なはずです。

```ruby:mrbgem.rake
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`の人は不要かもしれません。私の環境では以下の設定が必要でした。

```ruby:.travis_build_config.rb
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がついていて、コマンド一発でビルドしてくれます。

```bash
$ rake
```

# 実行

ビルドが成功すると、`./mruby/bin`の下に`mruby`の実行ファイルができています。試しに以下のスクリプトを実行してみましょう。

```rb:test.rb
puts Example.hi
puts Example.hi2
```

```bash
$ ./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のソースを含めるのであれば、静的ライブラリのビルドも自動でできるようにしていきたい