Java Native Interface

From Free net encyclopedia

Revision as of 17:25, 4 April 2006; view current revision
←Older revision | Newer revision→

The Java Native Interface (JNI) is a programming framework that allows Java code running in the Java virtual machine (VM) to call and be called by native applications (programs specific to a hardware and operating system platform) and libraries written in other languages, such as C, [[C++]] and assembly.

The JNI is used to write native methods to handle situations when an application cannot be written entirely in the Java programming language such as when the standard Java class library does not support the platform-dependent features or program library. It is also used to modify an existing application, written in another programming language, to be accessible to Java applications. Many of the standard library classes depend on the JNI to provide functionality to the developer and the user, e.g. I/O file reading and sound capabilities. Including performance- and platform-sensitive API implementations in the standard library allows all Java applications to access this functionality in a safe and platform-independent manner. Before resorting to using the JNI developers should make sure the functionality is not already provided in the standard libraries.

The JNI framework lets a native method utilize Java objects in the same way that Java code uses these objects. A native method can create Java objects and then inspect and use these objects to perform its tasks. A native method can also inspect and use objects created by Java application code.

JNI is sometimes referred to as the "escape valve" for Java developers because it allows them to add functionality to their Java Application that the Java API can't provide. It is also used to interface with code written in other languages, like C++. Or, it is used for time-critical calculations or operations like solving complicated mathematical equations, since native code can potentially be faster than JVM code.

The JNI is not trivial and requires a considerable effort to learn, and some people recommend that only advanced programmers should use the JNI. However, the capability for Java to communicate with C++ and assembly removes any limitations on what function Java programs can perform. Programmers considering using the JNI should be aware that

  1. as mentioned before, the JNI is not an easy API to learn;
  2. only applications and signed applets can invoke the JNI;
  3. an application that relies on JNI loses the platform portability Java offers (a workaround is to write a separate implementation of the JNI code for each platform and have Java detect the Operating System and load the correct one at runtime);
  4. there is no garbage collection for the JNI side (JNI code must do explicit deallocation);
  5. error checking is a MUST or it has the potential to crash the JNI side and the JVM.

Contents

JNI example walkthrough

Step 1: Implement the Java application

Create a file named JavaSide.java containing the following class and compile it.

public class JavaSide {
    public native void sayHello();

    static {
        System.loadLibrary("NativeSideImpl");
    }

    public static void main(String[] args) {
        JavaSide app = new JavaSide();
        app.sayHello();
    }
}

The Java side looks pretty much like everyday Java, except for two things: the native keyword and the Template:Javadoc:SE method in the static initializer. The native keyword specifies that the sayHello() method is implementated elsewhere in a native library. The method is invoked the same as any other Java method, so code that calls sayHello() does not need to be aware that it is a native method.

Each platform has a default location where native libraries are located. The System.loadLibrary() method loads the library name "NativeSideImpl" from the default location. On most platforms a platform-specific file extension is added to the library name to get the name of the corresponding file.

Step 2: Make the header file (.h)

The implementation in C++ needs some glue code to interact with the JVM, so the next step is to create the C/C++ header file using the javah tool to generate the header file. The javah tool is part of the JDK and in the same directory as the javac tool. After compiling the JavaSide.java file, type in this at the command line:

javah JavaSide

where JavaSide is the class name. This creates a header file called JavaSide.h:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JavaSide */

#ifndef _Included_JavaSide
#define _Included_JavaSide
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JavaSide
 * Method:    sayHello
 * Signature: (I)V
 */
JNIEXPORT void JNICALL Java_JavaSide_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

Step 3: Implement C++ native method

Next, type this in a new file called NativeSideImpl.cpp:

#include <stdio.h>
#include "JavaSide.h"

JNIEXPORT void JNICALL Java_JavaSide_sayHello
  (JNIEnv *env, jobject obj)
{
    printf("Hello Native World!");
}

and compile into a library with a C++ compiler of choice.

In the above example, env parameter is a pointer to the JNI context, which is the link between the JNI code and the JVM. The obj parameter is the this reference implicitly passed to non-static methods in Java.

Note you need to include the "include" folder and all its subfolders in the JDK as paths to search. These folders have all the JNI Header files in order to compile.

The following example uses Microsoft's Visual Studio .NET 2003 compiler:

cl /LD /I"C:\Program Files\Java\Include" /I"C:\Program Files\Java\Include\win32" NativeSideImpl.cpp

