2013年3月30日土曜日

mrubybindにC++のクラスバインダを追加した

Cの任意の関数をmrubyにバインドできるmrubybindに、C++のクラスやメンバ関数をバインドする機能を追加した。

mrubybind - github

使い方は、コンストラクタ用のヘルパー関数を用意してbind_class()でクラスをバインド、bind_class_method()でメソッドをバインドできる。
Foo* new_foo(int x) {
  return new Foo(x);
}

void install_foo_class(mrb_state* mrb) {
  mrubybind::MrubyBind b(mrb);
  b.bind_class("Foo", new_foo);
  b.bind_class_method("Foo", "bar", &Foo::bar);
}

するとmrubyから呼び出せるようになる:
foo = Foo.new(123)
p foo.bar(567)
ガベージコレクト時にはC++のクラスのデストラクタがちゃんと呼ばれる。


以下は実装の説明。

2013年3月29日金曜日

mrubyにC++のクラスを持ち込む

C++側で定義されているクラスをmrubyに持ち込む方法を調べた。参考にしたのはmruby-time/time.cmruby/C構造体組み込みを読む - Code Reading Wiki。正しいかどうかは保証できないけれど、一応ちゃんと動いているようです。

C++側になにかクラス
class Hoge {
public:
  Hoge(int x) : x_(x) {
    std::cout << "Hoge::ctor()" << std::endl;
  }
  virtual ~Hoge() {
    std::cout << "Hoge::dtor()" << std::endl;
  }
  int x() const { return x_; }

private:
  int x_;
};
があったとして、これをmruby側から扱いたい場合には、DATA型を使うといいようだ:
#include <mruby.h>
#include <mruby/class.h>
#include <mruby/data.h>

static void hoge_free(mrb_state *mrb, void *ptr) {
  Hoge* hoge = static_cast<Hoge*>(ptr);
  delete hoge;
}

static struct mrb_data_type hoge_type = { "Hoge", hoge_free };

static mrb_value hoge_initialize(mrb_state *mrb, mrb_value self) {
  // Initialize data type first, otherwise segmentation fault occurs.
  DATA_TYPE(self) = &hoge_type;
  DATA_PTR(self) = NULL;

  mrb_int x;
  mrb_get_args(mrb, "i", &x);
  Hoge* hoge = new Hoge(x);

  DATA_PTR(self) = hoge;
  return self;
}

static mrb_value hoge_to_s(mrb_state *mrb, mrb_value self) {
  Hoge* hoge = static_cast<Hoge*>(mrb_get_datatype(mrb, self, &hoge_type));
  char buf[32];
  snprintf(buf, sizeof(buf), "#Hoge<%d>", hoge->x());
  return mrb_str_new_cstr(mrb, buf);
}

void install_hoge_class(mrb_state* mrb) {
  struct RClass *tc = mrb_define_class(mrb, "Hoge", mrb->object_class);
  MRB_SET_INSTANCE_TT(tc, MRB_TT_DATA);
  mrb_define_method(mrb, tc, "initialize", hoge_initialize, ARGS_REQ(1));
  mrb_define_method(mrb, tc, "inspect", hoge_to_s, ARGS_NONE());
  mrb_define_method(mrb, tc, "to_s", hoge_to_s, ARGS_NONE());
}
DATA型用の型情報hoge_typeを用意する、それには解放時に呼ばれる関数を登録できる。あとはmrb_define_class()でmrubyのクラスを定義する。そしてMRB_SET_INSTANCE_TTMRB_TT_DATAにする(これはHogeクラスのインスタンスの型はDATA型だということを設定しているのだろうか?)。コンストラクタinitializeでC++のクラスのインスタンスを生成し、DATA_PTR(self)にセットしてやる。また型情報hoge_typeDATA_TYPE(self)にセットしてやる(これをしないとSegmentation faultが発生する)。
あとはC++のメンバ関数に対してmrb_define_method()でバインドしてやればmruby側からメソッドとして呼び出すことができる。
ここでは試しに、inspectto_sを定義している。データポインタの取り出しはmrb_get_datatype()でできて、型が違った場合はNULLが返る(上のコードではチェックしてない)。

ここまですれば、あとはmruby側から呼び出せる:
#include <mruby.h>
#include <mruby/compile.h>

int main() {
  mrb_state* mrb = mrb_open();
  install_hoge_class(mrb);
  std::cout << "Start" << std::endl;
  mrb_load_string(mrb,
                  "hoge = Hoge.new(123)\n"
                  "p hoge\n"
                  "hoge = nil\n"
                  );
  std::cout << "End" << std::endl;
  mrb_close(mrb);
  return 0;
}
// 実行結果:
// Start
// Hoge::ctor()
// #Hoge<123>
// End
// Hoge::dtor()
ガベコレされるときにちゃんと解放関数が呼び出される。

  • mruby-timeではDATA型を使う以外に、Time.nowなどのクラスメソッドではData_Wrap_Struct()マクロを使ってラップオブジェクトを作ってるみたいなんだけど、よくわからず…
  • mruby-timeでは、Cの構造体mrb_timeもmrb_malloc()でメモリ確保、mrb_free()で解放してるけど、解放関数を用意するのであればそうする必要はないのではないだろうか。

2013年3月19日火曜日

C++用のmrubyの関数バインダを作った

mrubyに任意の型のC言語の関数を登録するためのバインダを作りました。
mrubybind - Binding library for mruby/C++
使い方は簡単で、mrubybind.hをインクルードしてMrubyBindというクラスのインスタンスを生成する。そして、あるC言語の関数foobarがあったとき、
#include "mrubybind.h"
void init(mrb_state* mrb) {
  mrubybind::MrubyBind b(mrb);
  b.bind("foobar", foobar);
}
とすれば、mrubyからその登録した名前で呼び出すことができる。mruby側から渡した引数が自動的にCの関数に渡り、その関数からの戻り値がmruby側に戻る。関数をバインドした後はMrubyBindのオブジェクトは捨ててしまってokです。



以下は実装の説明。