首页 > 解决方案 > 如何从 C 结构创建 Ruby 对象

问题描述

我正在尝试使用 C API 制作一个 Ruby 模块。我必须承认,我无法完全理解它的在线文档,但我正在尝试使用来自另一个类方法的 C 结构的数据返回一个 Ruby 对象(如果这没有意义,抱歉)。这是我的问题的一个例子:

例子.c

#include "ruby.h"
#include "extconf.h"

typedef struct example1_t
{
    int x;
} example1_t;

typedef struct example2_t
{
    char *name;
} example2_t;


void example1_free(example1_t *e1);
void example2_free(example2_t *e2);


static VALUE rb_example1_alloc(VALUE klass)
{
    return Data_Wrap_Struct(klass, NULL, example1_free, ruby_xmalloc(sizeof(example1_t)));
}

static VALUE rb_example1_init(VALUE self, VALUE x)
{
    example1_t *e1;

    Check_Type(x, T_FIXNUM);

    Data_Get_Struct(self, example1_t, e1);

    e1->x = NUM2INT(x);

    return self;
}

static VALUE rb_example1_x(VALUE self)
{
    example1_t *e1;

    Data_Get_Struct(self, example1_t, e1);

    return INT2NUM(e1->x);
}

static VALUE rb_example2_alloc(VALUE klass)
{
    return Data_Wrap_Struct(klass, NULL, example2_free, ruby_xmalloc(sizeof(example2_t)));
}

static VALUE rb_example2_init(VALUE self, VALUE s)
{
    example2_t *e2;

    Check_Type(s, T_STRING);

    Data_Get_Struct(self, example2_t, e2);

    e2->name = (char*)malloc(RSTRING_LEN(s) + 1);
    memcpy(e2->name, StringValuePtr(s), RSTRING_LEN(s) + 1);

    return self;
}

static VALUE rb_example2_name(VALUE self)
{
    example2_t *e2;

    Data_Get_Struct(self, example2_t, e2);

    return rb_str_new_cstr(e2->name);
}

static VALUE rb_example2_name_len(VALUE self)
{
    example1_t *len;
    example2_t *e2;

    Data_Get_Struct(self, example2_t, e2);

    len->x = strlen(e2->name);

    /*

    How do I make a new Example1 Ruby Class from the "len" 
    structure and return it with the length of e2->name 
    assigned to len->x?

    */
 return it?
}

void Init_example()
{
    VALUE mod = rb_define_module("Example");
    VALUE example1_class = rb_define_class_under(mod, "Example1", rb_cObject);
    VALUE example2_class = rb_define_class_under(mod, "Example2", rb_cObject);

    rb_define_alloc_func(example1_class, rb_example1_alloc);
    rb_define_alloc_func(example2_class, rb_example2_alloc);

    rb_define_method(example1_class, "initialize", rb_example1_init, 1);
    rb_define_method(example1_class, "x", rb_example1_x, 0);

    rb_define_method(example2_class, "initialize", rb_example2_init, 1);
    rb_define_method(example2_class, "name", rb_example2_name, 0);
    rb_define_method(example2_class, "name_len", rb_example2_name_len, 0);
}


void example1_free(example1_t *e1)
{
    memset(e1, 0, sizeof(example1_t));
}

void example2_free(example2_t *e2)
{
    memset(e2, 0, sizeof(example2_t));
}

正如您在 中看到的rb_example2_name_len,我想创建一个Example1类并从Example2方法中返回它。我怎么能做到这一点?

任何帮助深表感谢。

标签: crubyclass

解决方案


您可以使用它rb_class_new_instance来创建新对象。

您将需要VALUE表示类对象。获得它的一种方法是将其存储在全局或静态变量中,然后在 init 函数中对其进行初始化,而不仅仅是使用局部变量:

static VALUE example1_class;

//...

void Init_example()
{
    //...
    example1_class = rb_define_class_under(mod, "Example1", rb_cObject);
    //...
}

您还需要将参数转换为 Ruby 形式。rb_class_new_instance需要一个数组VALUES

static VALUE rb_example2_name_len(VALUE self)
{
    example2_t *e2;
    Data_Get_Struct(self, example2_t, e2);

    // Format arguments as array of VALUES
    VALUE args[1];
    args[0] = INT2NUM((int)strlen(e2->name));

    // args are length of array, pointer to array and class you are
    // creating an instance of. example1_class is available here because
    // we made it a static variable.
    VALUE e1 = rb_class_new_instance(1, args, example1_class);
    return e1;
}

制作静态的另一种方法example1_class是使用rb_const_get. 在这种情况下,您还必须获取包含模块:

VALUE mExample = rb_const_get(rb_cObject, rb_intern("Example"));
VALUE cExample1 = rb_const_get(mExample, rb_intern("Example1"));

VALUE e1 = rb_class_new_instance(1, args, cExample1);

rb_class_new_instance基本上只是调用你的分配函数,然后是你的初始化程序,所以你可以自己在里面重现该代码rb_example2_name_len

example1_t *e1_struct =  ruby_xmalloc(sizeof(example1_t));
e1_struct->x = (int)strlen(e2->name);

VALUE e1 = Data_Wrap_Struct(example1_class, NULL, example1_free, e1_struct);
return e1;

这将避免需要将数据转换为 ruby​​ 格式,以便立即将其转换回来,但我认为您不会获得太多收益,并且使用rb_class_new_instance可能更清晰。


您还应该知道Data_Wrap_Structstruct 宏已被弃用

旧的(非类型化的)Data_XXX 宏系列已被弃用。在 Ruby 的未来版本中,旧的宏可能无法工作。

为了简单起见,我在这里使用了它们,但您可能想尝试使用较新的TypedData_宏。


推荐阅读