このメソッドは、C配列をRuby配列にコピーします。Data_Wrap_Struct
を使用してC配列をラップし、それに直接作用するRubyコレクションクラスを作成することで、これを回避できます。
Data_Wrap_Struct
は、RubyクラスとC struct
(および、オプションで、意図的に省略しているメモリ管理用の関数へのいくつかのポインター)を取り、struct
「アタッチ」されたそのクラスのインスタンスを作成するマクロです。このクラスの実装を提供する関数では、メソッドを使用して、関数でアクセスできるData_Get_Struct
を「アンラップ」します。struct
この場合、次のようになります。
// declare a variable for the new class
VALUE rb_cSPacketCollection;
// a struct that will be wrapped by the class
struct SPacketDataStruct {
struct SPacket * data;
int count;
};
VALUE rb_GetAllData() {
struct SPacketDataStruct* wrapper = malloc(sizeof (struct SPacketCollectionWrapper));
wrapper->data = GetAllData(&wrapper->count);
return Data_Wrap_Struct(rb_cSPacketCollection, 0, 0, wrapper);
}
メソッドでInit_whatever()
クラスを作成する必要があります。
rb_cSPacketCollection = rb_define_class("SPacketCollection", rb_cObject);
これだけではあまり役に立ちません。この新しいクラスでいくつかのメソッドを定義する必要があります。[]
例として、個々SPacket
のsへのアクセスを許可するメソッドを作成できます。
VALUE SPacketCollection_get(VALUE self, VALUE index) {
// unwrap the struct
struct SPacketDataStruct* wrapper;
Data_Get_Struct(self, struct SPacketDataStruct, wrapper);
int i = NUM2INT(index);
// bounds check
if (i >= wrapper->count) {
rb_raise(rb_eIndexError, "Index out of bounds");
}
// just return an array in this example
VALUE arr = rb_ary_new2(4);
rb_ary_store(arr, 0, UINT2NUM(wrapper->data[i].field1));
rb_ary_store(arr, 1, UINT2NUM(wrapper->data[i].field2));
rb_ary_store(arr, 2, UINT2NUM(wrapper->data[i].field3));
rb_ary_store(arr, 3, UINT2NUM(wrapper->data[i].field4));
return arr;
}
次に、Init_
メソッドで、クラスを作成した後、メソッドを定義します。
rb_define_method(rb_cSPacketCollection, "[]", SPacketCollection_get, 1);
注Data_Get_Struct
はマクロであり、ラップされていないを返さないという点で、使用法は少し奇妙struct
です。
この段階で使用を開始したので、さらに進んで、個々の構造体をラップし、それを直接操作するData_Wrap_Struct
新しいクラスを作成できます。SPacket
// declare a variable for the new class
VALUE rb_cSPacket;
//and a function to get a field value
// you'll need to create more methods to access the other fields
// (and possibly to set them)
VALUE SPacket_field1(VALUE self) {
struct SPacket* packet;
Data_Get_Struct(self, struct SPacket, packet);
return UINT2NUM(packet->field1);
}
関数で、Init_
関数を作成し、メソッドを定義します。
rb_cSPacket = rb_define_class("SPacket", rb_cObject);
rb_define_method(rb_cSPacket, "field1", SPacket_field1, 0);
これには、フィールドのすべてのゲッターとセッターを作成するための少しの作業が必要になる場合があります。これは、使用方法によって異なります。ここではffiのようなものが役立つ可能性がありますが、ffiがコレクションクラスをどのように処理するかはわかりません。おそらく調査する価値があります。
[]
次に、この新しいクラスの場合にインスタンスを返すように関数を変更します。
VALUE SPacketCollection_get(VALUE self, VALUE index) {
//unwrap the struct
struct SPacketDataStruct* wrapper;
Data_Get_Struct(self, struct SPacketDataStruct, wrapper);
int i = NUM2INT(index);
//bounds check
if (i >= wrapper->count) {
rb_raise(rb_eIndexError, "Index out of bounds");
}
//create an instance of the new class, and wrap it around the
//struct in the array
struct SPacket* packet = &wrapper->data[i];
return Data_Wrap_Struct(rb_cSPacket, 0, 0, packet);
}
これにより、Rubyで次のようなことができるようになります。
c = get_all_data # in my testing I just made this a global method
c[2].field1 # returns the value of field1 of the third SPacket in the array
each
コレクションクラスにメソッドを作成する価値があるかもしれません。そうすれば、Enumerable
モジュールを含めて、メソッドのロードを利用できるようにすることができます。
VALUE SPacketCollection_each(VALUE self) {
//unwrap the struct as before
struct SPacketDataStruct* wrapper;
Data_Get_Struct(self, struct SPacketDataStruct, wrapper);
int i;
for(i = 0; i < wrapper->count; i++) {
//create a new instance if the SPacket class
// wrapping this entry
struct SPacket* packet = &wrapper->data[i];
rb_yield(Data_Wrap_Struct(rb_cSPacket, 0, 0, packet));
}
return self;
}
でInit_whatever
:
rb_define_method(rb_cSPacketCollection, "each", SPacketCollection_each, 0);
rb_include_module(rb_cSPacketCollection, rb_mEnumerable);
この例では、オブジェクトIDやメモリ管理などについては気にしませんでした。すべてが同じ配列に支えられているため、すべてが同じデータを共有する複数のオブジェクトを持つことができるため、これが使用に問題がないかどうかを検討する必要があります。また、あなたは私がmalloc
編集したが、free
dではないことに気づいたかもしれません。データアレイを「所有」しているのは誰かを特定し、メモリリークが発生しないようにする必要があります。Data_Wrap_Struct
オブジェクトがガベージコレクションされてメモリを解放するときに呼び出される関数を渡すことができます。
まだご覧になっていない場合は、Pickaxeの本にC拡張機能に関する優れた章があり、オンラインで入手できるようになりました。