Java Native Interface


In software design, the Java Native Interface is a foreign function interface programming framework that enables Java code running in a Java virtual machine to call and be called by native applications and libraries written in other languages such as C, C++ and assembly.

Objectives

JNI enables programmers to write native methods to handle situations when an application cannot be written entirely in the Java programming language, e.g. when the standard Java class library does not support the platform-specific features or program library. It is also used to modify an existing application to be accessible to Java applications. Many of the standard library classes depend on JNI to provide functionality to the developer and the user, e.g. file I/O 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.
The JNI framework lets a native method use 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.
Only applications and signed applets can invoke JNI.
An application that relies on JNI loses the platform portability Java offers.
Not only can native code interface with Java, it can also draw on a Java, 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.
JNI also allows direct access to assembly code, without even going through a C bridge. Accessing Java applications from assembly is also possible in the same way.

Design

In the JNI framework, native functions are implemented in separate.c or.cpp files. When the JVM invokes the function, it passes a JNIEnv pointer, a jobject pointer, and any Java arguments declared by the Java method. For example, the following converts a Java string to a native string:

extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName


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.
The argument obj is a reference to the Java object inside which this native method has been declared.
Native data types can be mapped to/from Java data types. For compound types such as objects, arrays and strings the native code must explicitly convert the data by calling methods in the JNIEnv.
A JNI environment pointer is passed as an argument for each native function mapped to a Java method, allowing for interaction with the JNI environment within the native method. This JNI interface pointer can be stored, but remains valid only in the current thread. Other threads must first call AttachCurrentThread to attach themselves to the VM and obtain a JNI interface pointer. Once attached, a native thread works like a regular Java thread running within a native method. The native thread remains attached to the VM until it calls DetachCurrentThread to detach itself.
The JNI framework does not provide any automatic garbage collection for non-JVM memory resources allocated by code executing on the native side. Consequently, native side code assumes the responsibility for explicitly releasing any such memory resources that the native code acquires.
On Linux and Solaris platforms, if the native code registers itself as a signal handler, it could intercept signals intended for the JVM. A chain of responsibility can be used to allow native code to better inter-operate with the JVM. On Windows platforms, Structured Exception Handling may be employed to wrap native code in SEH try/catch blocks so as to capture machine generated software interrupts, and to handle these situations before the interrupt is propagated back up into the JVM, in all likelihood resulting in an unhandled exception.
The encoding used for the NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars and GetStringUTFRegion functions is "modified UTF-8", which is not valid UTF-8 for all inputs, but a different encoding really. The null character and codepoints not on the Basic Multilingual Plane are encoded differently in modified UTF-8. Many programs actually use these functions incorrectly and treat the UTF-8 strings returned or passed into the functions as standard UTF-8 strings instead of modified UTF-8 strings. Programs should use the NewString, GetStringLength, GetStringChars, ReleaseStringChars, GetStringRegion, GetStringCritical and ReleaseStringCritical functions, which use UTF-16LE encoding on little-endian architectures and UTF-16BE on big-endian architectures, and then use a UTF-16 to UTF-8 conversion routine.

Mapping types

The following table shows the mapping of types between Java and native code.
C TypeJava Language TypeDescriptionType signature
unsigned charjbooleanunsigned 8 bitsZ
signed charjbytesigned 8 bitsB
unsigned shortjcharunsigned 16 bitsC
shortjshortsigned 16 bitsS
longjintsigned 32 bitsI

long long
__int64
jlongsigned 64 bitsJ
floatjfloat32 bitsF
doublejdouble64 bitsD
voidV

In addition, the signature "L qualified-class ;" would mean the class uniquely specified by that name; e.g., the signature "Ljava/lang/String;" refers to the class java.lang.String. Also, prefixing to the signature makes the array of that type; for example, [I means the int array type. Finally, a void signature uses the V code.
These types are interchangeable. One 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 a jstring is used where a char * would be, the code could crash the JVM.

Performance

JNI incurs considerable overhead and performance loss under certain circumstances:
Microsoft's proprietary implementation of a Java Virtual Machine had a similar mechanism for calling native code from Java, called the Raw Native Interface. In addition, it had an easy way to call existing native code that wasn't itself aware of Java, such as the Windows API, called J/Direct. However, following the Sun–Microsoft litigation about this implementation, Visual J++ is no longer maintained.
RNI was less clumsy to use than JNI, because no bookkeeping with a Java environment pointer was needed. Instead, all Java objects could be accessed directly. To facilitate this, a tool was used that generated header files from Java classes. Similarly, J/Direct was easier to use than using the necessary intermediate native library and JNI, although at present JNA is an alternative.