Exchanging data between Ruby and C++

289 views Asked by At

I've been looking through tutorials on how to embed Ruby into a C++ program. I've found out how to define a class through "rb_define_class" and "rb_define_class_under" and methods through "rb_define_method". Now i need a good example that explains how to wrap an existing C++ object (pointer) with a ruby class written in C++. Example:

    class MyClass
    {
    public:
        MyClass();
        void MyMethod();
    };

    VALUE myclass_init(VALUE self)
    {
        // I'd like to create a new MyClass instance and store its pointer inside "self"
    }

    VALUE myclass_meth(VALUE self)
    {
        // Now i need to retrieve the pointer to the object and call its method
    }

    int main(int argc, char* argv[])
    {
        ruby_init();
        ruby_init_loadpath();

        VALUE myclass = rb_define_class("MyWrapperClass", rb_cObject);
        rb_define_method(myclass, "initialize", (VALUE(*)(...))myclass_init, 0);
        rb_define_method(myclass, "myWrappedMethod", (VALUE(*)(...))myclass_meth, 0);

        // Loading ruby script skipped..

        ruby_finalize();

        return 0;
    }

I also need a way to handle garbage collection in order to free my wrapped object (and do other stuff). Sorry for the bad english and thanks to whoever will try to answer this question!

1

There are 1 answers

0
Neil Slater On BEST ANSWER

To integrate with Ruby's memory management, you need to implement two functions that allocate and free memory for one of your objects - neither may take parameters. Ruby will store your C++ data structure "attached" to the Ruby self VALUE, and you need to use a couple of methods to create that attachment, and to get at your C++ from self.

Your code so far was close enough that I have just filled in the gaps for you here:

class MyClass
{
public:
    MyClass();
    void MyMethod();
};

//////////////////////////////////////////////////////////
// The next five are the functions that you were missing
// (although you could factor this differently if you chose)

MyClass *rb_create_myclass_obj() {
    return new MyClass();
}

void rb_delete_myclass_obj( MyClass *p_myclass ) {
    delete p_myclass;
    return;
}

VALUE myclass_as_ruby_class( MyClass *p_myclass , VALUE klass ) {
  return Data_Wrap_Struct( klass, 0, rb_delete_myclass_obj, p_myclass );
}

VALUE myclass_alloc(VALUE klass) {
  return myclass_as_ruby_class( rb_create_myclass_obj(), klass );
}

MyClass *get_myclass_obj( VALUE obj ) {
  MyClass *p_myclass;
  Data_Get_Struct( obj, MyClass, p_myclass );
  return p_myclass;
}

//////////////////////////////////////////////////////////

VALUE myclass_init(VALUE self)
{
    // You need do nothing here, Ruby will call myclass_alloc for 
    // you.
    return self;
}

VALUE myclass_meth(VALUE self)
{
    MyClass *p_myclass = get_myclass_obj( self );
    p_myclass->MyMethod();

    // If MyMethod returns some C++ structure, you will need to convert it
    // Here's how to return Ruby's nil

    return Qnil; 
}

int main(int argc, char* argv[])
{
    ruby_init();
    ruby_init_loadpath();

    VALUE myclass = rb_define_class("MyWrapperClass", rb_cObject);

    // The alloc function is how Ruby hooks up the memory management
    rb_define_alloc_func(myclass, myclass_alloc);

    rb_define_method(myclass, "initialize", (VALUE(*)(...))myclass_init, 0);
    rb_define_method(myclass, "myWrappedMethod", (VALUE(*)(...))myclass_meth, 0);

    // Loading ruby script skipped..

    ruby_finalize();

    return 0;
}