Thursday, November 8, 2007

Experience with SWIG + C/C++ = Java

Wrappers! Oh, joy.

Do you know how to be able to call C/C++ functions from Java? Wrappers...you have to create these things called "wrappers". Basically what it does is connects your C/C++ codes and your Java codes through some shared objects. "Shared objects? What in the world is that?? How am I supposed to know how to make 'shared objects'??" you ask? I had the same question. Luckily, there are some very nice fellows out there that provide us with a piece of software that is called an interface compiler, which... you guessed it, CREATES THOSE SHARED OBJECTS (and other necessary files) for us to use. Salute to them, I say! One good example of it (and the one I am using) is called SWIG (www.swig.org).

Unfortunately, I don't remember how to install SWIG on my Ubuntu 6.10 system. But I would like to share the problem I experienced in getting my Java code to call the C/C++ functions using SWIG.

To test, I used the example in the SWIG website tutorial page (www.swig.org/tutorial.html). So here goes:

You will have to write the codes for 3 files:
1. The C/C++ code (*.c) which contains the functions you wish to call from another language (in my case, Java).
2. The interface file (*.i) which is the input file for SWIG.
3. The other language code (in my case, *.java) which will call the C/C++ functions in *.c file in 1.
All the other files (shared objects, additional .c files, .java files) will be generated by SWIG. Please refer to the SWIG tutorial page for other languages, as I don't have any experience with SWIG and Python or Perl for example.

First, create your C/C++ file example.c:
/* File : example.c */

#include
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); }
Ok, nothing specific to point out there. It's just a straight forward C code, copied directly from the SWIG tutorial page. In fact, I'm pretty sure there's nothing in there that's specified for preparing it for the SWIG compilation. This next file, however, IS important; it is the interface file that SWIG will use to create the files necessary to make the wrapper.

/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
%}

extern double My_variable;
extern int fact(int n);
extern int my_mod(int x, int y);
extern char *get_time();
So, if I understand it correctly, the interface file (example.i) declares all the variables and functions used in the C/C++ file (example.c). Another way to do it, is to write the header file there instead of the variables and functions one-by-one. So here's another example for the interface file using header file from the same SWIG tutorial page:

/* example.i */
%module example
%{
/* Includes the header in the wrapper code */
#include "header.h"
%}

/* Parse the header file to generate wrappers */
%include "header.h"
So, you can use either type of interface file (depends on whether or not you have the header (*.h) file). As you can see, in both formats, you have to declare the variables/functions or header file twice: once within the % and curly brackets ({}), and another just preceded with the % sign (for the one with the header file). Note that when declaring the variables/functions, they must be preceded with the extern keyword. Here's a little thing about the extern keyword...

According to one definition:

External References: extern

  • If a variable is declared (with global scope) in one file but referenced in another, the extern keyword is used to inform the compiler of the variable's existence:
    • In declare.c:
      int farvar;
    • In use.c:
      {
      extern int farvar;
      int a;
      a = farvar * 2;
      }
  • Note that the extern keyword is for declarations, not definitions
    • An extern declaration does not create any storage; that must be done with a global definition
