The ``JNI HOW-TO'' by Udo Schuermann

Copyright © 1998,2000,2001 RingLord Technologies and Udo K. Schuermann
All rights reserved
Revised: 16-December-1998
Revised: 19-October-2000
Revised: 10-November-2000
Revised: 21-December-2000
Revised: 10-January-2001
Revised: 11-January-2001

This document can be found at http://www.ringlord.com/platform/java/jni-howto.html

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.


The Goal

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.

What you will need

The native code library that supplies us with this code is:

/usr/lib/libuuid.so

And the include file that matches it is:

/usr/include/uuid/uuid.h

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.


Creating the Java program to call native code

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 likely become something like 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

Creating the .h (include) file

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. 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

Creating the native code (.c) file

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.

Compiling the native code

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 -G -f /usr/lib/libuuid.so UUID.o -o libjava-rlt-uuid.so

The -G option is required here.

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:

static String mapLibraryName( String libname )

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 ;-)

Ready? GO!!

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}

Troubleshooting

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:

  1. Can't compile the Java program

  2. Can't generate the .h file

  3. Can't compile the .c file

  4. Can't link the shared library

  5. Can't run the com.ringlord.util.UUID program

A Makefile for convenience

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  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 -G -f /usr/lib/libuuid.so UUID.o -o libjava-rlt-uuid.so

Map Java Library Names to Native Filesystem Names

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])+"'" );
      }
  }
}

Copyright © 1998,2000,2001 RingLord Technologies and Udo K. Schuermann