back   Back to the index page

SWIG, Ruby and callbacks

(When all you have is a SWIG, everything looks like a wrappee)

The documentation on how to do callbacks from C library code to a SWIG generated Ruby (for example) wrapper out there amounts to the observation that “it can probably be done with typemaps”. Considering that it hardly can be an unusual thing to do, I thought that I would share the solution I came up with when faced with the problem.

The problem

In the example code below, I assume that you have a C library that expects to call C functions that take one or two arguments, one of them a void pointer with user definable data. The library provides a set of functions to register pointers to callback functions with the apropriate prototypes. The support code is written as a C++ class, so the library headers have to be compilable by a C++ compiler.

The concept is to make a SWIG typemap that inserts some code in the wrappers for the callback registry functions. In each wrapper function, a static instance of a C++ class serves as a container for the Ruby callback procedure object and the operations needed for keeping the garbage collector away. This code is inserted by a typemap that triggers on the function pointer type.

This basic model can be used for other related callback architectures and target languages. It can easily be rewritten with plain C code (I really don't know why I did it in C++. It's nifty to use function-local classes to implement specific behaviour for each callback type, but they make me feel a litte naughty, to be honest.)

There are many other ways to do this, many of them simpler than this. Perhaps I've missed some obvious, simpler way to handle callbacks — send me a nice mail if you know of any.

I'll let the code speak for itself. Refer to the SWIG manual for the specifics on typemaps, Ruby wrapping, et cetera.

The code

The library interface file:

typedef void (*CallbackType1)(void *, int);
typedef void (*CallbackType2)(void *, int);
typedef void (*CallbackType3)(void *);
typedef void (*CallbackType4)(void *);
void SetCallback1(CallbackType1 function, void *userData);
void SetCallback2(CallbackType2 function, void *userData);
void SetCallback3(CallbackType3 function, void *userData);
void SetCallback4(CallbackType4 function, void *userData);

File TCallbackObject.h:

#include <ruby.h>

class TCallbackObject
{
 protected:
  VALUE RubyFunction;
  VALUE MeWrapper;
  /// Called by Ruby's GC
  static void StaticMark(TCallbackObject *me);
  void Mark();
  void Callback();
 public:
  TCallbackObject();
  virtual ~TCallbackObject();
  /// Set Ruby callback function, or Qnil to unset.
  void SetCallback(VALUE rubyFunction);
  /// Called by C code
  static void StaticCallback(void *me);
};

File TCallbackObject.cpp:

#include "TCallbackObject.h"

void TCallbackObject::StaticMark(TCallbackObject *me)
{
  me->Mark();
}

void TCallbackObject::Mark()
{
  if (!NIL_P(RubyFunction)) rb_gc_mark(RubyFunction);
}

void TCallbackObject::StaticCallback(void *me)
{
  static_cast<TCallbackObject*>(me)->Callback();
}

void TCallbackObject::Callback()
{
  if (!NIL_P(RubyFunction)) {
    rb_funcall(RubyFunction, rb_intern("call"), 0);
  }
}

void TCallbackObject::SetCallback(VALUE rubyFunction)
{
  // Forget about the old function, if any, it will be garbage collected.
  // I really should do some type checking here -- it's too late when the
  // callback is eventually called.
  RubyFunction = rubyFunction;
}

TCallbackObject::TCallbackObject()
  : RubyFunction(Qnil)
{
  // This object is wrapped in a Ruby object, held in MeWrapper so
  // that the garbage collector has a way to get to the callback
  // function for marking and sweeping. The wrapper is registered as a
  // global object, which makes Ruby aware of its existence.
  MeWrapper = Data_Wrap_Struct(0 /* klass */, StaticMark, NULL, static_cast<void*>(this));
  rb_gc_register_address(&MeWrapper);
}

TCallbackObject::~TCallbackObject()
{
  // Unmark the wrapper as a global variable.
  rb_gc_unregister_address(&MeWrapper);
}

The interesting parts of the SWIG wrapper file wrapper.i:

%{
#include "TCallbackObject.h"
%}

/* Handle callbacks (fun!)
 * With SWIG 1.3.24, typemaps with function pointer syntax don't seem to work.
 * Like this one:
 *    %typemap(in) (void (*function)(void *), void *userData) { ... }
 * So I had to typedef the function pointers and use the deffed type in
 * the typemap. Since there was no common "base typedef" for all the function
 * pointers in the library (individual typedefs are used for each callback),
 * a hand-made list of all functions taking function pointer arguments is needed.
 */

typedef void (*fpvoid)(void *);
typedef void (*fpvoidint)(void *, int);

%typemap(in) (fpvoid, void *) {
  // CALLBACK TYPE (VOID*) for $1 of type $1_type and $2 of type $2_type
  static TCallbackObject cbo;
  cbo.SetCallback($input);
  $1 = TCallbackObject::StaticCallback;
  $2 = static_cast(&cbo);
}
%typemap(in) (fpvoidint, void *) {
  // CALLBACK TYPE (VOID*, INT) for $1 of type $1_type and $2 of type $2_type
  class TLocalCallbackObject : public TCallbackObject {
     void Callback(int v) {
       if (!NIL_P(RubyFunction)) {
	 rb_funcall(RubyFunction, rb_intern("call"), 1, INT2NUM(v));
       }
     }
   public:
     static void StaticCallback(void *me, int v) {
       static_cast(me)->Callback(v);
     }
   };

  static TLocalCallbackObject cbo;
  cbo.SetCallback($input);
  $1 = ($1_type)TLocalCallbackObject::StaticCallback;
  $2 = static_cast(&cbo);
}

%apply (fpvoidint, void *) { (CallbackType1 function, void *) };
%apply (fpvoidint, void *) { (CallbackType2 function, void *) };
%apply (fpvoid, void *)    { (CallbackType3 function, void *) };
%apply (fpvoid, void *)    { (CallbackType4 function, void *) };

A Ruby test script:



This page was last updated: Mon Mar 29 19:59:30 2010


Jonas Norling <norling@lysator.liu.se>
xhtml?, css?