Programmming in C/C++ with the Java Native Interface

Matthew Mead

Here is the quick overview of this example.

HelloWorld.java This is a trivial class with one method, but what makes the method special is the native keyword. This tells the Java compiler that the function is implemented in native code. Other Java classes that access this method are unaware that it is not a Java method.
class HelloWorld {

    // The native keyword is required by the compiler
    // to indicate that the function is implemented
    // in a shared library somewhere.

  public native void displayMessage();

    // Code within the static section is executed
    // when this class is initialized. Since part of
    // the functionality of this class resides in
    // a shared library, it must be loaded at runtime
    // when this class is initialized. 

  static 
  {

      // The shared library that contains the implementation
      // of the native methods is loaded here. The name of the
      // shared library file is arbitrary, but it is a good
      // idea to give it a meaningful name.

    System.loadLibrary("HelloWorldImp"); 

  }

}
Main.java This is simply the driver program for testing the native method in HelloWorld.java. Notice that this class is not required to do anything special in order to invoke a native method. In fact, the class is not even aware that the method is implemented in C++ and resides in a shared library somewhere.
class Main {

  public static void main(String[] args) 
  {
      // Nothing unusual here. A HelloWorld object
      // is created and a public method is invoked.

    HelloWorld hello = new HelloWorld();
    hello.displayMessage();
  }

}
HelloWorld.h This is the header file generated by the javah tool that is part of Sun's JDK. This file is compatible with both C and C++ compilers. The first line after the initial comment includes the jni.h file. This is why you may never need to include jni.h yourself.
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloWorld */

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    displayMessage
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_displayMessage
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
Notice how the name of the method has been "decorated" with two additional items:
JNIEXPORT void JNICALL Java_HelloWorld_displayMessage(JNIEnv *, jobject);
The first item, Java, is prepended to the name and is required for all functions. The second item is the name of the Java class, HelloWorld, where this method is declared. Finally, the name of the method appears. Underscores separate the parts. This is one of the reasons you don't want to create this header file manually. It is too easy to forget about little details like this, and leaving these embellishments off will render your program non-functional.

In addition to the modification of the function's name, there are also two other tags that have been applied to the function. These are JNIEXPORT and JNICALL. Basically, these two tags allow the function to be located in the shared library at runtime and are required.

JNIEXPORT void JNICALL Java_HelloWorld_displayMessage(JNIEnv *env, jobject obj) 

There are two other points of interest in this header file. The first point to notice is the function's arguments. As you recall, the displayMessage method declared in the Java code did not have any arguments. What gives? Every native method has two additional hidden arguments. The first argument is a pointer to the JNI runtime environment. Basically, it allows the functions to make callbacks into the JVM. You will see this used in more detail later. The second argument is a pointer to the object that called this function. In essence, it is like the this pointer in C++. Even if your functions never need to access either of these, you must still include them in the argument list for the function.

The second noteworthy point is the Signature: portion of the comment.

/*
 * Class:     HelloWorld
 * Method:    displayMessage
 * Signature: ()V
 */
This comment is also generated automatically and can help you to understand how Java types map to C/C++ types. There are two parts to the signature. The first part is enclosed within the parentheses and represents the number and type of arguments to the function. In this case, there were no arguments, so there is nothing between the parentheses. The second portion follows the closing parenthesis. Since the function didn't return anything (a void function), its type is V. Here's a list that maps Java types to signature characters:
Type Chararacter
boolean Z
byte B
char C
double D
float F
int I
long J
object L
short S
void V
array [
For example, a method that takes a double and a byte, and returns a boolean, would have the signature (DB)Z.

To specify an array, the "[" character is followed by the signature character (e.g. an array of floats would be "[F".)

To specify an object, the "L" is followed by the object's class name and ends with a semi-colon, ';' (e.g. the signature for a String is "Ljava/lang/String;".)

HelloWorldImp.cpp This file contains the implementation of the native method declared in the Java code. The easiest (and safest) way to make sure that you have the correct function name and signature is to copy it from the header file created by javah. Since this method is trivial, there is only one exectuable statement. If the Java class HelloWorld had declared other native methods, they, too, would all be implemented in this C++ file.

#include <stdio.h>
#include "HelloWorld.h"   // this header file was generated by javah
JNIEXPORT void JNICALL Java_HelloWorld_displayMessage(JNIEnv *env, jobject obj) 
{
  printf("Hello World!\n");
}

Common mistakes. Until you get used to the details, you are likely to make a few mistakes. The most common mistake generates an error message like this at runtime:
java.lang.UnsatisfiedLinkError: no HelloWorldImp in shared library path
There are several reasons why this message may be encountered.

Another common error message is:

java.lang.UnsatisfiedLinkError: displayMessage
This indicates that the shared library has been found and properly loaded, however the specified function (displayMessage here) cannot be located in the library. This is generally an indication that there is a mismatch between the method's signature in the Java code and the signature in the native code. For example, both functions below will compile (probably without any warnings) but at runtime, if the second function is what is implemented, it won't be found by the OS:
  // Correct. This function will be located by the OS at runtime.
JNIEXPORT void JNICALL Java_HelloWorld_displayMessage
               (JNIEnv *env, jobject obj) 
{
  printf("Hello World!\n");
}

  // WRONG! Extra parameter. This doesn't match the declaration
  // and therefore, at runtime, OS will not be able to locate
  // the function being called by the Java code.
JNIEXPORT void JNICALL Java_HelloWorld_displayMessage
               (JNIEnv *env, jobject obj, int extraParam) 
{
  printf("Hello World!\n");
}

Copyright © 1998 Sergio Antoy. All Rights Reserved
February 99, version 020199