tags:

views:

314

answers:

2

I'm trying to call a class method from C++. I've tried all combinations of rb_intern I could think of to make it work, but I've gotten nothing.

Example class

class CallTest
   def go
    (do something here)
   end
end

Trying to call in C++:

rb_funcall(?, rb_intern("go"), 0);

What goes in the ? space? I know if I use Qnil there, it will call global functions, but I'd prefer class methods.

Am I heading in the wrong direction?

Also, I'd prefer not to have to know the class name ahead of time if possible, but if I have to require that I know what it is, I can try passing it by name to my application.

I'm using SWIG to generate the binding.

+3  A: 

First off, go is, as you've defined it, not a class method, but an instance method.

As an object oriented language, all ruby methods require a receiver, that is, an object that the method is invoked on. For instance methods, the receiver is an instance of the class, for class methods, the receiver is the class object itself.

The ? placeholder you have is the slot for the receiver of the method call.

If you want to leave it as an instance method, then you need to do this:

rb_funcall(a_CallTest_instance, rb_intern("go"), 0);

where a_CallTest_instance was an instance of CallTest you created using rb_class_new_instance().

If you make it into a class method:

class CallTest
  def self.go
    # ...
  end
end

Then you need to use the CallTest class itself as the receiver:

rb_funcall(klass, rb_intern("go"), 0);

You can get a reference to the CallTest class using rb_const_get()

VALUE klass = rb_const_get(rb_cObject, rb_intern('CallTest'));

Use rb_cObject there, since CallTest is defined in the global context.

I'd suggest reading the chapter in the Pickaxe about extending ruby.

rampion
If I have an instance of the class created in the ruby file that I'm currently running, how do I pass that back to the C++ program to access effectively?
Robert Rouse
It depends. How do you want to call your C++ code from ruby? You can have your C++ code be a method of that instance, or as a method of something else, and pass the instance as an argument. Either way, ou'll need to use `rb_define_method()` to make the C++ function into a method.
rampion
Really, though, this is a different question. I'd be happy (and able, w/o the character restriction) to answer more fully were it a toplevel question.
rampion
A: 

I also use SWIG. These specific example files should help you.

1) test.rb

require 'test.so'
class A
    def func1(buffer)
     puts "ruby instance method: #{buffer}"
    end
end
def func2(buffer)
    puts "ruby global method: #{buffer}"
end
module Z
    def Z.func3(buffer)
     puts "ruby module method: #{buffer}"
    end
end

a = A.new
t = Test::Test1.new()
t.call(a, "func1", "Hello", 5)
t.call(method(:func2), "func2", "Yabaaa", 6)
t.call(Z, "func3", "Yahooooooo", 10)

2) test.h:

#include <ruby.h>
class Test1
{
public:
    void call(VALUE obj, char* func_name, const char* buffer, int size)
    {
     VALUE val = rb_str_new(buffer, size); 
     rb_funcall(obj, rb_intern(func_name), 1, val);
    }
};

3) test.i:

%module test

%{
#include "test.h"
%}

%exception
{
    try
    {
        $action
    }
    catch (std::exception& ex)
    {
        static VALUE cpperror = rb_define_class("test Exception", rb_eStandardError);
        rb_raise(cpperror, ex.what());
    }
    catch (...)
    {
        static VALUE cpperror = rb_define_class("test UnknownException", rb_eStandardError);
        rb_raise(cpperror, "Unknown catched");
    }
}

%include "test.h"

OUTPUT:

ruby ./test.rb 
ruby instance method: Hello
ruby global method: Yabaaa
ruby module method: Yahooooooo
Gosh