Jump to content

Java Native Interface

From Wikipedia, the free encyclopedia

This is an old revision of this page, as edited by 71.108.6.196 (talk) at 19:05, 26 December 2005 (How The JNI Works). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

The Java Native Interface Introduction

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.

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 ta7sks. A native method can also inspect and use objects created by Java application code.

JNI is sometimes refered 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 "legacy" code, that is, code written in "older" languages, like C++. Or, it is used for time-critical calculations or operations like solving complicated mathematical equations, as C++ is much faster than Java.

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 ability for Java to communicate with C++ will give you the power you may need. (And it doesn't stop adventurous programmers from experimenting around it!)

Before going on, you should consider these things before going on:


1. Mentioned before, JNI is not an easy API to learn

2. Only applications and signed applets can invoke the JNI

3. Once you put JNI in your application, you lose the platform portability Java had to offer. (A workaround is to write a separate JNI side 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.

5. Error-Checking IS A MUST or it has the potential to crash the JNI side and the JVM.

And by no means is this list complete. However, if you are ready and want to access platform-dependent features, read on

JNI Example Walkthrough

1. Implement the Java Side

Type this in a .java file named JavaSide.java, then compile:

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 2 things: the "native" keyword and the "System.loadLibrary()" method in the static construct. The "native" keyword is the only way Java knows it's a native method and that its implementation exists elsewhere (And also tells Java that the method exists). You would invoke this method as usual. The System.loadLibrary() method simply loads the library that has the implemenation of the method (Which we'll make later).

2. Make the Header File (.h)

Now we need to get Java to link with the implementation in C++, we create the Header File, which will be computer-generated with the tool named "javah", 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

3. Implement in C++

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!");
}

Then, compile into a library with a C++ compiler of choice

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.

Here's a Microsoft Example: (Visual Studio .NET 2003 compiler)

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

4. Run the example

Run this:

java JavaSide

And you should get this output:

Hello World!

How The JNI Works

In JNI, you need to implement native functions in a separate .c or .cpp file. (C++ provides a slightly cleaner way to go about JNI)

When the JVM invokes the function, it passes a JNIEnv pointer, a jobject pointer, and any Java arguments if there is any. Its 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 useful JNI functions like converting native arrays to Java arrays, strings, vice-versa, etc.

For example, to convert a Java string to a Native string, you do this:

//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(jFileName, 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++ code is cleaner as you don't have this messy "(*env)->" or the extra parameter in the JNI functions. Remember that when writing Native functions in C++, you can write "env->" instead of "(*env)->". Also please omit the "env" argument!

If you don't know already, this is the code to get the Native String from a Java String!

Also note that Native Types can be easily mapped to Java Types and Vice Versa, except for Arrays and Strings (You need the functions provided by the JNIEnv pointer).

Mapping Types

Types in Java are easily mapped to Native Types, as illustrated in the following table:

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 are 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(javaString, 0);
    printf("%s", nativeString);
    //DON'T FORGET THIS LINE!!!
    env->ReleaseStringUTFChars(javaString, str);
}

This is similar to Java Arrays, as shown below (That simply takes the sum of all the elements in the 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 Painting

Not only you can interface Native code with Java, you can have your Native code draw on a Java Canvas, 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 JDK 1.3)

Conclusion

As you can see, it is not easy to implement JNI in your application. But, its performance, perserving 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

Java Native Interface: Programmer's Guide and Specification

The JNI 5.0 Specification

The JNI Tutorial