概要
NativeExtensions で accessor (所謂 getter/setter) を作ってみたいと思います
当然ですが attr_accessor
のようなメソッドはないので accessor となるメソッドを準備する必要があります
環境
- macOS 10.14.3
- Ruby 2.5.1p57
サンプルコード
#include <ruby.h>
typedef struct {
char *name;
int age;
} FirstExt;
static void first_ext_free(FirstExt *s) {
free(s);
}
static VALUE first_ext_alloc(VALUE self) {
FirstExt *s = ALLOC(FirstExt);
return Data_Wrap_Struct(self, 0, first_ext_free, s);
}
static VALUE wrap_set_name(VALUE self, VALUE _name) {
char *name = StringValuePtr(_name);
FirstExt *s;
Data_Get_Struct(self, FirstExt, s);
s->name = name;
return Qnil;
}
static VALUE wrap_get_name(VALUE self) {
FirstExt *s;
Data_Get_Struct(self, FirstExt, s);
return rb_str_new2(s->name);
}
void Init_firstext(void) {
VALUE fe = rb_define_class("FirstExt", rb_cObject);
rb_define_alloc_func(fe, first_ext_alloc);
rb_define_method(fe, "name=", wrap_set_name, 1);
rb_define_method(fe, "name", wrap_get_name, 0);
}
テストコード
RSpec.describe FirstExt do
it "create a instance" do
cli = FirstExt.new
cli.name = 'hawksnowlog'
puts cli.name
end
end
ポイント
setter の定義をするのに rb_define_method(fe, "name=", wrap_set_name, 1);
としているのが最大のポイントだと思います
普通にオペレーションを含む関数名を定義することができるようです
これで Ruby の accessor のように構造体のメンバを参照することができます
ただこのままだとの実装だと 1 つ問題になる点があります
それは name
に何もセットされていない状態で cli.name
として参照すると Segmentation fault が発生してしまいます
なので getter となる wrap_get_name
関数は以下のように NULL チェックを入れることが望ましいです
static VALUE wrap_get_name(VALUE self) {
FirstExt *s;
Data_Get_Struct(self, FirstExt, s);
if (RTEST(s->name)) {
return rb_str_new2(""); // return Qnil;
} else {
return rb_str_new2(s->name);
}
}
これで値が設定されていない場合は空の文字列が返るので Segmentation fault が発生しません
こんな感じのチェックは NativeExtensions の開発では日常茶飯事で出てくるので覚えておいて損はないテクニックだと思います
最後に
NativeExtensions で getter/setter を実装する方法を紹介しました
おそらくこれが一番単純で簡単なやり方かなと思います
今回はクラスに対応する Struct のメンバにアクセスする accessor を作りましたが単純に Struct に対して同じことをしても良いかなと思います
0 件のコメント:
コメントを投稿