C++ Functors and Ruby extensions

J

Jason Roelofs

[Note: parts of this message were removed to make it a legal post.]

I wonder if anyone has tried to do what I'm doing and if they've come up
with an appropriate solution.

Basic idea:

struct ruby_func_wrapper
{
VALUE operator()(VALUE self, VALUE args)
{
m_callback(args ...);
}
}

extern "C"
void Init_functor_test() {
ruby_func_wrapper functor;
[ ... code that sets callback of functor ... ]

rb_define_global_function("functor_test", [something here &functor], -2);
}

Basically, I want to use instances of the ruby_func_wrapper functor as
callback for Ruby methods. I'm using Boost right now, and have tried
Boost.Function and Boost.Bind to no avail (the code compiles but the actual
call causes a seg fault, I assume the memory location is voided after the
init is done, so trying to pass execution to a bad memory location, but I'm
not sure). For the record, I am trying my hand at a Boost.Ruby library.

I'm also wondering if there is another way to add methods to Kernel or
Classes outside of the rb_define_* methods. In Python you can do
PyObject_SetAttr(namespace, name, PyCFunction) which is effectively ("
namespace.name = function"). I've yet to find an equivalent in Ruby.

I hope this is clear, though if not I can post the full code I've got right
now.

Thanks

Jason
 
A

Axel Etzold

-------- Original-Nachricht --------
Datum: Tue, 25 Dec 2007 04:02:38 +0900
Von: "Jason Roelofs" <[email protected]>
An: (e-mail address removed)
Betreff: C++ Functors and Ruby extensions
I wonder if anyone has tried to do what I'm doing and if they've come up
with an appropriate solution.

Basic idea:

struct ruby_func_wrapper
{
VALUE operator()(VALUE self, VALUE args)
{
m_callback(args ...);
}
}

extern "C"
void Init_functor_test() {
ruby_func_wrapper functor;
[ ... code that sets callback of functor ... ]

rb_define_global_function("functor_test", [something here &functor],
-2);
}

Basically, I want to use instances of the ruby_func_wrapper functor as
callback for Ruby methods. I'm using Boost right now, and have tried
Boost.Function and Boost.Bind to no avail (the code compiles but the
actual
call causes a seg fault, I assume the memory location is voided after the
init is done, so trying to pass execution to a bad memory location, but
I'm
not sure). For the record, I am trying my hand at a Boost.Ruby library.

I'm also wondering if there is another way to add methods to Kernel or
Classes outside of the rb_define_* methods. In Python you can do
PyObject_SetAttr(namespace, name, PyCFunction) which is effectively ("
namespace.name = function"). I've yet to find an equivalent in Ruby.

I hope this is clear, though if not I can post the full code I've got
right
now.

Thanks

Jason

Dear Jason,

I think SWIG (http://www.swig.org/) may have a solution
for what you want to do:

http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/funcptr/index.html

Here's some more information on how to use it in conjunction with
Ruby :

http://www.swig.org/Doc1.3/Ruby.html

Best regards,

Axel
 
J

Jason Roelofs

[Note: parts of this message were removed to make it a legal post.]

I'm doing this because I SWIG doesn't work for my current wrapping project.

Also, SWIG generates static method wrappers. I'm trying to do this
dynamically (see Boost.Python: http://www.boost.org/libs/python/doc/)

Jason

-------- Original-Nachricht --------
Datum: Tue, 25 Dec 2007 04:02:38 +0900
Von: "Jason Roelofs" <[email protected]>
An: (e-mail address removed)
Betreff: C++ Functors and Ruby extensions
I wonder if anyone has tried to do what I'm doing and if they've come up
with an appropriate solution.

Basic idea:

struct ruby_func_wrapper
{
VALUE operator()(VALUE self, VALUE args)
{
m_callback(args ...);
}
}

extern "C"
void Init_functor_test() {
ruby_func_wrapper functor;
[ ... code that sets callback of functor ... ]

rb_define_global_function("functor_test", [something here &functor],
-2);
}

Basically, I want to use instances of the ruby_func_wrapper functor as
callback for Ruby methods. I'm using Boost right now, and have tried
Boost.Function and Boost.Bind to no avail (the code compiles but the
actual
call causes a seg fault, I assume the memory location is voided after the
init is done, so trying to pass execution to a bad memory location, but
I'm
not sure). For the record, I am trying my hand at a Boost.Ruby library.

I'm also wondering if there is another way to add methods to Kernel or
Classes outside of the rb_define_* methods. In Python you can do
PyObject_SetAttr(namespace, name, PyCFunction) which is effectively ("
namespace.name = function"). I've yet to find an equivalent in Ruby.

I hope this is clear, though if not I can post the full code I've got
right
now.

Thanks

Jason

Dear Jason,

I think SWIG (http://www.swig.org/) may have a solution
for what you want to do:


http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/funcptr/index.html<http://www.goto.info.waseda.ac.jp/~fukusima/ruby/swig-examples/funcptr/index.html>

Here's some more information on how to use it in conjunction with
Ruby :

http://www.swig.org/Doc1.3/Ruby.html

Best regards,

Axel
 
G

gga

I'm using Boost right now, and have tried
Boost.Function and Boost.Bind to no avail (the code compiles but the actual
call causes a seg fault, I assume the memory location is voided after the
init is done, so trying to pass execution to a bad memory location, but I'm
not sure). For the record, I am trying my hand at a Boost.Ruby library.

Your assumption is correct. You can't do it. The functor needs to
remain visible in memory. Also, depending on your compiler, the way C+
+ functions expect arguments may be different from what ruby expects
in its C API (this is usually a problem with Windows' __stdcall vs.
__fastcall, etc).
You will also not get ANY speed benefit from using a functor, thou, so
using it is kind of pointless.

I'm also wondering if there is another way to add methods to Kernel or
Classes outside of the rb_define_* methods. In Python you can do
PyObject_SetAttr(namespace, name, PyCFunction) which is effectively ("
namespace.name = function"). I've yet to find an equivalent in Ruby.

No.

class A
def f; end
end

repeat as many times as needed. Classes are open in ruby.
rb_define_* do just the above and is equivalent to python's adding of
functions.

Overall, as the other poster said, you should use SWIG. There's
really no benefit to using a C++ approach to wrapping code like
Boost.Python does. If anything, Boost.Python is much more primitive
than what swig can do.
 
J

Jason Roelofs

[Note: parts of this message were removed to make it a legal post.]

Your assumption is correct. You can't do it. The functor needs to
remain visible in memory. Also, depending on your compiler, the way C+
+ functions expect arguments may be different from what ruby expects
in its C API (this is usually a problem with Windows' __stdcall vs.
__fastcall, etc).
You will also not get ANY speed benefit from using a functor, thou, so
using it is kind of pointless.



No.

class A
def f; end
end

repeat as many times as needed. Classes are open in ruby.
rb_define_* do just the above and is equivalent to python's adding of
functions.

Overall, as the other poster said, you should use SWIG. There's
really no benefit to using a C++ approach to wrapping code like
Boost.Python does. If anything, Boost.Python is much more primitive
than what swig can do.
SWIG does not handle nested classes, a *serious* defect to what I'm trying
to do. I've looked into helping add this feature, but the amount of work
required makes it more feasible to build a better, Ruby-specific wrapper
system.

And comparing Boost.Python to SWIG really doesn't make sense. You need to
compare Boost.Python with Py++ to SWIG.

I'm going to post this to Ruby-core as well to see if I can glean any
insight from those who know the innards of Ruby.

For Ruby-core: Is the following code bit even possible or feasible, to the
best of your knowledge. Basically, I need to send a dynamically generated
function pointer to rb_define_* methods. If this is not possible, I guess I
could go with method_missing, and dispatch the call in C++ according to the
name, but man, that sounds hacky.

Anyway, here is the proto code I'm using to figure this out:

function_test.cpp:

#include "ruby.h"

#include <iostream>

#include <boost/function.hpp>
#include <boost/bind.hpp>

using namespace std;

typedef VALUE (ruby_method)(...);

struct ruby_func
{
VALUE operator()(VALUE self, VALUE args) {
cout << "In functor" << endl;
//cout << "Function called, name " << m_name << endl;
return Qnil;
}
};

ruby_func func;
boost::function2<VALUE, VALUE, VALUE> f1;

VALUE func_check(VALUE self, VALUE args) {
if (f1) {
cout << "Function object exists" << endl;
// And just to prove, call it
f1(Qnil, Qnil);
} else {
cout << "Function object does not exist" << endl;
}
return Qnil;
}

extern "C"
void Init_function_test()
{
f1 = boost::bind<VALUE>(func, _1, _2);

// Prove that boost::bind worked correctly
f1(Qnil, Qnil);

// Method created to check that the function object still exists in memory
rb_define_global_function("check_function", (ruby_method*) &func_check,
-2);

// Actual function call, this causes segfault
rb_define_global_function("do_function_test", (ruby_method*) &f1, -2);
}


test.rb:

require 'function_test'

# Proper output from extension
check_function

# Segfault
do_function_test




Thanks for your help.

Jason
 
R

Robert Klemme

2007/12/27 said:
SWIG does not handle nested classes, a *serious* defect to what I'm trying
to do. I've looked into helping add this feature, but the amount of work
required makes it more feasible to build a better, Ruby-specific wrapper
system.

I'm not too familiar with Boost. So could you quickly summarize what
is it that you expect to gain from creating a Boost Ruby integration?

Kind regards

robert
 
J

Jason Roelofs

[Note: parts of this message were removed to make it a legal post.]

I'm not too familiar with Boost. So could you quickly summarize what
is it that you expect to gain from creating a Boost Ruby integration?

Kind regards

robert
Boost.Python and luabind are libraries built to make it extremely easy to
build interfaces into the target language from C++. It's not really about
integration with Boost, Boost just provides some very, very useful
constructs and a powerful meta-programming subsystem that makes this library
feasible in such a strict language. Here's what you're able to do:

Wrap this:

class A {
public:
A();
A(int, int);

void doSomething();
int getSomethingBack();
};

Like this:

class_<A>("A")
.def(initialize<int, int>())
.def("do_something", &A::doSomething)
.def("get_something_back", &A::getSomethingBack);

and in Ruby

a1 = A.new
a = A.new(1,2)

a.do_something
a.get_something_back


For those of you pushing SWIG, trust me, I've spent many, many hours trying
different ways to make nested classes work in a way that won't require me to
re-write a full quarter of the headers that I'm trying to wrap (Ogre
rendering engine, for the record). Nested classes are *not* supported in
SWIG, there are hacks to make it look so, hacks that do not work all of the
time.

Jason
 
P

Paul Brannan

For Ruby-core: Is the following code bit even possible or feasible, to
the best of your knowledge. Basically, I need to send a dynamically
generated function pointer to rb_define_* methods. If this is not

Your code doesn't use dynamically-generated function pointers; it uses
function objects. If you want dynamically-generated function pointers
(which you probably don't), you'll need to use something like ffcall or
similar.

Ruby can't call function objects directly; it can only call function
pointers. Unfortunately, the Ruby API doesn't provide a mechanism to
attach data to a particular method, which is necessary in order to
provide call a function object from inside a function.

The Rice library uses function objects to automatically do type
conversion from Ruby type to C++ type (and back again when the function
returns). It gets around the above limitation by mapping class name and
method name to the data, then doing a lookup (*) when the function is
called to get a pointer to a base class. The conversion function is
then just a virtual function call on the retrieved pointer.

Take a look at rice/detail/method_data.cpp if you want the gory details.
possible, I guess I could go with method_missing, and dispatch the
call in C++ according to the name, but man, that sounds hacky.

Using method_missing would be hacky. Fortunately, on Ruby 1.8, you can
get the name of the function just called via ruby_frame->last_class and
ruby_frame->last_func. Ruby 1.9 gives an interface to get the method
name via rb_frame_callee() (**).
typedef VALUE (ruby_method)(...); ...
boost::function2<VALUE, VALUE, VALUE> f1; ...
void Init_function_test()
{
f1 = boost::bind<VALUE>(func, _1, _2);
// Prove that boost::bind worked correctly
f1(Qnil, Qnil);
// Method created to check that the function object still exists in
memory
rb_define_global_function("check_function", (ruby_method*)
&func_check, -2);
// Actual function call, this causes segfault
rb_define_global_function("do_function_test", (ruby_method*) &f1,
-2);

This cast is invalid. f1 is a boost::function, not a function pointer.
You need to create a wrapper function as mentioned above.

Incidentally, at one point I attempted to add support for function
objects to Rice, but I gave up when I realized that I couldn't make it
work with boost::bind (since operator() in the object returned by
boost::bind is a template function, which makes introspection on the
parameter types and return type impossible). I might give this another
shot sometime, but it's unfortunately non-trivial.

Paul


(*) To avoid memory allocation, the actual data is stored in the unused
member of the CFUNC node that Ruby allocates when the method is defined.

(**) AFAICT, there's no function interface to get the class that was
just called. I'll likely cross this bridge when I port Rice to 1.9.
 
P

Paul Brannan

Wrap this:

class A {
public:
A();
A(int, int);

void doSomething();
int getSomethingBack();
};

Like this:

class_<A>("A")
.def(initialize<int, int>())
.def("do_something", &A::doSomething)
.def("get_something_back", &A::getSomethingBack);

Rice (http://rubyforge.org/projects/rice) lets you do it like this:

extern "C"
void Init_myextension()
{
define_class<A>("A")
.define_constructor<Constructor<A>())
.define_method("do_something, &A::doSomething)
.define_method("get_something_back", &A::getSomethingBack);
}

I avoided the boost::python syntax so the library would feel more
familiar to people who use the C Ruby API.

I don't yet have support for non-default constructors working, but I'd
welcome the help. :)

Paul
 
R

Robert Klemme

[Note: parts of this message were removed to make it a legal post.]

I'm not too familiar with Boost. So could you quickly summarize what
is it that you expect to gain from creating a Boost Ruby integration?

Kind regards

robert
Boost.Python and luabind are libraries built to make it extremely easy to
build interfaces into the target language from C++. It's not really about
integration with Boost, Boost just provides some very, very useful
constructs and a powerful meta-programming subsystem that makes this library
feasible in such a strict language. Here's what you're able to do:

Wrap this:

class A {
public:
A();
A(int, int);

void doSomething();
int getSomethingBack();
};

Like this:

class_<A>("A")
.def(initialize<int, int>())
.def("do_something", &A::doSomething)
.def("get_something_back", &A::getSomethingBack);

and in Ruby

a1 = A.new
a = A.new(1,2)

a.do_something
a.get_something_back

Thanks for taking the time to explain! I was not aware that something
like this is part of Boost.
For those of you pushing SWIG, trust me, I've spent many, many hours trying
different ways to make nested classes work in a way that won't require me to
re-write a full quarter of the headers that I'm trying to wrap (Ogre
rendering engine, for the record). Nested classes are *not* supported in
SWIG, there are hacks to make it look so, hacks that do not work all of the
time.

Kind regards

robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top