Step 4: Run the example

Run this:

java JavaSide

And you should get this output:

Hello Native World!

How the JNI works

In JNI, native functions are implemented in a separate .c or .cpp file. (C++ provides a slightly cleaner interface with JNI.) When the JVM invokes the function, it passes a JNIEnv pointer, a jobject pointer, and any Java arguments declared by the Java method. A JNI function may looks like this:

JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj)
{
    //Implement Native Method Here
}

The env pointer is a structure that contains the interface to the JVM. It includes all of the functions necessary to interact with the JVM and to work with Java objects. Example JNI functions are converting native arrays to/from Java arrays, converting native strings to/from Java strings, instantiating objects, throwing exceptions, etc. Basically, anything that Java code can do can be done using JNIEnv, albeit with considerably less ease.

For example, the following converts a Java string to a native string:

//C++ code
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    //Get the native string from javaString
    const char *nativeString = env->GetStringUTFChars(javaString, 0);

    //Do something with the nativeString

    //DON'T FORGET THIS LINE!!!
    env->ReleaseStringUTFChars(javaString, str);
}
//C code
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    //Get the native string from javaString
    const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);

    //Do something with the nativeString

    //DON'T FORGET THIS LINE!!!
    (*env)->ReleaseStringUTFChars(env, javaString, str);
}

Note that C++ JNI code is cleaner than C JNI code because like Java, C++ uses object method invocation semantics. That means that in C the env parameter is dereferenced using (*env)-> and env has to be explicitly passed to JNIEnv methods. In C++, the env parameter is dereferenced using env-> and the env parameter is implicity passed as part of the object method invocation semantics.

Native data types can be mapped to/from Java data types. For compound types such as objects, arrays and strings the native code must exlicitly convert the data by calling methods in the JNIEnv.

Mapping types

The following table shows the mapping of types between Java and native code.

Java Language Type Native Type Description
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits

Here, these types are interchangable. You can use jint where you normally use an int, and vice-versa, without any typecasting required.

However, mapping between Java Strings and arrays to native strings and arrays is different. If you use a jstring in where a char * would be, your code could crash the JVM.

/***********************************************
*NO!!! NO!!! NO!!! NO!!! NO!!!                 *
***********************************************/
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    printf("%s", javaString);
}
/***********************************************
*YES!!! YES!!! YES!!! YES!!! YES!!!            *
***********************************************/
JNIEXPORT void JNICALL Java_ClassName_MethodName
  (JNIEnv *env, jobject obj, jstring javaString)
{
    //Get the native string from javaString
    const char *nativeString = env->GetStringUTFChars(env,javaString, 0);
    printf("%s", nativeString);
    //DON'T FORGET THIS LINE!!!
    env->ReleaseStringUTFChars(env,javaString, str);
}

This is similar with Java arrays, as illustrated in the example below that takes the sum of all the elements in an array.

/***********************************************
*NO!!! NO!!! NO!!! NO!!! NO!!!                 *
***********************************************/
JNIEXPORT jint JNICALL 
    Java_ClassName_MethodName(JNIEnv *env, jobject obj, jintArray arr)
{
     int i, sum = 0;
     for (i = 0; i < 10; i++) {
         sum += arr[i];
     }
     return sum;
}
/***********************************************
*YES!!! YES!!! YES!!! YES!!! YES!!!            *
***********************************************/
JNIEXPORT jint JNICALL 
    Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintArray arr)
{
     jint buf[10];
     jint i, sum = 0;
     env->GetIntArrayRegion(arr, 0, 10, buf);
     for (i = 0; i < 10; i++) {
         sum += buf[i];
     }
     return sum;
}

Of course, there is much more to it than this. Look for links below for more information.

Native AWT painting

Not only can native code interface with Java, it can also draw on a Java Template:Javadoc:SE, which is possible with the Java AWT Native Interface. The process is almost the same, with just a few changes. The Java AWT Native Interface is only available since J2SE 1.3.

Microsoft's RNI

Microsoft's implementation of a Java Virtual Machine has a similar mechanism for calling native Windows code from Java, called the Raw Native Interface (RNI).

Conclusion

As you can see, it is not easy to implement JNI in your application. However, its performance, preserving legacy code, and adding more functionality to your Java App may outweigh its challenges. There is much more to JNI than there is in this introductory article, just look for links at the bottom.

See also

Java AWT Native Interface

External links

nl:Java Native Interface pl:Java Native Interface pt:JNI