ruby c扩展如何管理2个对象之间的垃圾收集

时间:2015-05-23 08:54:53

标签: ruby garbage-collection ruby-c-extension

我有一个C扩展,其中我有一个用经典创建的主类(例如A类):

Data_Wrap_Struct
rb_define_alloc_func
rb_define_private_method(mymodule, "initialize" ...)

此A类具有生成B对象的实例方法。这些B对象只能从A对象生成,并且包含的​​C数据取决于A实例中包含的数据。

我在B对象之前由垃圾收集器收集A对象,这可能导致Seg Fault。

如何在他的某些B对象仍然存在的情况下告诉GC不收集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:解决方案

构建" b"引用" a"的对象对象:

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);
}

用于构建" b"的c函数。来自" a"对象:

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)中找到

1 个答案:

答案 0 :(得分:1)

在B类的mark函数中,你应该标记一个Ruby对象,告诉垃圾收集器不要垃圾收集它。

可以将mark函数指定为Data_Wrap_Struct的第二个参数。您可能需要以某种方式修改您的设计以暴露指向A对象的指针。

另一个选择是让A对象成为B对象的实例变量。无论如何你应该这样做,以便Ruby代码可以从B对象获取A对象。这样做会产生副作用,使垃圾收集器不会在B之前收集A,但是你不应该依赖这种副作用,因为你的Ruby代码可能会意外地搞乱实例变量然后导致分段错误。

编辑:另一种选择是使用共享C数据的引用计数。然后,当使用该共享数据的最后一个Ruby对象被垃圾收集时,您将删除共享数据。这将涉及找到一个好的,跨平台的,线程安全的方式来进行引用计数,因此它可能不是微不足道的。