C++側で定義されているクラスをmrubyに持ち込む方法を調べた。参考にしたのは
mruby-time/time.cや
mruby/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_TTで
MRB_TT_DATAにする(これは
Hogeクラスのインスタンスの型はDATA型だということを設定しているのだろうか?)。コンストラクタ
initializeでC++のクラスのインスタンスを生成し、
DATA_PTR(self)にセットしてやる。また型情報
hoge_typeを
DATA_TYPE(self)にセットしてやる(これをしないとSegmentation faultが発生する)。
あとはC++のメンバ関数に対して
mrb_define_method()でバインドしてやればmruby側からメソッドとして呼び出すことができる。
ここでは試しに、
inspectや
to_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()で解放してるけど、解放関数を用意するのであればそうする必要はないのではないだろうか。