mrubyをCのプログラムに組み込んで使う方法を調べる。今のところ公式のドキュメントはHello Worldくらいしか用意されてない?ヘッダファイルmruby.hmruby/value.hなどから使えそうな関数を推測して用いることになりそうだ。

文字列を直接実行

#include <mruby.h>
#include <mruby/compile.h>

int main() {
  mrb_state* mrb = mrb_open();
  mrb_load_string(mrb, "puts 'hello world'");
  mrb_close(mrb);
  return 0;
}
// 実行結果:
// hello world
  • mrb_open()でコンテキスト作成、mrb_close()で終了
  • mrb_load_string()で実行

確認用として使う分にはよいだろうが、コンパイルが入るので何度も実行するには向かないでしょう。

C言語からmrubyの関数を呼び出す

#include <mruby.h>

int main() {
  mrb_state* mrb = mrb_open();
  mrb_value v = mrb_str_new_cstr(mrb, "Hello, mrb func!");
  mrb_funcall(mrb, mrb_top_self(mrb), "puts", 1, v);
  mrb_close(mrb);
  return 0;
}
// 実行結果:
// Hello, mrb func!
  • mrb_funcall(mrb, オブジェクト, 関数名, 引数の数, 引数...)でmrubyの関数呼び出し
  • mrb_str_new_cstr()でCの文字列からmrubyの文字列を生成

Cの関数を登録してmrubyから呼び出す

#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/string.h>

static mrb_value plus(mrb_state *mrb, mrb_value self) {
  mrb_value a, b;
  mrb_get_args(mrb, "oo", &a, &b);
  if (mrb_fixnum_p(a) && mrb_fixnum_p(b)) {
    mrb_int x = mrb_fixnum(a);
    mrb_int y = mrb_fixnum(b);
    return mrb_fixnum_value(x + y);
  } else if (mrb_string_p(a) && mrb_string_p(b)) {
    mrb_value s = mrb_str_plus(mrb, a, b);
    return s;
  } else {
    return mrb_nil_value();
  }
}

int main() {
  mrb_state* mrb = mrb_open();
  mrb_define_method(mrb, mrb->kernel_module, "plus", plus, MRB_ARGS_REQ(2));

  mrb_load_string(mrb,
                  "puts plus(1, 2);"
                  "puts plus('foo', 'bar');");
  if (mrb->exc) {
    mrb_p(mrb, mrb_obj_value(mrb->exc));
  }
  mrb_close(mrb);
  return 0;
}
// 実行結果:
// 3
// foobar
  • mrb_define_method(mrb, モジュール, メソッド名, Cの関数, 要求する引数)でCの関数を登録する
    • 要求する引数は、MRB_ARGS_REQ, MRB_ARGS_OPT, MRB_ARGS_REST, MRB_ARGS_BLOCKなどを組み合わせて使う
  • 登録するCの関数は、mrb_func_t
    • typedef mrb_value (*mrb_func_t)(mrb_state *mrb, mrb_value);
  • mrb_get_args(mrb, 引数の型, 受け取る変数のポインタ...)で、引数の受け取り
    • i=整数、S=文字列、o=オブジェクト、*=残りすべて、など
  • 変数に入っている値の型を調べるには、mrb_fixnum_p()mrb_string_p()などが使える。
  • 関数から返すmrb_valueの値がmruby側への戻り値となる。

コンパイル済みのバイトコードを実行する

#include <mruby.h>
#include <mruby/dump.h>
#include <mruby/proc.h>

int main() {
  // Created on mrbc:                                                                                                                                                                                                                        
  //   $ mrbc -Bfoo foo.rb                                                                                                                                                                                                                   
  //                                                                                                                                                                                                                                         
  // foo.rb:                                                                                                                                                                                                                                 
  //   (0...5).each do |i|                                                                                                                                                                                                                   
  //     puts i                                                                                                                                                                                                                              
  //   end                                                                                                                                                                                                                                   
static
const uint8_t foo[] = {
0x52,0x49,0x54,0x45,0x30,0x30,0x30,0x31,0x63,0x5c,0x00,0x00,0x00,0x90,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x72,0x30,0x30,
0x30,0x30,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x33,0x00,0x01,0x00,0x03,0x00,0x00,
0x00,0x06,0x00,0xbf,0xff,0x83,0x01,0x40,0x02,0x03,0x00,0x80,0x40,0xc1,0x01,0x00,
0x03,0x40,0x00,0x80,0x00,0x21,0x00,0x00,0x00,0x4a,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x01,0x00,0x04,0x65,0x61,0x63,0x68,0x00,0x00,0x00,0x00,0x2f,0x00,0x03,0x00,
0x05,0x00,0x00,0x00,0x05,0x02,0x00,0x00,0x26,0x01,0x80,0x00,0x06,0x02,0x00,0x40,
0x01,0x01,0x80,0x00,0xa0,0x01,0x80,0x00,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x04,0x70,0x75,0x74,0x73,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};

  mrb_state* mrb = mrb_open();
  int n = mrb_read_irep(mrb, foo);
  if (n >= 0) {
    mrb_run(mrb, mrb_proc_new(mrb, mrb->irep[n]), mrb_top_self(mrb));
  } else {
    printf("Failed to load byte code.\n");
  }
  mrb_close(mrb);
  return 0;
}
  • 実行ファイルmrbcを使えばRubyのソースコードをmruby用のバイトコードにコンパイルできる。-BオプションでC言語の配列形式に出力してくれる。
  • mrb_read_irep()にバイトコードを食わせた戻り値が正の場合に読み込み成功、mrb_proc_new(), mrb_run()を使って実行。