Using SWIG, a simple example for perl and Java

General

If you do not know what SWIG is, go here first. IN short, SWIG = Simplified Wrapper and Interface Generator is a C/C++to other language code generator.

 

Example

SWIG has a few examples on their Web site (I used the same with small modifications for automation) and even more explanation in their long documentation and manual (download the PDF, it is easier) better explaining how to handle the most complex cases. The example below is based on the standard example, adapted for both cygwin and a standard Scientific Linux installation. For the cygwin version, some path were hardcoded (so just change it).

 

The base code

The base code we want to wrap is called example.c and example.h . Both code are provided below.

The include file (a bit dirty for a single code)

extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();

and the code

/* File : example.c */
#include <time.h>
double My_variable = 3.0;
 
int fact(int n) {
  if (n <= 1) return 1;
  else return n*fact(n-1);
}
 
int my_mod(int x, int y) {
  return (x%y);
}

char *get_time()
{
  time_t ltime;
  time(&ltime);
  return ctime(&ltime);
}
 

Nothing complex so far and basically a factorial recursive function, a time function returning a string and a local variable.

The question is: considering that my return values and function arguments are simple (but the internal logic may be more complex), how do I leverage this compiled code and logic from another language stand point? This is where SWIG comes to help.

SWIG allows yu to declare what you would like to see as wrapper and interface to other language. You do this by defining a .i file. Below is our example.i file.

/* example.i */
%module example
%{
 /* Lazzy example, add header in the .i and not much work needed from now on */
#include "example.h"
%}

%include "example.h"

And that is it. From there, I can now generate interfaces in many langages. What I did really is to import the .h from the .i asking SWIG to convert all functions and even the interval variable.

Makefile and helper script

I used the following helper script which purpose was to make automatic the grabbing of options necessary for compiling and linking in perl. I used perl itself as wrapper but you could use csh or whatever shell script really (not important).

#!/bin/env perl

if ( $ARGV[0] eq "LD" ){
    print `perl -MExtUtils::Embed -e ldopts`;
} else {
    print `perl -MExtUtils::Embed -e ccopts`;  
}

The Makefile is valid for both Scientific Linux and cygwin. It is made to generate the perl library and the Java interface. Note a few shurtcuts

  • JAVAC, JAVAX,JINC are hardcoded for cygwin
  • JINC is of fixed path for SL

The rest is independent of your local setup but assumes most commands will be found in your regular $PATH.

# Cygwin support
ifeq ($(OS),Windows_NT)
EXE   = .exe
SOEXT = dll
else
EXE   =
SOEXT = so
endif

CC  = gcc
LD  = gcc

# Standard definitions
CFLAGS    = -Wall -c
CXXFLAGS  = $(CFLAGS)
EASYFLAGS = -c
LDFLAGS   = -o
SOFLAGS   = -shared -Wl,-Bdynamic -o

# Because it is not defined in my PATH, I need to define by hand
ifeq ($(OS),Windows_NT)
JAVAC= /cygdrive/c/Program\ Files/Java/jdk1.5/jdk/bin/javac.exe
JAVAX= /cygdrive/c/Program\ Files/Java/jdk1.5/jdk/bin/java.exe
JINC= -I/cygdrive/c/Program\ Files/Java/jdk1.5/jdk/include -I/cygdrive/c/Program\ Files/Java/jdk1.5/jdk/include/win32
else
JAVAC=javac
JAVAX=java
JINC= -I/usr/java/latest/include -I/usr/java/latest/include/linux
endif

# My project definition (same name in .i)
MYMOD = example

# This is inelegant but $(shell perl) did not work so ...
PERL_INC = $(shell ./perllink.pl)
PERL_LDL = $(shell ./perllink.pl LD)

# targets and sources
SRCS = $(MYMOD)_perl_wrap.c example.c
INCS = example.h
OBJS = $(MYMOD)_perl_wrap.o example.o

JAVAS= test.java
JDEPS= $(MYMOD)_java_wrap.c example.c
JOBJS= $(MYMOD)_java_wrap.o example.o

# Main target
all: $(MYMOD).$(SOEXT) $(MYMOD)_java.$(SOEXT) $(MYMOD)_main.class
        @ echo "Try % perl test.pl"
        @ echo "Try % $(JAVAX) -Djava.library.path=./ $(MYMOD)_main"


