はじめに †
2012/9/9にMobiRubyがリリースされました。というわけでMobiRubyを読むことにします。
まずはCの関数をRubyから呼び出すためのライブラリであるmruby-cfuncから読解を始めます。
test/main.c †
「MobiRubyってどこから読み始めればいいのかわからない」とつぶやいたところ、増井さんから「mruby-cfuncとmruby-cocoaのtestからどうぞ」とのお返事をいただきました。というわけで、test/main.cから読解を開始します。
test/main.cのうち、main()は以下のようになっています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| -
|
!
-
|
|
|
|
|
|
|
-
|
!
|
|
-
|
!
!
| struct mrb_state_ud {
struct cfunc_state cfunc_state;
};
int main(int argc, char *argv[])
{
mrb_state *mrb = mrb_open();
mrb->ud = malloc(sizeof(struct mrb_state_ud));
cfunc_state_offset = cfunc_offsetof(struct mrb_state_ud, cfunc_state);
init_cfunc_module(mrb);
init_unittest(mrb);
if (mrb->exc) {
mrb_p(mrb, mrb_obj_value(mrb->exc));
}
init_cfunc_test(mrb);
if (mrb->exc) {
mrb_p(mrb, mrb_obj_value(mrb->exc));
}
}
|
mrb_stateはmrubyの実行情報を格納する構造体です。mrb_stateにはudという追加情報へのポインタが含まれており、mruby-cfuncの追加情報を格納するのに使用されていることがわかります。
なお、cfunc_state_offsetはudで指定している構造体中のcfunc_state構造体のオフセットを表すグローバル変数です。mruby-cfunc中ではこの情報が使われるのでinit_cfunc_module()を呼び出す前に設定する必要があります。
init_cfunc_module(src/cfunc.c) †
init_cfunc_module()にてmruby-cfuncの初期化が行われます。条件コンパイルになっていますがデフォルトはオフなのでその部分は省いて載せます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-
|
|
|
|
|
|
|
|
|
|
|
|
!
| void init_cfunc_module(mrb_state *mrb)
{
struct RClass *ns = mrb_define_module(mrb, "CFunc");
cfunc_state(mrb)->namespace = ns;
init_cfunc_type(mrb, ns);
init_cfunc_pointer(mrb, ns);
init_cfunc_struct(mrb, ns);
init_cfunc_closure(mrb, ns);
init_cfunc_call(mrb, ns);
mrb_define_class_method(mrb, ns, "mrb_state", cfunc_mrb_state, ARGS_NONE());
init_cfunc_rb(mrb);
}
|
というわけで、型、ポインタ、構造体、クロージャ、呼び出しについて初期化が行われるようです。
init_cfunc_type(src/cfunc_type.c) †
CFunc::TypeをスーパークラスとしたC(libffi)の型を表現するクラスの定義を行っています。関数ポインタとマクロ、必要情報の埋め込みを使うことで型ごとに必要となる定義をコンパクトにまとめています。具体的には、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
-
|
!
-
|
!
-
-
|
!
-
|
!
!
-
-
|
!
-
|
!
!
| #define define_cfunc_type(name, ffi_type_ptr, ctype, c_to_mrb, mrb_to_c) \
\
static mrb_value \
cfunc_type_ffi_##name##_c_to_mrb(mrb_state *mrb, void *p) \
{ \
return c_to_mrb(*(ctype*)p); \
} \
\
static void \
cfunc_type_ffi_##name##_mrb_to_c(mrb_state *mrb, mrb_value val, void *p) \
{ \
*(ctype*)p = mrb_to_c(val); \
} \
\
static mrb_value \
cfunc_type_ffi_##name##_data_to_mrb(mrb_state *mrb, struct cfunc_type_data *data) \
{ \
if(data->refer) { \
return c_to_mrb(*(ctype*)data->value._pointer); \
} \
else { \
return c_to_mrb(data->value._##name); \
} \
} \
\
static void \
cfunc_type_ffi_##name##_mrb_to_data(mrb_state *mrb, mrb_value val, struct cfunc_type_data *data) \
{ \
if(data->refer) { \
*(ctype*)data->value._pointer = mrb_to_c(val); \
} \
else { \
data->value._##name = mrb_to_c(val); \
} \
}
define_cfunc_type(sint8, &ffi_type_sint8, int8_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(uint8, &ffi_type_uint8, uint8_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(sint16, &ffi_type_sint16, int16_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(uint16, &ffi_type_uint16, uint16_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(sint32, &ffi_type_sint32, int32_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(uint32, &ffi_type_uint32, uint32_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(sint64, &ffi_type_sint64, int64_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(uint64, &ffi_type_uint64, uint64_t, mrb_fixnum_value, mrb_fixnum);
define_cfunc_type(float, &ffi_type_float, float, mrb_float_value, mrb_float);
define_cfunc_type(double, &ffi_type_double, double, mrb_float_value, mrb_float);
|
といった感じにRubyとCの変換関数を定義し、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
-
|
|
|
|
|
|
!
-
|
|
|
|
|
|
|
|
|
|
|
!
| #define define_mrb_ffi_type(name_, type_) \
{ \
.name = #name_, \
.ffi_type_value = &ffi_type_##type_, \
.mrb_to_c = &cfunc_type_ffi_##type_##_mrb_to_c, \
.c_to_mrb = &cfunc_type_ffi_##type_##_c_to_mrb, \
.mrb_to_data = &cfunc_type_ffi_##type_##_mrb_to_data, \
.data_to_mrb = &cfunc_type_ffi_##type_##_data_to_mrb \
}
static struct mrb_ffi_type types[] = {
define_mrb_ffi_type(Void, void),
define_mrb_ffi_type(UInt8, uint8),
define_mrb_ffi_type(SInt8, sint8),
define_mrb_ffi_type(UInt16, uint16),
define_mrb_ffi_type(SInt16, sint16),
define_mrb_ffi_type(UInt32, uint32),
define_mrb_ffi_type(SInt32, sint32),
define_mrb_ffi_type(UInt64, uint64),
define_mrb_ffi_type(SInt64, sint64),
define_mrb_ffi_type(Float, float),
define_mrb_ffi_type(Double, double)
};
|
と各型について変換に使う関数を関数ポインタとして設定することで記述量を最小化しています。
そして上記で定義した情報を各型クラスにインスタンス変数*1として持たせておくことでinitialize等もスーパークラスのTypeに対して定義したものだけで動くようになっています。って、initializeは長いのでvalueの実装関数であるcfunc_type_get_value()を載せます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
-
|
|
|
!
-
-
|
-
|
!
|
!
|
|
!
| mrb_value
cfunc_type_get_value(mrb_state *mrb, mrb_value self)
{
struct cfunc_type_data *data = (struct cfunc_type_data*)DATA_PTR(self);
struct mrb_ffi_type *mft = rclass_to_mrb_ffi_type(mrb, RBASIC_KLASS(self));
return mft->data_to_mrb(mrb, data);
}
struct mrb_ffi_type*
rclass_to_mrb_ffi_type(mrb_state *mrb, struct RClass *cls)
{
while(cls) {
mrb_value ffi_type = mrb_obj_iv_get(mrb, (struct RObject*)cls, mrb_intern(mrb, "ffi_type"));
if(mrb_test(ffi_type)) {
return (struct mrb_ffi_type*)DATA_PTR(ffi_type);
}
cls = cls->super;
}
mrb_raise(mrb, E_TYPE_ERROR, "Cannot convert to c value");
return NULL;
}
|
init_cfunc_pointer(src/cfunc_pointer.c) †
Cのポインタを表現するCFunc::Pointerの定義を行っています。特筆することはありません。
init_cfunc_struct(src/cfunc_struct.c) †
Cの構造体を表現するCFunc::Structの定義を行っています。cfunc_struct.cに書かれているのはRubyでCの構造体を定義するためのメソッドの実装関数のみです。他のメソッドは?という疑問は後で説明します。(ネタバレ禁止)
init_cfunc_closure(src/cfunc_closure.c) †
次はクロージャを表現するCFunc::Closureの定義です。Cで言うクロージャとは関数ポインタのことです。正確にはクロージャは自分が定義されているスコープの変数を参照できるので関数ポインタとはちょっと違いますが。ともかく、Rubyで定義したクロージャをCの関数に関数ポインタとして渡すことを実現するための仕組みです。
init_cfunc_call(cfunc_call.c) †
次にCの関数を呼び出すCFunc::callを定義しています。
init_cfunc_rb †
最後にinit_cfunc_rb()が呼び出されています。このinit_cfunc_rb()ですが、grepしてもヒットしません(ビルド前の場合)。実はinit_cfunc_rb()はsrc/mrb/cfunc_rb.rbをコンパイルすることで作られます。そのからくりは、以下のようになっています。
src/Makefile
# mrby complie
$(OBJMRB) : %.o : %.rb
$(MRBC) -Cinit_$(*F) $<
$(CC) $(ALL_CFLAGS) -MMD $(INCLUDES) -c $(basename $<).c -o $@
というわけでビルドプロセスとしてsrc/mrb/cfunc_rb.rbがコンパイルされてCコードになり、init_cfunc_rb()という関数が作られます。cfunc_rb.rbでは既存クラスのmruby-cfunc向けの拡張、Rubyで書かれたmruby-cfuncの追加メソッドが記述されています。先ほどのCFunc::Structのメンバーアクセスメソッドとかもcfunc.rbに記述されています。
init_cfunc_test †
mruby-cfuncの初期化が終わったので次は使い方の方を見ていきます。init_cfunc_test()はmruby-cfuncの使い方サンプルになっています。init_cfunc_test()の元はtest/mrb/cfunc_test.rbです。
配列の作り方 †
以下のように書きます。
ci = CFunc::CArray(CFunc::Int).new(10)
個人的には、
ci = CFunc::Int[10]
と書けた方がうれしいです。pull request送ってみるか。
メモリ確保方法 †
以下のように書きます。
@ptr = CFunc::Pointer.malloc(7)
C関数の呼び出し方 †
以下のように書きます。strcpyの戻り値は実際にはchar*ですが戻り値が必要ないならvoidにしてもいいようです。
CFunc::call(CFunc::Void, "strcpy", @ptr, @str)
構造体の定義 †
以下のように書きます。
class STest < CFunc::Struct
define CFunc::SInt8, :x,
CFunc::SInt16, :y,
CFunc::SInt32, :z
end
個人的にはやや気に食わない。RubyのStructのように
CFunc::Struct.new("STest", CFunc::SInt8, :x, CFunc::SInt16, :y, CFunc::SInt32, :z)
と定義できた方がうれしいです。まだ型と名前の間の","が気に食わないけど仕方ないかな。
構造体メンバーへのアクセス †
以下のように書きます。
@stest[:x] = 10
ん〜、これもメンバーアクセスメソッドを用意して
@stest.x = 10
と書きたい。釣られてるのか?俺。
クロージャの定義 †
以下のように書きます。第1引数が戻り値で第2引数に配列でクロージャの引数の型を書きます。
@closure = CFunc::Closure.new(CFunc::Int, [CFunc::Int, CFunc::Int]) do |a, b|
a.value * b.value
end
@closureを渡すことで、
int closure(int a, int b)
{
return a * b;
}
という関数を定義して関数ポインタが要求されるところに渡すことと同じことになります。
おわりに †
今回はmruby-cfuncを眺めました。読んでいて、「Cで書かないといけないことはCで書き、Rubyで書ける(Rubyで書いた方が生産性がいい)ところはRubyで書く」というmrubyの思想を実践しているように感じました。