涼風コンピュータblog

涼風 ・Rubyist, RubyやRuby on Railsに詳しくなっていきたいです

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言語rubyC言語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として表す関数があります。

インスタンスメソッドや他の型も扱える方法を調べていきたいと思います。