Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Ruby class methods from C++

Tags:

c++

ruby

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.

like image 784
Robert Rouse Avatar asked Jan 30 '09 17:01

Robert Rouse


People also ask

Does Ruby have class methods?

There are two standard approaches for defining class method in Ruby. The first one is the “def self. method” (let's call it Style #1), and the second one is the “class << self” (let's call it Style #2). Both of them have pros and cons.

How do you call a class from another class in Ruby?

A class method can be called without creating a new instance of the class. class Foo def self. method puts "I'm a class method!" end end Foo. method # => "I'm a class method!"


2 Answers

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.

like image 58
rampion Avatar answered Sep 18 '22 15:09

rampion


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
like image 43
Gosh Avatar answered Sep 20 '22 15:09

Gosh