概要
NativeExtensions の開発でブロックを渡せるメソッドを作成してみました
環境
- macOS 10.14.3
- Ruby 2.5.1p57
とりあえずブロックを文字列として扱う
まずは動かしてみましょう
ブロックの引数として受け取った引数に「hello」という文字列をセットするサンプルです
#include "ruby.h"
static VALUE wrap_yield_test(VALUE self) {
VALUE result;
if (!rb_block_given_p()) {
rb_raise(rb_eArgError, "Expected block");
}
result = rb_yield(rb_str_new2("hello"));
return result;
}
void Init_firstext() {
VALUE fe = rb_define_class("FirstExt", rb_cObject);
rb_define_method(fe, "yield_test", wrap_yield_test, 0);
}
呼び出す場合は以下のよういになります
ちゃんと文字列クラスのオブジェクトが返ってくるのでそのまま upcase
メソッドなどが呼べます
RSpec.describe FirstExt do
it "create a instance" do
fe = FirstExt.new
fe.yield_test { |c|
puts c.upcase
}
# fe.yield_test("a")
end
end
rb_block_given_p
は引数がブロックとして渡されているかチェックできます
ブロックでない場合は引数エラーを返しています
独自のクラスのオブジェクトを扱う
ブロック内で独自のクラスのオブジェクトを扱いケースは多いと思います
そのようなケースで使えるサンプルになります
急にコード量が増えてしまいます、、、
#include "ruby.h"
#include "stdio.h"
VALUE fe;
typedef struct {
char *name;
} 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_yield_test(VALUE self) {
VALUE result;
if (!rb_block_given_p()) {
rb_raise(rb_eArgError, "Expected block");
}
FirstExt *p;
Data_Get_Struct(self, FirstExt, p);
result = rb_yield(Data_Wrap_Struct(fe, 0, 0, p));
return result;
}
static VALUE wrap_set_name(VALUE self, VALUE _name) {
FirstExt *p;
Data_Get_Struct(self, FirstExt, p);
p->name = StringValuePtr(_name);
return Qnil;
}
static VALUE wrap_get_name(VALUE self, VALUE _name) {
FirstExt *p;
Data_Get_Struct(self, FirstExt, p);
return rb_str_new2(p->name);
}
void Init_firstext() {
fe = rb_define_class("FirstExt", rb_cObject);
rb_define_alloc_func(fe, first_ext_alloc);
rb_define_method(fe, "yield_test", wrap_yield_test, 0);
rb_define_method(fe, "name=", wrap_set_name, 1);
rb_define_method(fe, "name", wrap_get_name, 0);
}
まず、クラスに紐づく Struct が必要になるので定義します
そして (おなじみの) alloc と free 関数を準備し rb_define_alloc_func
で登録します
ポイントは rb_yield(Data_Wrap_Struct(fe, 0, 0, p));
で FirstExt Struct を Ruby のオブジェクトに変換し rb_yield
に渡してる点です
これでブロックに渡された引数にクラスのオブジェクトが設定されます
使い方は以下の通りです
RSpec.describe FirstExt do
it "create a instance" do
fe = FirstExt.new
fe.yield_test { |c|
c.name = "hawk"
}
puts fe.name
end
end
ブロック内でオブジェクトの初期化をして、それを別の場所で参照するみたいな使い方ができるようになります
最後に
NativeExtensions でブロック引数を扱う方法を紹介しました
ポイントは rb_yield
かなと思います
0 件のコメント:
コメントを投稿