Ringlord Technologies Products

The "JNI HOW-TO" by Udo Schuermann

Copyright © 1998,2000,2001 RingLord Technologies and Udo K. Schuermann
All rights reserved
Revised: 17-Jan-2006
Revised: 5-Dec-2002
Revised: 12-Oct-2001
Revised: 6-Sep-2001
Revised: 1-Jun-2001
Revised: 11-Jan-2001
Revised: 10-Jan-2001
Revised: 21-Dec-2000
Revised: 10-Nov-2000
Revised: 19-Oct-2000
Revised: 16-Dec-1998

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.


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

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

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

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
    • Is your Java compiler properly installed and in the path?
    • Is your CLASSPATH set? Alternately, try -classpath ${CLASSPATH} as arguments to the compiler.
    • The UUID.java program above is defined as part of the com.ringlord.util package. Did you create it in a directory com/ringlord/util/?

  2. Can't generate the .h file
    • Is the javah program (or equivalent) available?
    • Is your CLASSPATH set? (see (1) above)

  3. Can't compile the .c file
    • Are you using gcc 2.7.2 or later? We compiled fine with version 2.7.2.3
    • Are you specifying directories where some of the following files are located? jni_md.h, byteorder_md.h, etc. If not, make sure you locate the appropriate directory where the JDK's include files are located. These are required to provide the proper translation to native architecture conventions.
    • Do you actually have the "uuid/uuid.h" file in your path? This file is located (on our system) in /usr/lib/uuid/uuid.h
    • Are you compiling but not linking the program, i.e. are you using the -c switch on gcc?

  4. Can't link the shared library
    • Make sure you have ld available; it's the native linker for your system.
    • Make sure you give it the -shared argument, otherwise it will not generate the correct type of file.
    • Make sure you give it the -f /usr/lib/libuuid.so argument. You must ensure that the libuuid.so file exists on your system. Substitute the correct path where this file lives. The -f option tells the linker that at run-time it is to resolve symbols using that file (in addition to more generic I/O libraries and the like.)
    • Make sure you give it the UUID.o name, too, which is the result of the previous step's compilation.

  5. Can't run the com.ringlord.util.UUID program
    • "…cannot open shared object file…"
      The libjava-rlt-uuid.so file is not available. Look at the name of the file and compare it to the core name that the UUID.java program is hoping to find.

    • "…UnsatisfiedLinkError…"
      There are two possibilities of which I know:
      1. Your shared object (e.g. libjava-rlt-uuid.so file) cannot be located by Java. There are several solutions to this:
        1. The first is to ensure that you use the fully qualified class name, not just the relative name of the class file. We've now emphasized this above as well.

        2. The second (and the only other I can recommend at this point) is to add the directory where the .so file is stored to the LD_LIBRARY_PATH environment variable before running the JVM; Java will pick up a few additional directories for its own use. That combination will be reflected in the JVM's java.library.path property. You can check the value of System.getProperties().get("java.library.path");

        3. The third applies if you are using Sun Solaris. Apparently adding -g -DSOLARIS2 to the compiler options will also help.

        4. The fourth is to directly modify the JVM's java.library.path property (when starting the JVM, not when it's already running!) using the -Djava.library.path=… argument to the JVM. You'd first have to find out what other libraries your JVM would need. This is about the very, very last solution I'd look at because it's completely non-portable and will vary depending on where the JVM is installed. Why do I even mention such a shoddy idea? Because I can, that's why; and to take this opportunity to steer you to the LD_LIBRARY_PATH solution instead, just in case you thought of this one yourself :)

      2. Something is wrong with the shared library you created: it's not the one that the Java program was hoping to find, or the methods it is looking for are not actually in the library. Check the .h file and make sure you use the same signature for the method as what Java will expect to be calling!

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 ${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

Map Java Library Names to Native Filesystem Names

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

Other Resources

Sun Microsystems: Java Native Interface: Programmer's Guide and Specifications
A book as well as downloadable source code of the book's examples, which may lend additional insight into JNI.
Kelly O'Hair's Blog: Compilation of JNI Code
Lots of details on Solaris, Linux, and Windows compilation of JNI code with explanation of compiler options.

Thanks

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?

Find something useful here? Maybe donate some Bitcoin!