mrubyでC言語のGEMを作成する
rubyの方言に組み込み向け用のmrubyというものがあります。rubyには簡単にライブラリが組み込めます。mrubyにも便利なライブラリがあったらいいですよね。
mrubyはまだ若い言語ですが、GEMがあります。
rubyはGEMをあとからインストールできますが、mrubyのGEMはmruby本体と同時にビルドします。ではまず、mrubyをGitHubから取得してビルドしてみましょう。
$ git clone https://github.com/mruby/mruby.git
$ cd murby$ ls
AUTHORS MITL TODO doc mrblib
CONTRIBUTING.md Makefile benchmark examples src
ChangeLog NEWS bin include tasks
INSTALL README.md build minirake test
LEGAL Rakefile build_config.rb mrbgems tools
mrubyのソースを取得できたのでビルドします。
$ make
ruby ./minirake
(in /Users/xxxxxx/work/mruby/origin/mruby)
CC tools/mruby/mruby.c -> build/host/tools/mruby/mruby.o
CC src/array.c -> build/host/src/array.o
CC src/class.c -> build/host/src/class.o
CC src/codegen.c -> build/host/src/codegen.o
省略
Build summary:
================================================
Config Name: host
Output Directory: build/host
Binaries: mruby, mrbc, mirb
================================================
エラーがでずに上記のようにbuild summaryが表示されたら、ビルド成功です。mrubyが動作するかどうか確かめてみましょう。
$ cd bin
$ ls
mirb mrbc mruby
$ ./mruby -e 'p 1+1'
2
-eオプションはワンライナーと言いまして、一行でmrubyのコードを実行するオプションです。1+1の結果が2と表示されました。
毎回binディレクトリで実行するのは面倒ですのでパスを通します。私はmac OS Xを使用していますので、~/.profileに以下のように記載しました。
export MRUBY=$HOME/mruby
ホームの直下のmrubyディレクトリがあるとします。
source ./profileで有効にします。
$ source ./profile
$ mruby -e 'p 1 + 2'
3
これでどこでもmrubyを実行出来るようになりました。
次は、ビルドの設定ファイルについて見てみましょう。
mrubyのルートに戻ります。
$ cd ..
$ cd murby$ ls
AUTHORS MITL TODO doc mrblib
CONTRIBUTING.md Makefile benchmark examples src
ChangeLog NEWS bin include tasks
INSTALL README.md build minirake test
LEGAL Rakefile build_config.rb mrbgems tools
build_config.rbがビルドの設定ファイルです。rubyで記述されています。
MRuby::Build.new do |conf|
# load specific toolchain settings
toolchain :gcc
# Use mrbgems
# conf.gem 'examples/mrbgems/ruby_extension_example'
# conf.gem 'examples/mrbgems/c_extension_example' do |g|
# g.cc.flags << '-g' # append cflags in this gem
# end
# conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
# conf.gem :github => 'masuidrive/mrbgems-example', :branch => 'master'
# conf.gem :git => 'git@github.com:masuidrive/mrbgems-example.git', :branch => 'master', :options => '-v'
# 省略
end
GEMを作成するにはC言語、ruby、C言語とrubyの3種類がありますが、ここはC言語で作成してみようと思います。インタフェースはrubyで実装はC言語で高速にすることがライブラリとしてよいのではないかと思ったからです。
すでにmruby/examples/mrbgems/c_extension_exampleディレクトリにサンプルがありますのでそれを使用します。config.gem のあとにGEMのパスを記述します。
MRuby::Build.new do |conf|
# load specific toolchain settings
toolchain :gcc
# Use mrbgems
conf.gem 'examples/mrbgems/c_extension_example'
別端末を起動し、サンプルディレクトリに移動します。
$ cd mruby/examples/mrbgems/c_extension_example
$ ls
README.md mrbgem.rake src test
mrbgem.rakeのgemの情報を書き込みます。
spec.licenseがラインセンス、spec.authorsが著者権者になります。公開するときは書き換えましょう。
MRuby::Gem::Specification.new('c_extension_example') do |spec|
spec.license = 'MIT'
spec.authors = 'mruby developers'
end
srcがC言語のソースになります。
$ cd src
$ ls
example.c
example.cをテキストエディタで開きます。
#include <mruby.h>
#include <stdio.h>
static mrb_value
mrb_c_method(mrb_state *mrb, mrb_value self)
{
puts("A C Extension");
return self;
}
void
mrb_c_extension_example_gem_init(mrb_state* mrb) {
struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, ARGS_NONE());
}
void
mrb_c_extension_example_gem_final(mrb_state* mrb) {
// finalizer
}
まずはそのままサンプルをビルドしてみましょう。先ほど、ビルドした端末でビルドします。
$ make
エラーがでなければビルド成功です。
別端末を起動します。mrubyを実行できるように~/.profileを読み込みます。
$ source ~/.profile
適当な場所でgemを実行するファイルを作成します。
example.rb
# encoding : utf-8
# 引数なしメソッド
CExtension::c_method
それでは実行してみましょう。
$ mruby example.rb A C Extension
CExtension::c_methodメソッドが実行され、A C Extensionと表示されました。
GEMのソースexample.cを見てみましょう。
CExtension::c_methodメソッドがよばれるとmrb_c_method関数が呼ばれます。
static mrb_value
mrb_c_method(mrb_state *mrb, mrb_value self)
{
puts("A C Extension");
return self;
}
初期化です。
void mrb_c_extension_example_gem_init(mrb_state* mrb)
{
struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, ARGS_NONE());
}
mrb_define_class_methodの第2引数"c_method"がrubyでのメソッド名、第3引数mrb_c_methodが第2引数で指定されたメソッドが呼ばれたときに実行されるC言語の関数となります。
終了処理です。
void mrb_c_extension_example_gem_final(mrb_state* mrb)
{
// finalizer
}
それでは、rubyからGEMを呼び出してみましょう。rubyのコードです。
example.rb
# encoding : utf-8
# 引数なしメソッド
CExtension::c_method
実行してみましょう。
$ ruby example.rb
A C Extension
mrb_c_method関数が実行されて、「A C Extension」と表示されました。 次は引数に整数、戻り値に整数を返すメソッドを実装してみましょう。
example.c
#include <stdio.h>
#include "mruby.h"
#include "mruby/variable.h"
static mrb_value
mrb_c_method(mrb_state *mrb, mrb_value self)
{
puts("A C Extension");
return self;
}
// インクリメントする
static mrb_value
mrb_inc(mrb_state *mrb, mrb_value self)
{
mrb_int i;
mrb_get_args(mrb, "i", &i);
i++;
return mrb_fixnum_value(i);
}
void
mrb_c_extension_example_gem_init(mrb_state* mrb) {
struct RClass *class_cextension = mrb_define_module(mrb, "CExtension");
mrb_define_class_method(mrb, class_cextension, "c_method", mrb_c_method, ARGS_NO\
NE());
mrb_define_class_method(mrb, class_cextension, "inc", mrb_inc, ARGS_ANY());
}
void
mrb_c_extension_example_gem_final(mrb_state* mrb) {
// finalizer
}
example.cにC言語で整数をインクリメントするmrb_inc()関数を追加しました。mrb_c_extension_example_gem_init()関数に>mrb_define_class_method(mrb, class_cextension, "inc", mrb_inc, ARGS_ANY())を追加しました。第4引数に引数任意を指定しました。
example_inc.rb
# encoding : utf-8
# インクリメント
x = 2
puts "x=#{x}"
x = CExtension::inc(x)
puts "インクリメント"
puts "x=#{x}"
実行してみましょう。
$ mruby example_inc.rb x=2 インクリメント x=3
xの値が2から3にインクリメントされました。
1: static mrb_value
2: mrb_inc(mrb_state *mrb, mrb_value self)
3: {
4: mrb_int i;
5: mrb_get_args(mrb, "i", &i);
6: i++;
7: return mrb_fixnum_value(i);
8: }
1行目mrb_valueはmrubyの値を表す構造体です。
4行目はmruby内部のintを表します。
5行目でmrubyメソッドの第1引数をiに代入します。
6行目でインクリメントします。
7行目のmrb_fixnum_value関数でrubyの整数にとして戻り値を返します。
C言語のintとmrubyは同じではないので変換する必要があります。
他にfloatやtrue,false,nilをmrb_valueとして表す関数があります。
インスタンスメソッドや他の型も扱える方法を調べていきたいと思います。