As we have ourselves struggled through some of the finer details of using the Java Native Interface (JNI) recently, we thought that sharing our new-found insights might help others understand the details better.
The following instructions for accessing native code from Java through the JNI interface apply to a Unix system with gcc. If you are using Microsoft Windows, Apple Macintosh, or another non-Unix operating systems then you can obviously not follow these instructions too blindly…
Note: There is a trouble-shooting section at the end of this article that addresses (and has always addressed) the majority of the questions we receive from readers.
Please note that one of our readers, Adrian Agapie, has made us aware of the possibility that the information below may not work with JDK 1.1 (e.g. JDK 1.1.8); he got it to work with JDK 1.2, though, so please be aware that different rules may apply to different (major?) versions of the JDK. Without having investigated the matter myself, I propose that there is likely some significant differences with JNI between what is Java 1 and Java 2. Caveat emptor.
We will call a method in a native library and return a java.lang.String to the calling Java application. This is useful when we need to access some machine dependent information or perhaps call on a faster native code routine to accomplish what would take too long in Java's interpreted code model.
In this particular case we will make use of a native library to obtain a UUID (Universally Unique Identification). This is a 128-bit quanity that depends on the network card's unique 6-byte (48 bit) address, a representation of the current machine time (hopefully accurate), as well as a counter to provide a resolution of up to 10000 unique identification per timestamp. I think the clock is deemed accurate to nano seconds, but I'm probably off there.
The native code library that supplies us with this code is:
And the include file that matches it is:
If you have these files on your system, then you can work through the examples below verbatim and should have no trouble getting this to work. If you do not have either of the above files you must first choose something to accomplish, then apply some thought as to where your project differs from the one we are presenting here.
The following program will compile with Sun's javac, Blackdown's port of the JDK to Linux, and with IBM's fantastic Jikes compiler. The comments in the code will explain what the code is doing, including how to bind to a native code library and call native methods in it:
UUID.java |
package com.ringlord.util; public class UUID { /** * The program entry point. Call it as: * java com.ringlord.util.UUID with an optional numeric * parameter 1 or higher for the total number of UUIDs to be generated. */ public static void main( String args[] ) { int count; try { if( (count = Integer.parseInt(args[0])) < 1 ) { count = 1; } } catch( Exception e ) { count = 1; } for( int i=0; i<count; i++ ) { // create a new UUID object (this same class) // using the default constructor (see below) UUID test = new UUID(); System.out.println( "{" + test + "}" ); } } /** * Default constructor: calls the getUUID() method which is defined * (below) as a native method! */ public UUID() { super(); // always a good idea to call the super constructor uuid = getUUID(); // call the native method } /** * A convenient way of ensuring that the String representation of this * object is the UUID itself. This makes for very easy ways of printing * the object's value (see the main(java.lang.String[]) method above) */ public String toString() { return uuid; } /** * This is the defininition of our native method. NOTICE THAT IT HAS NO * BODY. The static block following it is executed as part of the class * construction. */ private native String getUUID(); static { // Load a library whose "core" name is 'java-rlt-uuid' Operating // system specific stuff will be added to make from this an actual // filename: Under Unix this will become // libjava-rlt-uuid.so while under Windows it // will become java-rlt-uuid.DLL System.loadLibrary("java-rlt-uuid"); } // the constructor stores here the result of the native method call private String uuid; } |
We compile this code with the following command (we use IBM's Jikes because it's an order of magnitude better than anything else we've used; it's also Open Source, and it's available for free):
Command line: | jikes UUID.java |
The include file is easily generated with the javah program that should be part of every self-respecting Java development environment. This command will parse the byte-compiled Java class file and generate an appropriate file that tells us all we need to know to write the native code that the Java class wants to call. Be sure to use the fully qualified name of the class, not just the relative name of the class file! This is how javah is called:
Command line: | javah -jni com.ringlord.util.UUID |
We have provided the output of this program below so that you get to see all the pieces in operation. Again, this is machine-generated code and should not be provided by you! You will notice that the filename reflects the name of the package and class name (com.ringlord.util.UUID) from which the file was generated.
com_ringlord_util_UUID.h |
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_ringlord_util_UUID */ #ifndef _Included_com_ringlord_util_UUID #define _Included_com_ringlord_util_UUID #ifdef __cplusplus extern "C" { #endif /* * Class: com_ringlord_util_UUID * Method: getUUID * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_ringlord_util_UUID_getUUID (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif |
We are now ready to write the native code. We are under no obligation to use the long and unwieldy name that the javah program chose. We'll just use "UUID.c" for our purposes, although we will have to include the .h file from above:
UUID.c |
#include <stdio.h> #include <uuid/uuid.h> #include "com_ringlord_util_UUID.h" jstring Java_com_ringlord_util_UUID_getUUID(JNIEnv *env, jobject this) { jstring value; /* the return value */ uuid_t id; /* a UUID we're building */ unsigned char buf[40]; /* working buffer (really only need 37) */ int i,j; /* counters into 'id' & 'buf' */ /* generate a uuid as per 'libuuid.so' library call (Unix). */ uuid_generate( id ); /* format the (16) bytes we get back as follows: * 01020304-0102-0102-0102-010203040506 * including the dashes in the middle. */ for( i=0,j=0; i<sizeof(uuid_t); i++ ) { sprintf( buf+j, "%02x", id[i] ); j += 2; if( (i==3) || (i==5) || (i==7) || (i==9) ) { *(buf+j) = '-'; ++j; } } /* N.B. * as we always end with a normal digit that is added with an sprintf() * call, it is not necessary to ensure that the string is 0 terminated: * sprintf() has already accomplished this for us. */ /* now create from this nicely formatted result a Java-native UTF-8(?) * string (java.lang.String) and return that as the fruit of our labors. */ value = (*env)->NewStringUTF(env,buf); return value; } |
The "uuid/uuid.h" file defines the uuid_t type (which happens to be 16 unsigned characters, or 128 bits of data for the UUID) as well as function prototypes that the compiler should use to ensure that we're not messing up on parameters or return types.
If you don't have this file then you probably don't have the shared code library either. This particular example won't work for you in that case, but you are still likely to learn a lot from continuing to read.
We will first examine each of the two steps in the compilation separately, and afterwards present you with a handy Makefile that you can use to build and rebuild the example.
The first step is to compile the UUID.c file. The following centered line(s) are actually all one command line:
Command line: | gcc -I/java/jdk/include -I/java/jdk/include/genunix -c UUID.c -o UUID.o |
You will notice that we are specifying the /java/jdk/include directory. We have installed the JDK class files in the /java/jdk/ directory. Your own choice probably is different. The point is that there is an include directory with some platform independent and platform dependent files. For the platform dependent files we choose the "genunix" subdirectory.
The -c UUID.c command compiles (but does not link) our native code. The output goes to the UUID.o file (as per -o UUID.o option).
The only step left to accomplish is to generate a loadable file. Again, the following centered line(s) are actually all one command line:
Command line: | ld -shared -f /usr/lib/libuuid.so UUID.o -o libjava-rlt-uuid.so |
The -shared option is required here. In versions of this document prior to 5-Dec-2002 this option was described as -G which is (no longer) correct.
To ensure that the native code library can properly link to what may otherwise appear to be a quite elusive shared library, we need to give the linker a reference to the actual file that contains unresolved external symbols, such as the call to the generate_uuid() method in the UUID.c program we wrote. This is what the -f /usr/lib/libuuid.so parameter does.
And again, output goes to a specific file libjava-rlt-uuid.so the name of which we discussed in the UUID.java source code. This name is, naturally, platform dependent.
There is a method in java.lang.System that you can use to find out what actual filename the native code shared object file is expected to have:
At the very end of this article is a little Java program that you can use to explore the native names (in case you're too lazy to write your own ;-)
You should now have the following files; the only ones you actually need to run the program are the ones indicated:
UUID.c UUID.class UUID.java UUID.o com_ringlord_util.UUID.h libjava-rlt-uuid.so
You run the program in the following manner:
Command line: | java com.ringlord.util.UUID 5 |
…and you should see 5 universally unique identifiers appear, similar to the following:
{1e38f892-94a6-11d2-8143-00e018901983} {1e394fe0-94a6-11d2-8143-00e018901983} {1e396f98-94a6-11d2-8143-00e018901983} {1e398eba-94a6-11d2-8143-00e018901983} {1e39af26-94a6-11d2-8143-00e018901983}
There are bound to be some glitches that occur. Before you use the Makefile below, make sure that each step alone works properly. Use the following checklist to see if you can't find the problem:
Makefile |
INCLUDE=/java/jdk/include all: make uuid jikes *.java uuid: UUID.class libjava-rlt-uuid.so clean: rm -f *.class *.so *.o cleanall: rm -f *.class *.so *.o *~ UUID.class: UUID.java jc UUID.java javah -jni -classpath ${CLASSPATH} com.ringlord.util.UUID libjava-rlt-uuid.so: UUID.c com_ringlord_util_UUID.h gcc -I$(INCLUDE) -I$(INCLUDE)/genunix -c UUID.c -o UUID.o ld -shared -f /usr/lib/libuuid.so UUID.o -o libjava-rlt-uuid.so |
This code might be useful to you if you are curious what the physical name of the library file should be that Java expects to be loading when you use a System.loadLibrary call:
NativeName.java |
final class NativeName { static final public void main( final String[] args ) { if( args.length == 0 ) { System.out.println( "Give me one or more Java library names "+ "to display as native names" ); return; } final String os = (String)System.getProperties().get("os.name"); for( int i=0; i<args.length; i++ ) { System.out.println( "On '"+os+"' "+ "the library '"+args[i]+"' "+ "maps to '"+System.mapLibraryName(args[i])+"'" ); } } } |
Long(!) overdue, we thank Sun Microsystems for providing a link to this page. (12-Oct-2001)
All content is copyright © Ringlord Technologies unless otherwise stated. We do encourage deep linking to our site's pages but forbid direct reference to images, software or other non-page resources stored here; likewise, do not embed our content in frames or other constructs that may mislead the reader about the content ownership. Play nice, yes?