The Java Native Interface (JNI) comes with the standard Java Development Kit (JDK) from Sun Microsystems. It permits Java programmers to integrate native code (currently C and C++) into their Java applications. This article will focus on how to make use of the JNI and will provide a few examples illustrating the usefulness of this feature. Although a native method system was included with the JDK 1.0 release, this article is concerned with the JDK 1.1 JNI which has several new features, and is much cleaner than the previous release. Also, the examples given will be specific to JDK 1.1 installed on the Solaris Operating System.
Recently I found myself in the position of needing to place a user-friendly Java Graphical User Interface (GUI) on a very bland, keyboard input menu-driven program that I had written in C. The C program was just a wrapper which made calls to a very rich C library. Having had some experience with Java, I knew the basics of creating a simple GUI (creating a frame and adding panels to the frame consisting of buttons, text fields etc.). I needed to find a way I could interface the C program and the Java GUI. I was certainly not about to rewrite the meat of the C program just for the sake of the interface. Then I came to know about the Java Native Interface, or JNI, which allows Java programmers to make calls to native code.
The biggest change which came with JDK 1.1 is the ability for a native application to interface with the Java Virtual Machine (JVM). In the native method system from JDK 1.0, an application programmer could call native code but the native code could not invoke methods from the Java application/applet. Not being able to work with the JVM meant that the interface was very one-sided. This placed restrictions on anyone doing native interface programming which required more from the Java side than just calling out native methods [1]. Sun solved this problem in JDK 1.1 with the introduction of the JNI. Using the JNI, a programmer can have the Java application make a native call, then have the native method make a call back to a Java method, and so on. The JNI also supports invocation of the JVM. What this means is that a programmer can start up the JVM, call methods and create Java objects all within native code. This is done by running the native code executable after compiling and linking the native code, the libraries shipped with the JDK and the JNI function library.
The other major change deals with portability. With the older system, inconsistencies occurred when moving native method code to different machines because of platform dependent features such as data sizes [1]. With the current version, the creators of the JNI have provided for a clean, uniform definition of all data sizes, so that the application programmer does not need to make any source level changes during a port [2].
Rather than writing Hello World programs (which can be very beneficial), lets put some of the features of the JNI to use for an actual problem.A powerful program which simulates fluid pressure along a pipeline is used by an engineering team to perform numerical analysis and design analysis upon the structure. Unfortunately, to input data into the simulator one must edit text files and manually perform repetitive, and archaic commands. Rewriting this legacy code is not an option because it is not only functional, but is very complex, and makes use of many other legacy code libraries. A front end GUI is desired for the simulator. This GUI will be written in Java and will interface with the simulator which was written in C.
First, let us assume that the pipeline simulator accepts command line input for various initialization settings. Our first task is to find a way of having those inputs sent to the simulator. Here is a sample program which takes in command line arguments given to a Java program, and makes a native call with those arguments.
class Arguments
{
private native void setArgs (String[] javaArgs);
public static void main (String args[])
{
Arguments A = new Arguments();
newArgs[] = A.setArgs(args);
}
static
{
System.loadLibrary("MyArgs");
}
}
There are 3 points of interest here.
javac Arguments.java
javah -jni Arguments
This will create a file named Arguments.h which can be found online at http://www.csc.calpoly.edu/~fouzi/crossroads/Arguments.h.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Arguments */
#ifndef _Included_Arguments
#define _Included_Arguments
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Arguments
* Method: setArgs
* Signature: ([Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Arguments_setArgs
(JNIEnv *, jobject, jobjectArray);
#ifdef __cplusplus
}
#endif
#endif
#include "/usr/local/java/include/jni.h"
#include "Arguments.h"
#include <stdio.h>
JNIEXPORT void JNICALL
Java_Arguments_setArgs (JNIEnv *jenv, jobject job, jobjectArray oarr)
{
/* obtain the size the array with a call to the JNI function
GetArrayLength() */
jsize argc = (*jenv)->GetArrayLength(jenv, oarr);
/* Declare a char array for argv */
char const* argv[128];
int i;
for (i = 1; i < argc + 1; i++)
{
/* obtain the current object from the object array */
jobject myObject = (*jenv)->GetObjectArrayElement(jenv, oarr, i-1);
/* Convert the object just obtained into a String */
const char *str = (*jenv)->GetStringUTFChars(jenv,myObject,0);
/* Build the argv array */
argv[i] = str;
/* Free up memory to prevent memory leaks */
(*jenv)->ReleaseStringUTFChars(jenv, myObject, str);
/* print the argv array to the screen */
printf ("argv[%i] = %s\n",i,argv[i]);
}
/* Increment argc to adjust the difference between Java and C arguments
argc++;
Call a pipeline simulator function which uses command line arguments
initializePipeline(argc,argv);
*/
return;
}
cc -G -I/usr/local/java/include -I/usr/local/java/include/solaris Arguments.c -o libMyArgs.so NOTE: Substituting gcc for cc will also work.Before running the program, you must make sure that the shared library file is placed in a directory which is contained within the LD_LIBRARY_PATH environment variable. Otherwise it will not run, and will result in an error.
java Arguments firstargument secondArgument thirdargument
argv[1] = firstargument
argv[2] = secondArgument
argv[3] = thirdargument
class Compressor
{
private native void setCompression(double pressure);
public static void main (String args[])
{
Compressor C = new Compressor();
double pressure = 5.0;
C.setCompression(pressure);
}
public void drawCompressionIncrease(double amount)
{
System.out.println ("Increase: Amount = " + amount);
}
public void drawCompressionDecrease(double amount)
{
System.out.println ("Decrease: Amount = " + amount);
}
static
{
System.loadLibrary("MyCompressor");
}
}
javac Compressor.java
javah -jni Compressor
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Compressor */
#ifndef _Included_Compressor
#define _Included_Compressor
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Compressor
* Method: setCompression
* Signature: (D)V
*/
JNIEXPORT void JNICALL Java_Compressor_setCompression
(JNIEnv *, jobject, jdouble);
#ifdef __cplusplus
}
#endif
#endif
javap -s -p Compressor
The -s flag tells javap to output signatures instead of Java types, and the -p
forces that private methods be included as well [2].
This gives us the following:
Compiled from Compressor.java
synchronized class Compressor extends java.lang.Object
/* ACC_SUPER bit set */
{
private native void setCompression(double);
/* (D)V */
public static void main(java.lang.String[]);
/* ([Ljava/lang/String;)V */
public void drawCompressionIncrease(double);
/* (D)V */
public void drawCompressionDecrease(double);
/* (D)V */
Compressor();
/* ()V */
static static {};
/* ()V */
}
#include "/usr/local/java/include/jni.h"
#include "Compressor.h"
#include <stdio.h>
JNIEXPORT void JNICALL
Java_Compressor_setCompression (JNIEnv *jenv, jobject job, jdouble amount)
{
/* obtain the class of the "this" object */
jclass myClass = (*jenv)->GetObjectClass(jenv, job);
/* obtain the method ID of the drawCompressionIncrease Java Method */
jmethodID increaseMID = (*jenv)->GetMethodID(jenv,myClass,
"drawCompressionIncrease", "(D)V");
/* obtain the method ID of the drawCompressionDecrease Java Method */
jmethodID decreaseMID = (*jenv)->GetMethodID(jenv,myClass,
"drawCompressionDecrease", "(D)V");
double change;
/* set the change to a value returned by a pipeline simulator call
change = pipeline_set_compression(amount); */
/* method invocation */
change = 1.0;
if (change > 0)
(*jenv)->CallVoidMethod(jenv,job,increaseMID,change);
else
(*jenv)->CallVoidMethod(jenv,job,decreaseMID,change);
return;
}
cc -G -I/usr/local/java/include
-I/usr/local/java/include/solaris
Compressor.java -o libMyCompressor.so
java Compressor
Increase: Amount = 1.0
An article written on Java that does not mention platform independence is probably an incomplete article. How does platform independence play into the JNI? Well, with languages like C and C++, software builders find themselves porting their software to various systems. In our example we can assume that the legacy pipeline simulator had been ported to both a UNIX machine such as a Sun Sparc, and a DOS/Windows PC. With some other non-platform independent language we would have been forced to port the GUI along with the legacy code. With Java that is not necessary. Our GUI can simply be moved over to the PC and plugged into that simulator with a few minor modifications made to the shared library. No new code, no hassle!
The examples I have provided above are just a few reasons for why someone would want to use the JNI. The need to interface with legacy code will always be there. By providing programmers with the JNI, Java makes itself more flexible and available to a wider range of applications.
My expectations before looking into the JNI were that it would be a 3 week intensive lesson just trying to figure out how to get it to work. It turned out to be quite the opposite. After reading through the tutorials ( http://java.sun.com/docs/books/tutorial/native1.1) available from Sun Microsystems, I was up and running within a few hours. The developers of the JNI have done an excellent job of providing Java programmers with a helpful, and easy to use tool. On the downside though, there is not very much literature out there about the JNI. The tutorial by Beth Stearns at the Sun Microsystems web page [1] is very helpful, but more needs to be written about it. Hopefully in writing this article, I will not only inform some people about the JNI but it will prompt others to do more in-depth research and provide us all with more literature about this great feature of Java.
Want more Crossroads articles about Java? Get a listing or go to the next one or the previous one.
Last Modified:
Location: www.acm.org/crossroads/xrds4-2/jni.html