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
- If you can get your interface into Java you can use it with a numberof different scripting languages.
- 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
%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