您的方法将 C 数组复制到 Ruby 数组中。您可以通过创建一个 Ruby 集合类来避免这种情况,该集合类使用Data_Wrap_Struct
并直接作用于 C 数组。
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);
NoteData_Get_Struct
是一个宏,用法有点奇怪,因为它不返回 unwrapped 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);
这可能需要一些工作来为字段创建所有 getter 和 setter,这取决于您如何使用它。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);
在这个例子中,我没有关心对象标识和内存管理之类的事情。使用同一个数组支持的所有内容,您可以拥有多个共享相同数据的对象,您必须考虑这是否适合您的使用。此外,您可能已经注意到我已经malloc
ed 但不是free
d。您需要确定谁“拥有”数据数组,并确保不会引入任何内存泄漏。您可以传递一个函数Data_Wrap_Struct
,当对象被垃圾收集以释放内存时将调用该函数。
如果您还没有看过,Pickaxe 书中有一章很好地介绍了 C 扩展,现在可以在线获得。