(from: http://icecube.wisc.edu/~dglo/c_class/vstorage.html)

And another:

extern

The extern keyword is used to inform the compiler about variables declared outside of the current scope. Variables described by extern statements will not have any space allocated for them, as they should be properly defined elsewhere.

Extern statements are frequently used to allow data to span the scope of multiple files.

(from: http://www.cppreference.com/keywords/extern.html)


So, it has got something to do for variables that are being used in one code (e.g. iUseVariableA.c), which are declared in another code (e.g. iDeclareVariableA.h or iDeclareVariableA.i). Is it something like "universal" (across several codes/files) variable for the codes that are being compiled? I don't know how to explain it appropriately, so if anyone can help me explain it in plain english, that'll be awesome.

Ok, let's move on to the SWIG-ing (note to self: got to find a better way to say it... that sounded weird). So, the third, and final file you have to make is the other language (in my case, Java) where you have to call the C/C++ functions.

/* main.java */
public class main {
public static void main(String argv[]) {
System.loadLibrary("example");
System.out.println(example.getMy_variable());
System.out.println(example.fact(5));
System.out.println(example.get_time());
}
}
See how you kind of calls the getMy_variable(), fact(int), and get_time() functions that were defined in the example.c by doing example.getMy_variable(), example.fact(5), example.get_time()? Seems pretty straightforward.

Now come the exciting part... compilation!

Here's how it was explained in SWIG tutorial page for compiling for Java module (all these are done in Terminal):

$ swig -java example.i
$ gcc -c example.c example_wrap.c -I/c/jdk1.3.1/include -I/c/jdk1.3.1/include/win32
$ gcc -shared example.o example_wrap.o -mno-cygwin -Wl,--add-stdcall-alias -o example.dll
Uh-oh... yeah, after reading more carefully, I realized that this example was for Cygwin/Windows. You can figure it out by the .../win32 include directory. And more importantly, see the third line, where the output is example.dll, a Windows Dynamic Linked Library (you know it's the output file because it was preceded by the '-o' command). So, what about for Ubuntu (Linux) users like me? Fear not! There is salvation, yet.

After digging deeper into the SWIG website (and unfortunately, couldn't find exactly what I was looking for through searching the forums), I found this documentation for Linux:

$ swig -java example.i
$ gcc -fpic -c example.c example_wrap.c -I/usr/java/j2sdk1.4.2/include -I/usr/java/j2sdk1.4.2/include/linux
$ gcc -shared example.o example_wrap.o -o libexample.so
$ javac main.java
$ java main
(from: http://www.dabeaz.com/cgi-bin/wiki.pl?SwigFaq/Java)

So, first you do (everything here are done in Terminal):

$ swig -java example.i

which tells the SWIG compiler to create a Java module, with the input interface file: example.i
Cool. If it works, SWIG then creates several files in the same directory:

example.java
example.class
example.o
exampleJNI.java
exampleJNI.class
example_wrap.c
example_wrap.o

For the most part, you won't be modifying these files. You can try, but DO IT AT YOUR OWN RISK; make sure you know what you're doing :) As a side note: if you've never done Java programming, the *.class files are the compiled *.java files. So the source codes are in *.java files.

Allrighty, next let's compile the *.c files that we have: example.c and example_wrap.c (one of the result files from SWIG compilation).

$ gcc -fpic -c example.c example_wrap.c -I/usr/java/j2sdk1.4.2/include -I/usr/java/j2sdk1.4.2/include/linux

I don't know if it will make any difference if you use g++ or other C/C++ compiler rather than gcc. I used gcc and it worked fine. So, the thing to keep in mind is the -I include paths; you have to find for yourself where those java***/include and java***/include/linux are located. Mine are located in: /usr/lib/jvm/java-1.5.0-sun/include and /usr/lib/jvm/java-1.5.0-sun/include/linux, respectively, since I used Java SDK 1.5 (which I installed through Synaptic Package Manager). So my actual command looked like this:

$ gcc -fpic -c example.c example_wrap.c -I /usr/lib/jvm/java-1.5.0-sun/include -I /usr/lib/jvm/java-1.5.0-sun/include/linux

Don't forget the .../linux include directory, otherwise it will give some nasty errors. OK, that should compile nicely. Next step, compile and create the shared object.

$ gcc -shared example.o example_wrap.o -o libexample.so
Nothing to be changed here. So basically the compiler will take the object file (note the .o extension) example.o and example_wrap.o, and creates the output file which is the shared object libexample.so (i.e. library -- hence the .so extension). IMPORTANT: Note that for Linux, the output file must start with lib*** (in this case: lib+example = libexample.so), otherwise it won't work.

So now you'll have these files:

example.java
example.class
example.o
exampleJNI.java
exampleJNI.class
example_wrap.c
example_wrap.o
libexample.so

Can you see where we're going with this? We now have a example.class.... which means, now we can have a main Java routine (method, function, whatever) that instantiates (creates) the object named 'example' and thus can access the variables and/or functions (methods) of the object 'example', which variables and functions are actually defined in example.c.

So, let's create that Java routine that calls those lovely functions in example.c (again, this is from the SWIG tutorial page):

/* main.java */
public class main {
public static void main(String argv[]) {
System.loadLibrary("example");
System.out.println(example.getMy_variable());
System.out.println(example.fact(5));
System.out.println(example.get_time());
}
}
In case you're wondering, yeah, I already shown this code. But now it should be clearer; you can notice that we're calling the example functions by: "example." there. So now you just need to compile this main.java file by:

$ javac main.java
Now, here's one part where I had some problems. I got these error messages:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no example in java.library.path

Unfortunately, I didn't do the troubleshooting systematically, that I couldn't really tell which steps fixed the problem, and which don't. But, there is one more step that was not explicitly said in the SWIG tutorial. There are some conversations in some forums (alas, I forgot the link) that said you HAVE TO set the LD_LIBRARY_PATH to where your .so file is. You can do:

export LD_LIBRARY_PATH=:$LD_LIBRARY_PATH

(Don't use the brackets "<>") Apparently, that error is one that most commonly occurs. I think it should be safe to set that path before you compile the .java file, but my experience with it is that I set that path AFTER I compiled the example.i with swig, gcc the *.c files, and the gcc -shared stuff, and I kept getting error messages during compilation. So I deleted all the files that were generated by SWIG, and recompiled the whole thing from scratch. After the javac compilation was successful, I ran the main.class with:

$ java main
And it worked!

$ java main
3.0
120
Fri Nov 9 00:17:37 2007

Hooray!!! Unfortunately, I'm still not sure what's the proper procedure for an error-free, trouble-free, headache-free compilation.

Another thing I want to add is, be careful when you're writing your Java program in packages. I gave packages names on my main.java, example.java, and exampleJNI.java, and here's the error that I got:

$ javac main.java
main.java:8: cannot find symbol
symbol : variable example
location: class facerecognition.main
System.out.println(example.getMy_variable());
^
main.java:9: cannot find symbol
symbol : variable example
location: class facerecognition.main
System.out.println(example.fact(5));
^
main.java:10: cannot find symbol
symbol : variable example
location: class facerecognition.main
System.out.println(example.get_time());
^
3 errors

Unfortunately, I have limited knowledge in compiling Java from the Terminal (I've been using Eclipse). I think if using packages, compiling requires additional arguments:

$ javac -package [package name] [main program].java

I think. If anyone can fill me in on that, I'll really appreciate it.

So there you have it, my experience making wrappers for C/C++ for Java using SWIG. Now I have to create a GUI in Java for application using OpenCV...

Some useful links:
http://www.swig.org/Doc1.3/Java.html
http://www.dabeaz.com/cgi-bin/wiki.pl?SwigFaq/Java