Native Code with JNI

From Processing

Jump to: navigation, search

JNI is the Java Native Interface, a way to call native machine code from Java programs. It allows you to integrate existing libraries with your Processing code. This tutorial doesn't assume you have any prior experience with C or C++, but you will need to know what you're doing with those languages if you want to make use of JNI in a real project.

Contents

Why JNI?

Java code is (mostly) run from bytecode on a virtual machine, and in certain circumstances this will be slower than native code. You might also want to use a library which is not available for Java. The disadvantages are that it pretty much stops your code being cross platform, and makes deploying as an applet harder.

You will need:

  • 1 C++ compiler
  • 1 copy of processing
  • 1 copy of the latest Java SDK
  • 1 sketch with a heavy computation to perform

If you install the full java SDK, I find it useful to put it in C:\j2sdk\ (without the version number provided by default) because it means you shouldn't need to update your paths when you upgrade to the next version.

Installing MinGW and MSYS

For C++ compiling in Windows, I use MinGW which is a version of GCC. Download MinGW-3.1.0-1.exe and MSYS-1.0.10.exe from

http://www.mingw.org/download.shtml and install them in that order. Mac OS X and Linux users almost certainly have GCC available already, so you can skip this section. If you're familiar with C++, you might like to use your own environment.

If you use the default install settings then running MSYS should bring up a unix-like terminal window.

tomc@MACHINE ~
$  

MSYS sets up a home directory with your username. On the default install this was at C:\msys\1.0\home\tomc\, you can check this using the pwd (print working directory) command.

tomc@MACHINE ~
$ pwd
/home/tomc

Setting Up A C++ JNI Project

Make a folder called code, and change to that folder.

tomc@MACHINE ~
$ mkdir code
code

tomc@MACHINE ~
$ cd code

tomc@MACHINE ~/code
$  

Now you need to make two files. One Java file where your function will be defined so that Processing can see it, and one C++ one which will do the work. To make them on the MSYS commandline, use the touch command...

tomc@MACHINE ~/code
$ touch MyHeavyFunction.java

tomc@MACHINE ~/code
$ touch MyHeavyFunction.cpp

To make life easier, I made my heavy function into a self contained static version. This way I don't have to exchange data backwards and forwards between the DLL and the VM. So where in Processing I originally had this:

void draw() {
  // draw stuff
  myHeavyFunction(width, height, pixels);
}
 
static void myHeavyFunction(int w, int h, int[] pix) {
  // do complicated stuff to pixels
}

I now have this in the draw loop:

void draw() {
  // draw stuff
  MyHeavyFunction.myHeavyFunction(width,height,pix);
}

And MyHeavyFunction.java contains just the following:

class MyHeavyFunction {
  static {  
    System.loadLibrary("MyHeavyFunction");
  }
 
  static native void myHeavyFunction(int w, int h, int[] pix); 
}

The native keyword means that we won't implement the method right here and now, but the compiler believes us when we say it will be available somewhere, somehow. The static block gets run the first time the class is loaded, and it will load up MyHeavyFunction.dll (or .so on Linux) for us.

Now the hard bit(!). Most C++ programs use header files which tell the compiler what functions are going to be defined. The Java SDK provides a tool called javah which will create this for us from a Java .class file with a native method. So first you need to compile the Java file:

tomc@MACHINE ~/code
$ javac MyHeavyFunction.java  

Then you run javah on the resulting class:

tomc@MACHINE ~/code
$ javah -o MyHeavyFunction.h MyHeavyFunction

This makes a file called MyHeavyFunction.h. Open it up. It should have a lot of messy C code in it, but basically all you need is the function declaration, which should look like this:

JNIEXPORT void JNICALL Java_Blur_myHeavyFunction
  (JNIEnv *, jclass, jint, jint, jintArray);

Copy that into MyHeavyFunction.cpp. Then make the following changes:

  • include the header
  • add names to each of the arguments
  • make it into an empty function
 
#include "MyHeavyFunction.h"
 
JNIEXPORT void JNICALL Java_Blur_fastblur
  (JNIEnv *env, jclass c, jint w, jint h, jintArray pix) {
  // the hard work will be done here...
}

OK, save all that. Now, assuming the Java SDK is installed at C:\j2sdk, then do:

tomc@MACHINE ~/code

$ g++ -I/c/j2sdk/include -I/c/j2sdk/include/win32 -Wl,--add-stdcall-alias -shared -o MyHeavyFunction.dll MyHeavyFunction.cpp 

You should now have a DLL file. Move MyHeavyFunction.class and MyHeavyFunction.dll to the code folder in your sketch folder and you're ready to go.

Related Links

Processing Forum Post

Personal tools