JNI and Gluegen
I know I can develop software faster using Java so I want to learn how to interface with native code using JNI. No solution seems particularly elegant and going for a completely manual approach seems to involve a lot of boiler plate and work.
I had a quick look around google and Wikipedia and a couple of options to help with code generation - SWIG and Gluegen. The latter particularly caught my eye, in particular the ability to have structs treated as Java classes.
It took me a little while to get a hello world app running, so instructions are below to remind me in future and for anyone else that might find it useful.
Obtaining and building Gluegen
- git clone git://github.com/mbien/gluegen.git gluegen
- cd gluegen/make
- ant clean all
This gives the required jars in the build directory
- gluegen.jar for generating the Java version of C code and the bridging C code.
- antlr.jar for parsing C code.
- gluegen-rt.jar required at runtime.
Creating a sample application
The C code
int one_plus(int a) {
return 1 + a;
}
Taken from the Gluegen website
The Java code
import testfunction.*;
class Test
{
static {
System.loadLibrary("nativelib");
}
public static void main(String args[])
{
System.out.println(TestFunction.one_plus(5));
}
}
I'm jumping ahead a little here. This code assumes that the C code is in a package call testfunction and that there is a native lib called nativelib.
Using Gluegen
Running Gluegen creates binding C code and the Java code that defines the C methods.
Gluegen needs some configuration to guide its behaviour. It's here that the Java package and class names are defined.
Package testfunction Style AllStatic JavaClass TestFunction JavaOutputDir gensrc/java NativeOutputDir gensrc/native
To run Gluegen, I went for the ant approach. My build file is below
<?xml version="1.0"?> <project name="sampleProject" basedir="."> <path id="gluegen.classpath"> <pathelement location="gluegen.jar" /> <pathelement location="antlr.jar" /> </path>
<taskdef name="gluegen" classname="com.jogamp.gluegen.ant.GlueGenTask" classpathref="gluegen.classpath" /> <target name="build"> <gluegen src="function.h" config="function.cfg" emitter="com.jogamp.gluegen.JavaEmitter"> <classpath refid="gluegen.classpath" /> </gluegen> </target> </project>
running ant build results in a new directory "gensrc" containing a "native" and a "java" directory.
The generated native code is the Java to C glue code. The only thing worth noting about it is that it pulls in JNI.h C header file.
The generated java code has the "native" method and pulls in the runtime lib.
package testfunction;
import com.jogamp.gluegen.runtime.*; import com.jogamp.common.os.*; import com.jogamp.common.nio.*; import java.nio.*;
public class TestFunction {
/** Interface to C language function: <br> <code> int one_plus(int a); </code> */
public static native int one_plus(int a);
} // end of class TestFunction
Building the results
For me, this was the most challenging part. Most of the above is covered quite well on the Gluegen website. The following isn't.
Building the C code
gcc -Wl,-soname,libnative.so -o libnativelib.so -fPIC --shared gensrc/native/TestFunction_JNI.c function.c -I/usr/lib/jvm/java-6-sun/include -I/usr/lib/jvm/java-6-sun/include/linux /usr/lib/jvm/java-6-sun-1.6.0.26/jre/lib/amd64/server/libjvm.so -lc
Quite a lot here.
- gcc is used to build the code
- -Wl passes options to the linker. In this case, the shared object name is set to libnative
- -o specifies the output filename
- -fPIC was required to avoid an error message along the lines of
- /usr/bin/ld: /tmp/ccI3vLJd.o: relocation R_X86_64_PC32 against symbol `one_plus' can not be used when making a shared object; recompile with -fPIC
- --shared causes a shared object to be built
- TestFunction_JNI.c is the Gluegen generated code and function.c is the actual implemenation. These are the two files that are actually being built.
- the two -I options specify the locations of the JNI headers (jni.h)
- libjvm.so is also to be passed in.
Building the Java Code
javac Test.java gensrc/java/testfunction/TestFunction.java -cp ../gluegen/build/gluegen-rt.jar
More straightforward. The Gluegen code and the code I wrote to drive it is compiled. The gluegen runtime jar needs to be on the classpath.
Running the Result
java -Djava.library.path=. -cp gensrc/java:. Test
6
Running the Java requires the native lib path to be specified. The program then specifies 6 as expected. Phew!
(Re)Learning C
I've spent the day skimming over http://c.learncodethehardway.org/ I've currently only half written but covers all the basics including things like using valgrind, writing complete programs and obviously all the basic language constructs. It was a useful refresher and I would recommend it. I certainly think it has a lot more depth than I've had time to get out of it but it has still server as a great refresher/overview.
I've started looking at the librailfare code with my newly refreshed C knowledge. My previous aspirations for improving my Vim skills have gone out the window. I've been happily using Vim for the learncodethehard way examples but once I started wanting to poke round a multi-file project, I reached for trusty Eclipse. The CDT looks a lot more polished than when I last looked at it a few years ago and coupled with Msys and MiniGW I'm happily navigating my way around the code using familiar key bindings!
I've got librailfare compiled and running on linux (Ubuntu). I had to install the C Minimal Perfect Hashing libs. But I can't get it compiling on Windows yet - I need to work out how to compile the CMPH libs on windows first.
Fares Data
I'm going to start looking at librailfare (http://librailfare.sourceforge.net/) for fare data. Looks like it could be a high performance way of getting detailed fares data.
My C is a little rusty so I'm working my way through "Learn C the hard way". (http://c.learncodethehardway.org/) It's still in Alpha but the overall structure seems to be there. Nothing too groundbreaking yet but I'm liking the style. I'm running Windows on my laptop so I've got Ubuntu Server in a VM for my compilation needs . Should be a good excuse to take my Vim skills to the next level too!
Once I've got the code compiling and I can query fares data I need to decide what to do with it. Current thoughts are porting it to Java (useful for me), porting it to .Net (useful for someone else) and/or exposing a web API to it.