Passing C++ objects to embedded Ruby with SWIG

Posted by Nat Tuck Mon, 21 Jul 2008 19:12:00 GMT

SWIG does an excellent job wrapping C++ classes for use as a Ruby extension module. Unfortunately, it doesn’t look to be designed to handle some of the issues that I’ve run into when embedding Ruby to extend a C++ application. Specifically, how to pass C++ objects to Ruby calls in such a way that they can be passed back to SWIG generated methods requires a bit of hackery.

 

Example:
Say I’m writing a GUI app in some fictional UI library where every drawing call takes a Window object.

// gui.h - library header
class Window {
    ...
};

void draw_text(Window*, string text);

Using SWIG to translate this into Ruby is well-documented:

// gui.i - SWIG interface
%module gui
%{
#include 
%}

#include

That makes it simple enough to call Window methods and draw_text from Ruby if I have a Window object. Here’s the problem code:

// Core code for the app.
#include 
#include

void some_event_callback(Window* w) {
rb_funcall(callback_obj, rb_intern("callback_fun"), 1, w);
}

int main() {
// ruby interpreter setup
gui_set_callback(some_event_callback);
gui_main_loop();
}

This all looks great - except for the fact that rb_funcall takes a VALUE as it’s last argument and I’m trying to pass a Window*. So the question is, how do I turn a Window* into a VALUE that will act as a Window* for the SWIG-generated draw_text() function?

SWIG generates all the code it needs to create properly wrapped Window* objects in its output file gui_wrap.cxx. Looking in there for Window, I find an interesting line:

/* -------- TYPES TABLE (BEGIN) -------- */

#define SWIGTYPE_p_Window swig_types[0]

SWIG knows what it’s wrapper type is called, but that macro (and array) only exist within the SWIG output file. We need to create a global function in that module that takes a Window* and returns a properly constructed VALUE.

Luckily, it’s easy enough to include code in the interface file that will be passed through to the output file. The updated interface file looks like this:

// gui.i - SWIG interface, updated
%module gui
%{
#include 
%}

#include

%{

VALUE window_to_value(Window* w) {
return SWIG_NewPointerObj(SWIG_as_voidptr(w),
SWIGTYPE_p_Window, 0 );
}

%}

That should mostly be self explanatory, except for that last constant 0. I’m pretty sure that it’s a boolean flag telling Ruby if it’s responsible for deleting the pointer when the object gets garbage collected.

The next complication is loading the resulting object file. Normally SWIG output is compiled to a .so / .dll that can be loaded with a Ruby require statement, but that may not work in this case due to issues with dynamically loaded code calling functions in the code that loaded it. In order to work around this, the _wrap.cxx file needs to be linked directly into the application like any other source file and the SWIG Init_ function needs to be manually called.

// Core code for the app.
#include 
#include

extern "C" {
void Init_gui();
VALUE window_to_value(Window*);
};

void some_event_callback(Window* w) {
rb_funcall(callback_obj, rb_intern("callback_fun"), 1,
window_to_value(w));
}

int main() {
// ruby interpreter setup
gui_set_callback(some_event_callback);
gui_main_loop();
}

Related Links:

 

Tags , ,

Comments are disabled