概要
NativeExtensions でインスタンスメソッドを定義する場合大抵は FIX2INT
や rb_str_new2
を使って値を返します
ただケースとしてインスタンスメソッドが Struct (VALUE
) を返したい場合があると思います
かつ self
であれば簡単に返せますが self
ではない Struct を返したいケースもあると思います
今回はその方法を紹介します
環境
- macOS 10.14.3
- Ruby 2.5.1p57
サンプルコード
FirstExt というクラスと FirstExt という Struct 、SecondExt と SecondExt の Struct が紐付いています
FirstExt のインスタンスメソッドに second
というメソッドがありこれが SecondExt のインスタンスを返却しています
詳細は後述しています
#include <ruby.h>
VALUE se;
typedef struct {
char *name;
int age;
} FirstExt;
typedef struct {
char *address;
char *phone_number;
} SecondExt;
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 void second_ext_free(SecondExt *s) {
free(s);
}
static VALUE second_ext_alloc(VALUE self) {
SecondExt *s = ALLOC(SecondExt);
return Data_Wrap_Struct(self, 0, second_ext_free, s);
}
static VALUE wrap_second(VALUE self, VALUE _address, VALUE _phone_number) {
char *address = StringValuePtr(_address);
char *phone_number = StringValuePtr(_phone_number);
SecondExt *s;
Data_Get_Struct(self, SecondExt, s);
s->address = address;
s->phone_number = phone_number;
return Data_Wrap_Struct(se, 0, 0, s);
}
static VALUE wrap_show(VALUE self, VALUE _address, VALUE _phone_number) {
SecondExt *s;
Data_Get_Struct(self, SecondExt, s);
printf("%s\n", s->address);
printf("%s\n", s->phone_number);
return Qnil;
}
void Init_firstext(void) {
VALUE fe = rb_define_class("FirstExt", rb_cObject);
se = rb_define_class("SecondExt", rb_cObject);
rb_define_alloc_func(fe, first_ext_alloc);
rb_define_alloc_func(se, second_ext_alloc);
rb_define_method(fe, "second", wrap_second, 2);
rb_define_method(se, "show", wrap_show, 0);
}
ポイント
基本的な alloc
や free
、 rb_define_class
や rb_define_method
の説明は省略します
最大のポイントは wrap_second
メソッドです
static VALUE wrap_second(VALUE self, VALUE _address, VALUE _phone_number) {
char *address = StringValuePtr(_address);
char *phone_number = StringValuePtr(_phone_number);
SecondExt *s;
Data_Get_Struct(self, SecondExt, s);
s->address = address;
s->phone_number = phone_number;
return Data_Wrap_Struct(se, 0, 0, s);
}
一度 Data_Get_Struct
で構造体情報を取得し各フィールドに値をセットします
そして事前に rb_define_class
で定義した VALUE se
クラスを元に Data_Wrap_Struct
に構造体のポインタ (s
) と返り値として返したいクラス (se
) の情報を渡すことで Ruby で扱えるクラスのオブジェクトに変換し return
しています
この際に second_ext_free
の関数は指定しません
なぜならばここで作成したオブジェクトが参照されなくなった際には second_ext_free
が自動でコールされます
そこに更に second_ext_free
をコールするように指定してしまうと double free というメモリ参照エラーを起こしてしまうからです
テスト
テストしてみます
RSpec.describe FirstExt do
it "create a instance" do
cli = FirstExt.new
cli.second('123-4567', '080-1234-5678').show
end
end
こんな感じでメソッドチェイン的にコールできます
second
メソッドの返り値が SecondExt クラスのインスタンスなのでそのまま show
が呼べるようになっています
最後に
NativeExtensions の関数で self
ではない別のクラスのオブジェクトを返却する方法を紹介しました
Ruby だとあるメソッドの返り値がクラスになっていて、そのフィールドを参照するという使い方はよくあるので、NativeExtensions でそれを実現するためには必須の方法かなと思います
もしかするとこれがベストプラクティスではなく、これ以外にも方法があるかもしれません