mruby advent calendar 2013 12日目の記事です。
今回はmrubyとCとの基本的な連携の仕方について解説します。
具体的には以下のやり方について解説していきます。
- Cでmrubyのモジュールを定義する
- Cでmrubyのクラスを定義する
- Cでmrubyの定数を定義する
- Cでmrubyのメソッドを定義する
- Cでmrubyのハッシュテーブルを生成
- mrubyからCで作ったハッシュテーブルにset/getするメソッドを定義する
前準備
まずはmrubyをダウンロード&ビルドします。
$ git clone https://github.com/mruby/mruby
$ cd mruby
$ ruby ./minirake
mrubyのビルドに関するより詳細な解説については@masuidriveさんの記事が参考になります。
次に以下のようなmrubyスクリプトを実行することを考えてみます。.
puts Hoge::TRUE # => 1
puts Hoge::FALSE # => 0
Hoge.hello() # => Hello, mruby!
puts Hoge.add(2, 3) # => 5
Hoge::Table.set("name", "cubicdaiya")
Hoge::Table.set("github", "https://github.com/cubicdaiya")
Hoge::Table.set("twitter", "https://twitter.com/cubicdaiya")
puts Hoge::Table.get("name") # => cubicdaiya
puts Hoge::Table.get("github") # => https://github.com/cubicdaiya
puts Hoge::Table.get("twitter") # => https://twitter.com/cubicdaiya
このスクリプトを実行するには、
- Hogeモジュールを定義する
- Hoge::(TRUE|FALSE)を定義する
- Hoge.(hello|add)メソッドを定義する
- ハッシュテーブルを持つHoge::Tableクラスを定義する
- Hoge::Tableにメソッド(get, set)を追加する
といったことが必要です。当然mrubyだけでもできますが、今回はmrubyのAPIを使ってCで実現する方法について解説します。
まずはhoge.mrbをコンパイルするCプログラムを書いてみます。こんな感じです。(簡略化のため、エラー処理は極力省いています)
#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/string.h>
static struct RProc *compile(mrb_state *mrb, const char *path)
{
FILE *mrb_file;
struct mrb_parser_state *p;
struct RProc* proc;
if ((mrb_file = fopen(path, "r")) == NULL) {
return NULL;;
}
p = mrb_parse_file(mrb, mrb_file, NULL);
fclose(mrb_file);
proc = mrb_generate_code(mrb, p);
if (proc == NULL) {
mrb_pool_close(p->pool);
return NULL;
}
mrb_pool_close(p->pool);
return proc;
}
int main(int argc, char *argv[])
{
mrb_state *mrb;
struct RProc *proc;
const char *path = "hoge.mrb";
struct RString *err_str;
// mrubyのステートマシンを生成
mrb = mrb_open();
// =======================================
// 何か書く
// =======================================
// mrubyスクリプトをコンパイル
if((proc = compile(mrb, path)) == NULL) {
mrb_close(mrb);
printf("failed to compile:%s\n", path);
return 1;
}
// mrubyスクリプトを実行
mrb_run(mrb, proc, mrb_top_self(mrb));
if (mrb->exc) {
err_str = mrb_str_ptr(mrb_funcall(mrb, mrb_obj_value(mrb->exc), "inspect", 0));
printf("mrb_run failed:%s\n", err_str->as.heap.ptr);
}
// mrubyのステートマシンを解放
mrb_close(mrb);
return 0;
}
これを実行するとエラーになります。
$ pwd
~/mruby
$ gcc -o hoge hoge.c build/host/lib/libmruby.a -Iinclude -Iinclude/mruby/
$ ./hoge
mrb_run failed:NameError: uninitialized constant Hoge
$
前置きが長くなりました。というわけでさきほど列挙した処理をCで記述していきましょう。
Hogeモジュールを定義する
モジュールを定義するにはmrb_define_moduleを利用します。簡単ですね。
struct RClass *hoge_module;
hoge_module = mrb_define_module(mrb, "Hoge");
定数Hoge::(TRUE|FALSE)を定義する
定数の定義にはmrb_define_constを利用します。
mrb_define_const(mrb, hoge_module, "TRUE", mrb_fixnum_value(1));
mrb_define_const(mrb, hoge_module, "FALSE", mrb_fixnum_value(0));
今回は整数型を利用していますが、その他の型についてはmruby/include/value.hを見るとよいでしょう。
Hoge::Tableクラスを定義する
クラスを定義するにはmrb_define_class_underを利用します。
struct RClass *table_class;
table_class = mrb_define_class_under(mrb, hoge_module, "Table", mrb->object_class);
今回はHogeモジュールでTableクラスを定義していますが、トップレベルで定義するには第二引数にhoge_moduleの替わりにmrb->kernel_moduleを指定します。
table_class = mrb_define_class_under(mrb, mrb->kernel_module, "Table", mrb->object_class);
Hoge.(hello|add)メソッドを定義する
今回はクラスメソッドなのでmrb_define_class_methodを利用します。
mrb_define_class_method(mrb, hoge_module, "hello", hello, MRB_ARGS_NONE());
mrb_define_class_method(mrb, hoge_module, "add", add, MRB_ARGS_ANY());
第3引数が実際のメソッド名、第4引数がCの関数へのポインタで最後が受け取る引数のシグネチャになります(MRB_ARGS_NONE()は引数なし)。
helloおよびadd関数の定義はこんな感じです。(例のごとくエラー処理は簡略化のため省いています)
static mrb_value hello(mrb_state *mrb, mrb_value self)
{
printf("Hello, mruby!\n");
return self;
}
static mrb_value add(mrb_state *mrb, mrb_value self)
{
mrb_int a, b;
mrb_get_args(mrb, "ii", &a, &b);
return mrb_fixnum_value(a + b);
}
ハッシュテーブルの作成およびHoge::Table.(set|get)メソッドの定義
Cでmrubyのハッシュテーブルを作成するにはmrb_hash_new関数を利用します。
table = mrb_hash_new(mrb);
さらにさきほど作ったHoge::Tableクラスにgetとsetメソッドを追加します。
mrb_define_class_method(mrb, table_class, "get", table_get, MRB_ARGS_ANY());
mrb_define_class_method(mrb, table_class, "set", table_set, MRB_ARGS_ANY());
table_getおよびtable_setの定義は以下のようになります。
static mrb_value table_get(mrb_state *mrb, mrb_value self)
{
mrb_value key;
mrb_value table;
table = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "table"));
if (mrb_nil_p(table)) {
table = mrb_hash_new(mrb);
mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "table"), table);
}
mrb_get_args(mrb, "o", &key);
return mrb_hash_get(mrb, table, key);
}
static mrb_value table_set(mrb_state *mrb, mrb_value self)
{
mrb_value key, val;
mrb_value table;
mrb_get_args(mrb, "oo", &key, &val);
table = mrb_iv_get(mrb, self, mrb_intern_lit(mrb, "table"));
if (mrb_nil_p(table)) {
table = mrb_hash_new(mrb);
}
mrb_hash_set(mrb, table, key, val);
mrb_iv_set(mrb, self, mrb_intern_lit(mrb, "table"), table);
return self;
}
完成コード
完成版のコードは以下のgistをご覧ください。
まとめ
CからmrubyのAPIを使ってモジュールやクラス、定数、ハッシュテーブルを扱う方法について解説しました。
個人的にmrubyのCインターフェースについてはスタックが全てのLuaに比べてリッチで使いやすいなという印象です。