# the needed .so for the pm to work
$(MYMOD).$(SOEXT): $(OBJS)
        $(LD) $(SOFLAGS) $@ $^ $(PERL_LDL)
        
$(MYMOD)_java.$(SOEXT): $(JOBJS)
        $(LD) $(SOFLAGS) $@ $^ 
        @ /bin/ln -s $(MYMOD)_java.$(SOEXT) ./lib$(MYMOD)_java.$(SOEXT)



#+
# SWIG dependencies
#-

# the creation of the perl module wrappers are made by swig. a .pm will be
# created as well
$(MYMOD)_perl_wrap.c: $(MYMOD).i $(INCS)
        swig -perl5 -o $@ $<

$(MYMOD)_java_wrap.c: $(MYMOD).i $(INCS)
        swig -java -o $@ $<



# XXX_wrap.cxx will be generated from the .i, .i was created
# once in "lazzy" mode. Compile using langage specific
$(MYMOD)_perl_wrap.o: $(MYMOD)_perl_wrap.c
        $(CC) $(EASYFLAGS) $< -o $@ $(PERL_INC)

$(MYMOD)_java_wrap.o: $(MYMOD)_java_wrap.c
        $(CC) $(EASYFLAGS) $(JINC) $<

$(MYMOD)_main.class: test.java $(MYMOD).java
        $(JAVAC) $?


# Global
.c.o:
        $(CC) $(CFLAGS) $< -o $@


clean:
        /bin/rm -f *.o
        /bin/rm -f *.class
        /bin/rm -f *_wrap.c
        /bin/rm -f *~ *.*~
        /bin/rm -f $(MYMOD)*.java
        /bin/rm -f *.$(SOEXT)
        /bin/rm -f $(MYMOD).pm

realclean: clean

Now, let's run it ... and explain what happens and how to test it.

Executing make and a first test

% make

will peform many tasks amongst which:

  • it will invoque swig with -perl5 option to generate the perl wrapper C code and a perl module (example.pm)
  • it will compile the perl wrapper
  • it will compile your c code and assemble both wrapper and compile code into a shared library
     
  • it will invoque swig with -java option and generate a Java wrapper
  • it will compile the wrapper and assemble both wrapper and compiled code into a shared library
  • For the java code, swig also generated an exampleJNI.java code
  • The make process also used JAVAC to assemble the additional wrapper class example.java (generated as well) and our example test.java program into .class files.
  • Since example.java depends on exampleJNI.java, this process also created the exampleJNI.class bytecode along example.class and since our test code defines a class example_main, an example_main.class is also created.

The java process was a bit more convoluted but not that much more.

Let's test in perl. We use the following example

#!/bin/env perl

use example;

print $example::My_variable."\n";
print example::fact(5)."\n";
print example::get_time()."\n";

This code basically loads the shared library which will then define the "example" perl module one can use directly. The execution gives

% ./test.pl
3
120
Thu Apr 30 17:38:40 2009

In other words, our example printed the value of the internal compiled code variable, called the fact() function with argument 5 and returned the expected value and printed the time as a string. It is noteworthy that while this example may not be impressive, conversio of type (char * to perl strings, etc ...) happened without any intervention from us.

You would get the same result if you would execute

% java -Djava.library.path=./ example_main

whereas example_main.class is executed as defined by our example test.java below


class example_main {
   public static void main(String argv[]) {
     System.out.println("Loading library");
     System.loadLibrary("example_java");
     System.out.println("Library loaded");
      
     System.out.println(example.getMy_variable());
     System.out.println(example.fact(5));
     System.out.println(example.get_time());
   }
}

This example code does the same thing than our perl example but from Java. In our example, we needed to load a single library and, while we created two dynamic libraries (one for perl and one for java) we should note that in practice, we could put both the c compiled code and all related compiled wrappers into a single shared library.

Sorry, I still did not get it

Well, SWIG allows me to write code and complex logics once, generate wrappers and interfaces likely once as well (unless I change the interface all the time) and use my code and logic from many langags, decreasing development cycles as maintenace concentrates on one langage.

There are some limitations. Your best trick remains to use simple type and avoid embeded object (like A creates B and I need a pointer to B from A while calling the code in the external langage) to make it simple. But really, swig allows you to do whatever you need in terms of returning pointers and objects (it is not as simple and you may need to write additional code to handle special cases related to contructor/destructor issues as it is then not all clear who can destroy and/or own the object).