2

I am trying to make a Ruby module using the C API. I must admit, I am having trouble fully understanding the documentation online for it, but I am trying to return a Ruby Object using data from a C Structure from another class method (sorry if that doesn't make sense). Here is an example of my problem:

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

As you can see in the rb_example2_name_len, I would like to create an Example1 class and return that from an Example2 method. How would I be able to do this?

Any help is much appreciated.

1 Answer 1

1

You can use rb_class_new_instance to create new objects.

You will need the VALUE representing the class object. One way to get this would be to store it in a global or static variable and then initialize it in the init function rather than just having a local variable:

static VALUE example1_class;

//...

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

You will also need to convert the arguments to Ruby form. rb_class_new_instance takes an array of 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;
}

An alternative to making example1_class a static would be to use rb_const_get. In this case you would have to get the containing module as well:

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 basically just calls your allocation function followed by your initializer, so you could just reproduce that code yourself inside 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;

This would avoid needing to convert the data to ruby format just for it to be converted back straight away, but I don’t think you would gain much and using rb_class_new_instance is probably clearer.


You should also be aware that the Data_Wrap_Struct struct macros have been deprecated:

The old (non-Typed) Data_XXX macro family has been deprecated. In the future version of Ruby, it is possible old macros will not work.

I’ve used them here for simplicity but you might want to loo into using the newer TypedData_ macros.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot! I figured I'd have to use the class VALUEs as global variables to access them which worked for me after I posted this but this answer goes into good detail with some things I didn't know

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.