SWIG vs libxen

February 17, 2010

in linux,software

SWIG is an awesome tool. I’ve been developing a lot in ruby recently, and I’ve often wanted to use features from external C or C++ libraries, but hated writing the wrapper code to make them available to ruby. Enter SWIG — the Simplified Wrapper and Interface Generator. It takes 99% of the burden off the developer to write interface code. In the simplest scenario, you can just reference a C header file and it will generate a complete C wrapper which generates ruby (or python, C#, etc, etc) interfaces you can use directly in your higher-level language. Awesome.

It may be evident to some that I’ve been messing around with Xen management interfaces recently, and my pet project over the past couple days has been investigating the libxen api. The xen uber-library provides a c library which is a client interface for the API. There are some existing ruby client libraries, but they’re not very complete. I wanted a quick way to play with this API, so I attempted to wrap the existing C library (libxenapi) with SWIG.

Unfortunately, after quite a number of hours tweaking my SWIG directives file, I realized I was close to spending as much time working on wrapping the C library as I would spend writing a native library in ruby (the xen api uses oh-so-simple XML RPC, and there’s already a ruby lib for that). So in a rare moment of weakness I decided to bail. Writing the API client directly in ruby is going to be orders of magnitude more brainless than trying to wrap the C library with SWIG, but it may actually be quicker.

I guess the lesson here is that while there are tools like SWIG whose meta-programming features greatly simplify a lot of development work, there are still times when it’s better to get down-and-dirty with some old-school brute-force style coding. In this case, I had hoped that SWIG would provide me with an instant ruby xenapi library with minimal effort, but the amount of SWIG tweaking involved eventually outweighed the effort in just writing my own pure-ruby library. I may go back and complete the SWIG wrapper as an intellectual exercise, but for now I’m firing up Textmate to get funky with some ruby…

I’ll likely post the ruby xenapi library on this blog when it’s done, and maybe get it onto rubyforge.

Side note — all was not in vain with SWIG: I found it was quite interesting and helpful to look at the C code that’s generated. For example, in the xen api there’s a struct:

typedef struct xen_string_set
{
    size_t size;
    char *contents[];
} xen_string_set;

In ruby, I wanted Rubyxenapi::xen_string_set.contents to return an array of strings. It was tricky, as in C arrays of strings passed to functions can be tricky to deal with. After some battling, I got a crafty, though maybe hackish, typemap into my swig file:

%typemap(out) char * contents[] {
	VALUE rArray = rb_ary_new();
 
	// HACK ? -- calling an arg that's created by swig
	char** orig = arg1->contents;
	size_t size = arg1->size;
 
	size_t i = 0;
    for (i = 0; i < size; i++)
    {
		rb_ary_push(rArray, rb_str_new2(arg1->contents[i]));
    }
 
	$result = rArray;
}

As you can see in the resulting C code made by swig, I make reference to “arg1″, which is auto-generated. It’s a bit tweaky to make reference to it, but it works…:

SWIGINTERN VALUE
_wrap_xen_string_set_contents_get(int argc, VALUE *argv, VALUE self) {
  xen_string_set *arg1 = (xen_string_set *) 0 ;
  void *argp1 = 0 ;
  int res1 = 0 ;
  char **result = 0 ;
  VALUE vresult = Qnil;
 
  if ((argc < 0) || (argc > 0)) {
    rb_raise(rb_eArgError, "wrong # of arguments(%d for 0)",argc); SWIG_fail;
  }
  res1 = SWIG_ConvertPtr(self, &argp1,SWIGTYPE_p_xen_string_set, 0 |  0 );
  if (!SWIG_IsOK(res1)) {
    SWIG_exception_fail(SWIG_ArgError(res1), Ruby_Format_TypeError( "", "xen_string_set *","contents", 1, self )); 
  }
  arg1 = (xen_string_set *)(argp1);
  result = (char **)(char **) ((arg1)->contents);
  {
    VALUE rArray = rb_ary_new();
 
    // HACK ? -- calling an arg that's created by swig
    char** orig = arg1->contents;
    size_t size = arg1->size;
 
    size_t i = 0;
    for (i = 0; i < size; i++)
    {
      rb_ary_push(rArray, rb_str_new2(arg1->contents[i]));
    }
 
    vresult = rArray;
  }
  return vresult;
fail:
  return Qnil;
}

cheers!

Leave a Comment

Previous post:

Next post: