ruby c 扩展如何管理 2 个对象之间的垃圾回收
ruby c extension how to manage garbage collection between 2 objects
我有一个 C 扩展,其中有一个主要的 class(例如 class A)是用 classical:
创建的
Data_Wrap_Struct
rb_define_alloc_func
rb_define_private_method(mymodule, "initialize" ...)
这个A class有一个生成B对象的实例方法。那些 B 对象只能从 A 对象生成,并根据包装在 A 实例中的数据包装 C 数据。
如果 A 对象在 B 对象之前被垃圾收集器收集,这可能会导致段错误。
如何告诉 GC 在他的一些 B 对象仍然存在时不要收集 A 实例。我想我必须使用 rb_gc_mark 或类似的东西。每次创建 B 对象时都必须标记 A 实例吗??
编辑:更具体的信息
我正在尝试编写 Clang 扩展。使用 clang,您首先创建一个 CXIndex,您可以从中获得一个 CXTranslationUnit,您可以从中获得一个 CXDiagnostic 和/或 CXCursor 等等。这是一个简单的例子:
Clangc::Index#new => Clangc::Index
Clangc::Index#create_translation_unit => Clangc::TranslationUnit
Clangc::TranslationUnit#diagnostic(index) => Clangc::Diagnostic
您可以在此处查看一些代码:https://github.com/cedlemo/ruby-clangc
编辑 2:解决方案
通过引用 "a" 对象构建 "b" 对象的内容:
typedef struct B_t {
void * data;
VALUE instance_of_a;
} B_t;
static void
c_b_struct_free(B_t *s)
{
if(s)
{
if(s->data)
a_function_to_free_the_data(s->data);
ruby_xfree(s);
}
}
static void
c_b_mark(void *s)
{
B_t *b =(B_t *)s;
rb_gc_mark(b->an_instance_of_a);
}
VALUE
c_b_struct_alloc( VALUE klass)
{
B_t * ptr;
ptr = (B_t *) ruby_xmalloc(sizeof(B_t));
ptr->data = NULL;
ptr->an_instance_of_a = Qnil;
return Data_Wrap_Struct(klass, c_b_mark, c_b_struct_free, (void *) ptr);
}
用于从 "a" 对象构建 "b" 对象的 c 函数:
VALUE c_A_get_b_object( VALUE self, VALUE arg)
{
VALUE mModule = rb_const_get(rb_cObject, rb_intern("MainModule"));\
VALUE cKlass = rb_const_get(mModule, rb_intern("B"));
VALUE b_instance = rb_class_new_instance(0, NULL, cKlass);
B_t *b;
Data_Get_Struct(b_instance, B_t, b);
/*
transform ruby value arg to C value c_arg
*/
b->data = function_to_fill_the_data(c_arg);
b->instance_of_a = self;
return b_instance;
}
在Init_mainModule函数中:
void Init_mainModule(void)
{
VALUE mModule = rb_define_module("MainModule");
/*some code ....*/
VALUE cKlass = rb_define_class_under(mModule, "B", rb_cObject);
rb_define_alloc_func(cKlass, c_b_struct_alloc);
}
rb_gc_mark 的相同用法可以在项目 https://github.com/brianmario/mysql2
的 mysql2/ext/mysql2/client.c ( rb_mysql_client_mark function
) 中找到
在 B class 的标记函数中,您应该标记 A Ruby 对象,告诉垃圾收集器不要对其进行垃圾收集。
mark 函数可以指定为 Data_Wrap_Struct 的第二个参数。您可能需要以某种方式修改设计以公开指向 A 对象的指针。
另一种选择是让A对象成为B对象的实例变量。您可能无论如何都应该这样做,以便 Ruby 代码可以从 B 对象获取 A 对象。这样做会产生副作用,使垃圾收集器不会在收集 B 之前收集 A,但是您不应该依赖这种副作用,因为您的 Ruby 代码可能会不小心弄乱实例变量然后导致段错误。
编辑: 另一种选择是使用共享 C 数据的引用计数。然后,当使用该共享数据的最后一个 Ruby 对象被垃圾回收时,您将删除共享数据。这将涉及找到一种好的、跨平台的、线程安全的方法来进行引用计数,因此它可能不是微不足道的。
我有一个 C 扩展,其中有一个主要的 class(例如 class A)是用 classical:
创建的Data_Wrap_Struct
rb_define_alloc_func
rb_define_private_method(mymodule, "initialize" ...)
这个A class有一个生成B对象的实例方法。那些 B 对象只能从 A 对象生成,并根据包装在 A 实例中的数据包装 C 数据。
如果 A 对象在 B 对象之前被垃圾收集器收集,这可能会导致段错误。
如何告诉 GC 在他的一些 B 对象仍然存在时不要收集 A 实例。我想我必须使用 rb_gc_mark 或类似的东西。每次创建 B 对象时都必须标记 A 实例吗??
编辑:更具体的信息
我正在尝试编写 Clang 扩展。使用 clang,您首先创建一个 CXIndex,您可以从中获得一个 CXTranslationUnit,您可以从中获得一个 CXDiagnostic 和/或 CXCursor 等等。这是一个简单的例子:
Clangc::Index#new => Clangc::Index
Clangc::Index#create_translation_unit => Clangc::TranslationUnit
Clangc::TranslationUnit#diagnostic(index) => Clangc::Diagnostic
您可以在此处查看一些代码:https://github.com/cedlemo/ruby-clangc
编辑 2:解决方案
通过引用 "a" 对象构建 "b" 对象的内容:
typedef struct B_t {
void * data;
VALUE instance_of_a;
} B_t;
static void
c_b_struct_free(B_t *s)
{
if(s)
{
if(s->data)
a_function_to_free_the_data(s->data);
ruby_xfree(s);
}
}
static void
c_b_mark(void *s)
{
B_t *b =(B_t *)s;
rb_gc_mark(b->an_instance_of_a);
}
VALUE
c_b_struct_alloc( VALUE klass)
{
B_t * ptr;
ptr = (B_t *) ruby_xmalloc(sizeof(B_t));
ptr->data = NULL;
ptr->an_instance_of_a = Qnil;
return Data_Wrap_Struct(klass, c_b_mark, c_b_struct_free, (void *) ptr);
}
用于从 "a" 对象构建 "b" 对象的 c 函数:
VALUE c_A_get_b_object( VALUE self, VALUE arg)
{
VALUE mModule = rb_const_get(rb_cObject, rb_intern("MainModule"));\
VALUE cKlass = rb_const_get(mModule, rb_intern("B"));
VALUE b_instance = rb_class_new_instance(0, NULL, cKlass);
B_t *b;
Data_Get_Struct(b_instance, B_t, b);
/*
transform ruby value arg to C value c_arg
*/
b->data = function_to_fill_the_data(c_arg);
b->instance_of_a = self;
return b_instance;
}
在Init_mainModule函数中:
void Init_mainModule(void)
{
VALUE mModule = rb_define_module("MainModule");
/*some code ....*/
VALUE cKlass = rb_define_class_under(mModule, "B", rb_cObject);
rb_define_alloc_func(cKlass, c_b_struct_alloc);
}
rb_gc_mark 的相同用法可以在项目 https://github.com/brianmario/mysql2
的 mysql2/ext/mysql2/client.c (rb_mysql_client_mark function
) 中找到
在 B class 的标记函数中,您应该标记 A Ruby 对象,告诉垃圾收集器不要对其进行垃圾收集。
mark 函数可以指定为 Data_Wrap_Struct 的第二个参数。您可能需要以某种方式修改设计以公开指向 A 对象的指针。
另一种选择是让A对象成为B对象的实例变量。您可能无论如何都应该这样做,以便 Ruby 代码可以从 B 对象获取 A 对象。这样做会产生副作用,使垃圾收集器不会在收集 B 之前收集 A,但是您不应该依赖这种副作用,因为您的 Ruby 代码可能会不小心弄乱实例变量然后导致段错误。
编辑: 另一种选择是使用共享 C 数据的引用计数。然后,当使用该共享数据的最后一个 Ruby 对象被垃圾回收时,您将删除共享数据。这将涉及找到一种好的、跨平台的、线程安全的方法来进行引用计数,因此它可能不是微不足道的。