Jul 22

SWIG, Java, and JRuby

Providing a robust, maintainable, and interactive interface to your C/C++ application can be a challenge, but I’ve found that a combination of SWIG, Java, and JRuby (or Jython if you prefer) makes for a very powerful combination.

NOTE: All code is available here.
All languages have their trade offs: C/C++ is speedy, but hard to maintain and lacking in cross platform library support, Java has great cross platform support, but doesn’t directly interact with hardware, Ruby has great DSL capabilities, but doesn’t provide (necessarily) the performance or robust libraries.

Writing in multiple languages is no picnic either, but if you need to provide an extensible, robust scripting interface to C/C++ code this is a winning combination.

SWIG

SWIG is a wrapper-generator application which takes in a C/C++ header-like language (more on that later) and generates the binding of your choice (Ruby, Java, Python, etc) automatically.

Not only does this save a tremendous amount of time up front, but as your C/C++ interface changes, you can automatically and reliably update your cross-language interface to match.

JRuby/Jython/etc

In case you’ve been living under a rock, a number of popular scripting languages have been implemented on top of the JVM. In fact Java 6 includes a standard interface to access these scripting engines.

The offshoot of this is that

  1. If you can get your interface into Java you can use it with a numberof different scripting languages.
  2. These scripting languages have access not only to their own standard featureset, but to the entirety of the java libraries as well.

A Simple Example

So how do I make this all work together? First you need to define the interface you want to export to Java. People often make the mistake of trying to export a mess of existing headers. You really want this interface to represent the minimal set of clases/enumerations/constants/functions/etc that are required to interface with your code (good API design advice anyway).

In this case we’re going to provide a way to create, configure, and write data to a linux tun device allowing the manipulation of raw IP traffic from Java. (More information on tun here: http://4thmouse.com/index.php/2009/11/19/opening-a-tun-device-on-unix/).

The C++

First we need to define out C++ interface as follows:

#include <vector>
#include <string>
 
namespace tun{
 
struct allocated_tun{
        std::string name;
        int handle;
};
 
/**
Allocate a tun device (must be root)
*/
allocated_tun tun_alloc(std::string ip, int netmask);
/**
Write a single ipv4 packet to the tunnel.
*/
int write_packet(int fd, const std::vector<char> &data);
/**
Read a single ipv4 packet from the tunnel.
*/
std::vector<char> read_packet(int fd);
 
/**
Close the tunnel device.
*/
int tun_close(int fd);
}

The SWIG

For more details on SWIG look here. This swig file basically takes the C++ header as-is and adds in STL support, mapping strings and the vector classes into java.

%module tun
 
%{
#include "tun.hpp"
%}
 
%include "std_vector.i"
%include "std_string.i"
namespace std{
        %template(vectorb) vector<char>;
}
%include "tun.hpp"

The Java

This example requires a single piece of java because for some reason you cannot call loadLibrary directly from JRuby. The following code simply loads the shared library for JNI:

package com.ftm.example.swig;
 
public class Load{
        public static void load(){
                System.loadLibrary("tun");
        }
}

The ruby

Now all you have to do is load the jar containing your JNI code into ruby. For convenience I’ve wrapped the JNI calls in a class called Tun:

require 'java'
require 'tun_jar'
 
com.ftm.example.swig.Load.load
 
class Tun
        SWG=com.ftm.example.swig.tun
        def initialize ip, netmask
                temp =SWG.tun_alloc(ip.to_s, netmask.to_i)
                if temp.handle < 0 then
                        raise 'Unable to open tun device'
                end
                @fd = temp.handle
        end
 
        def write data
                vec = com.ftm.example.swig.vectorb.new(data.size)
                (0...data.size).each{ |i| vec.set(i, data[i])}
                ret = SWG.write_packet(@fd, vec)
                vec.delete()
                ret
        end
 
        def read
                vec = SWG.read_packet(@fd)
                Array.new(vec.size){|i| (vec.get(i) & 0xFF)}.pack("C*")
        end
 
        def close()
                SWG.tun_close(@fd)
        end
end

Using the code

Now I can use the Tun object just like any other JRuby code. (Note: you must be root to create a tun device). For instance, to tun the interactive console:

>LD_LIBRARY_PATH=<path_to_so> jirb
irb(main):001:0> require 'tun'
=> true
irb(main):002:0> t = Tun.new("10.1.1.1", 24)
=> #<Tun:0xa33ce2 @fd=12>
irb(main):003:0> t.read
=> "..."
irb(main):004:0>t.close
Comments are off for this post

Comments are closed.