Data_Wrap_Struct和销毁订单

时间:2014-07-24 11:20:04

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

我正在为物理引擎编写Ruby扩展。此物理引擎具有链接到世界主体,因此我的Ruby对象为WorldBody。使用world->CreateBody构造一个正文(使用C ++编写)并使用world->DestroyBody进行销毁。

问题是Ruby GC在身体之前摧毁了世界。因此,当GC破坏物体时,世界不再存在,并且我得到分段错误。我知道我需要为GC标记某些内容(使用rb_gc_mark),但我不知道在哪里。


World类很标准,看起来像这样:

extern "C" void world_free(void *w)
{
    static_cast<World*>(w)->~World();
    ruby_xfree(w);
}

extern "C" void world_mark(void *w)
{
    // ???
}

extern "C" VALUE world_alloc(VALUE klass)
{
    return Data_Wrap_Struct(klass, world_mark, world_free, ruby_xmalloc(sizeof(World)));
}

extern "C" VALUE world_initialize(VALUE self)
{
    World* w;
    Data_Get_Struct(self, World, w);
    new (w) World();
    return self;
}

Body类略有不同,因为它需要从World对象创建(我不能简单地new它)。所以它看起来像这样:

extern "C" void body_free(void* b)
{
    Body* body = static_cast<Body*>(b);
    World* world = body->GetWorld();
    world->DestroyBody(body);
}

extern "C" void body_mark(void* b)
{
    // ???
}

extern "C" VALUE body_alloc(VALUE klass)
{
    return Data_Wrap_Struct(klass, body_mark, body_free, 0);
}

extern "C" VALUE static_obj_initialize(VALUE self, VALUE world)
{
    Body* b;
    World* w;

    Data_Get_Struct(self, Body, b);
    Data_Get_Struct(world, World, w);

    b = w->CreateBody();
    DATA_PTR(self) = b;

    return self;
}

所以我的问题是:

  1. 我应该在GC上标记两个对象中的哪一个?
  2. 我该怎么办?我只是用rb_gc_mark标记,还是只在某些情况下才能这样做?哪些?
  3. 我应该标记什么?标记函数只接收指向我的结构的裸指针,但rb_gc_mark函数需要VALUE

1 个答案:

答案 0 :(得分:1)

您应该能够将VALUE World注册为与所有Body实例关联的生命周期。

这意味着在创建它们时,您将要执行以下操作:

extern "C" VALUE static_obj_initialize(VALUE self, VALUE world)
{
    Body* b;
    World* w;

    Data_Get_Struct(self, Body, b);
    Data_Get_Struct(world, World, w);

    b = w->CreateBody();
    DATA_PTR(self) = b;
    rb_gc_register_address(&world);

    return self;
}

您希望在Body被销毁时取消注册:

extern "C" void body_free(void* b)
{
    Body* body = static_cast<Body*>(b);
    World* world = body->GetWorld();
    world->DestroyBody(body);
    rb_gc_unregister_address(&world); //almost right
}

但这并不准确,因为rb_gc_unregister_address()需要VALUE *。您应该能够编写一个简单的包装器(或使用std :: pair&lt;&gt;)来承载世界VALUE

警告:我还没有测试过任何上述代码,但它应该是方向正确的。