mrubybindを拡張してみた


Keita Oboさんが作ったmrubybindに、私があると便利と考える機能を追加した。
コードはこちらから。

dycoon / mrubybind
https://github.com/dycoon/mrubybind

fork元はこちら

ktaobo / mrubybind
https://github.com/ktaobo/mrubybind

追加した機能は以下のとおり

* bindする関数の引数及び返り値に、代入可能な型すべてを設定可能に
* bindする関数の引数が関数の場合、それを呼ぶとmrubyのProcオブジェクトが呼ばれるように
* mrubyオブジェクトを保持するクラス(GC避け機能付き)と、それをbindする関数の引数及び返り値で扱えるように
* arena管理関係

std::function, std::shared_ptrやlambda記法などを使っているため、C++11を解釈するようにコンパイルする必要がある。

#　bindする関数の引数に、代入可能な型すべてを設定可能に

例えば以下のように書ける。

```c++
std::shared_ptr<ClassValue> create_class_value()
{
  return std::shared_ptr<ClassValue>(new ClassValue());
}

int class_value_get_a(std::shared_ptr<ClassValue> cv)
{
  return cv->a;
}

void install_class_value_function(mrb_state* mrb) {
  mrubybind::MrubyBind b(mrb);
  b.bind_class<std::shared_ptr<ClassValue> >("ClassValue");
  b.bind("create_class_value", create_class_value);
  b.bind("class_value_get_a", class_value_get_a);
}
```

```ruby
cv = create_class_value
puts "cv -> #{class_value_get_a(cv)}"
```

create_class_valueでは引数はstd::shared_ptr<ClassValue>が指定されている。
これによりmruby内でstd::shared_ptr<ClassValue>を保持するオブジェクトが作られ、
定義したclass_value_get_aでその値にアクセスできる。

mrubybindにもC++の値を保持する機能はあるが、
それは必ずmruby内で作られなければならない形で提供されていた。
例えば以下の様な形である。

```c++
class Foo {
public:
  ...
};

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);
  ...
}
```

```ruby
foo = Foo.new(123)  #=> Foo::ctor(123)
p foo
```

Fooをnewした場合にC++のオブジェクトが作られる(new_fooが呼ばれる)というものである。
Fooのコンストラクタを呼んだ時にnew_fooが呼ばれる。
返り値にC++のインスタンスの値などを返したい場合はnew_fooなどを呼ぶと不都合があるため
私の追加した機能を使う場合はnew_fooに相当する引数を渡さない方のbind_classメソッドを
使う必要がある。
混乱しやすい形になってしまっている。

# bindする関数の引数が関数の場合、それを呼ぶとmrubyのProcオブジェクトが呼ばれるように

例えば以下のように書ける。

```c++
std::string call_block(mrubybind::FuncPtr<void(int a0)> f) {
  if(f)
  {
      f.func()(23);
  }
  return "called\n";
}

void install_call_block_function(mrb_state* mrb) {
  mrubybind::MrubyBind b(mrb);
  b.bind("call_block", call_block);
}
```

```ruby
puts call_block { |a0|
  puts "a0 = #{a0}"
}
```

blockなどを呼ぶときに使いやすいかと思う。
mrubybind::FuncPtrは内部でmrubyのProcオブジェクトを持つため
以下記事で書いた
http://qiita.com/dycoon/items/fa8c66848bb37dc29454
GCを避ける必要があるが、
内部でGCを避ける機能を持っているため
ここではこの点を考える必要はない。

# mrubyオブジェクトを保持するクラス(GC避け機能付き)と、それをbindする関数の引数及び返り値で扱えるように

bindする関数の引数がmrubybind::MrubyRefの場合は
mrubyのオブジェクトがそのままmrubybind::MrubyRefから参照される形で引数に渡ってくる。

```c++
mrubybind::MrubyRef mruby_ref;

void set_mruby_ref(mrubybind::MrubyRef r){
  mruby_ref = r;
}

void install_mruby_ref_function(mrb_state* mrb) {
  mrubybind::MrubyBind b(mrb);
  b.bind("set_mruby_ref", set_mruby_ref);
}
```

``` mruby
set_mruby_ref "3test"
```
mrubyから渡ってきたオブジェクトに対して以下の様な操作をおこなえる。

```c++
std::cout << "mruby_ref = " << mruby_ref.to_s() << std::endl;
std::cout << "mruby_ref = " << mruby_ref.to_i() << std::endl;

// mrubyのgsubメソッドを呼ぶ。
std::cout << "mruby_ref = " << mruby_ref.call("gsub", "te", "toa").to_s() << std::endl;
```

mrubybind::MrubyRefも内部でmrubyオブジェクトの参照を持つため、
GC対策が必要だがその処理もmrubybind::MrubyRef側でおこなうようにしているため
ここライブラリではこの点を考える必要はない。

# arena管理関係

mrb_load_stringの返り値はarenaに積まれているため、開放はされないが
何度もmrb_load_stringを読んでいくとarenaが増えてしまい余分なメモリーを食ったり
arenaのサイズが固定されている場合は例外が発生する。
そのため適切なタイミングで
mrb_gc_arena_saveやmrb_gc_arena_restoreを使って
arenaのindexを調整する必要がある。
それをうまい具合にやってくれるクラスとして
mrubybind::MrubyArenaStoreを用意した。
mrubybind::load_stringでは以下のように使っている。

```c++
MrubyRef load_string(mrb_state* mrb, std::string code)
{
    mrubybind::MrubyArenaStore mas(mrb);
    
    RClass* mrubybind = mrb_define_module(mrb, "MrubyBind");
    mrb->exc = NULL;
    mrb_value r = mrb_load_string(mrb,
                    code.c_str());
    if(mrb->exc){
        mrb_obj_iv_set(mrb, (RObject*)mrubybind,
                       mrb_intern_cstr(mrb, untouchable_last_exception), mrb_obj_value(mrb->exc));
        r = mrb_nil_value();
    }
    else{
        mrb_obj_iv_set(mrb, (RObject*)mrubybind,
                       mrb_intern_cstr(mrb, untouchable_last_exception), mrb_nil_value());
    }
    
    return MrubyRef(mrb, r);
}
```
mrubybind::load_string関数を抜けたところで、arenaのindexが元に戻る。
mrubybind::load_stringはサンプルで例えば以下のように使用している。

```c++
mrubybind::load_string(mrb,
                  "puts square(1111)\n"
                  "puts emphasize('Hello, mruby!')\n"
                  );
```
こちらではarenaのインデックスが増えていかないようになっている。

# 今後の課題

mrb_stateが複数あった場合などを考慮して多少効率の悪そうな実装があるのが気になってはいる。
mrbstateのudをmrubybindで使うようにしてしまえばいろいろ効率が良くなりそうではある。
mruby_state自体を管理する機構が何かあってもいいのかもしれない。