Geeks With Blogs
Josh Reuben

*note: for brevity, error checking code has been left out of this post.

Overview

The JNI API allows Java applications to invoke native C/C++ code and native C/C++ code to invoke Java.

Using the JNI comes at the cost of losing JIT portability & type safety – recompilation of native code is machine instruction-set specific.

Usage

Declare a Native Method in Java:

class MyClass {
       private native void doSomething();	// indicate method is implemented in another language
       public static void main(String[] args) {
           new MyClass().doSomething(); 	//invoke native method
       }
       static {
           System.loadLibrary("MyClass"); 	// load the native library in the static ctor 
	  } 

}

Use javac to compile the Java class from the MyClass.java source file:

javac MyClass.java

use javah to generate a JNI header file - generate a MyClass.h file of same name containing the function prototype for the native method implementation:

javah -jni MyClass.java
  • The JNI header file will appear in the following format:

#include <jni.h> // defines macros
JNIEXPORT void JNICALL Java_MyClass_doSomething (JNIEnv*, jobject);
  • JNIEXPORT macro - export function from native library

  • JNICALL macro - cause C compiler to generate code correct calling convention for this function.

  • The generated name of the C function is formed by concatenating the “Java_” prefix, the class name, and the method name.

  • 1st argument - JNIEnv - interface pointer to a pointer to a function table.

  • 2nd argument - jobject or jclass - a reference to the java object instance on which the native method is defined. If defined as static native, is a reference to the class.

Write the Native Method Implementation. Compile the C/C++ implementation into a .so native library

Run the Program - specify the native library path to the local directory on the java command line – D switch sets a Java platform system property. (alternatively set the LD_LIBRARY_PATH environment variable).

java -D java.library.path=. MyClass

Type Mappings and Member Descriptor Strings

The JNI defines a set of C / C++ types that correspond to types in Java – for primitive types (eg jfloat, jstring) / reference types (classes, instances, arrays – all of which have type jobject).

native code can manipulate java objects via JNI functions exposed through the JNIEnv interface pointer.

Eg a canonical native method with String params and retval

.java file:

private native String doSomething(String s);

.h file:

JNIEXPORT jstring JNICALL Java_MyClass_doSomething(JNIEnv *env, jobject this, jstring s);

JNI member descriptor C stringsspecify a type. Primitive types have specific descriptors eg I for int , Z for Boolean, F for float, D for double. For a reference type the descriptor begins with the letter L, is followed by the JNI class descriptor, and ends with a semicolon. The dot separators in FQ class names are changed to slashes in JNI class descriptors. Eg

java.lang.String "Ljava/lang/String;"

Descriptors for array types start with [, followed by the descriptor of the array element type eg

float[] "[f"

javap -s -p - generate & print field descriptors from class files:

javap -s -p myClassMember

-->

   s Ljava/lang/String;

method descriptors - argument types appear first, are listed in order with no separators, and are surrounded by brackets. A method that takes no arguments is represented with an empty bracket pair. Eg, "(F)V" - denotes a method that takes one argument of type float and has return type void. "()F" denotes a method that takes no arguments and returns a float.

Method descriptors may contain class descriptors.

native String doSomething(String);		"(Ljava/lang/String;)Ljava/lang/String;"

Descriptors for array types begin with the [” character, followed by the descriptor of the array element type. Eg

native void doSomething(String[] args);		"([Ljava/lang/String;)V"

Passing String Params

JNI String Functions:

  • Get/ReleaseString[UTF]Chars		
  • GetString[UTF]Length -	 number of chars in the string. 
  • NewString[UTF]	- Creates a java.lang.String instance on the JVM
  • Get/ReleaseStringCritical critical section with JVM GC disabled - allowsfast direct pointer to chars - use only if native code does not allocate new JVM objects, call JNI functions or perform native blocking calls between get and release
  • Get/SetString[UTF]Region - Fast - for small fixed-size strings - Copies the contents of string to / from preallocated C buffer.  -  JNI bypasses out of memory exception checking.
jstring will not automatically cast to a char* pointer -  to convert jstring objects to C/C++ strings - call the JNI function GetStringChars to read the contents of the string. 
JNIEXPORT jstring JNICALL Java_MyClass_doSomething(JNIEnv *env, jobject obj, jstring s) {
	// access string param from java     
       const jbyte *inbuff  = (*env)->GetStringChars(env, s, NULL); 
       if (inbuff == NULL) {  return NULL; /* explicit return if JNI throws out of memory exception */  }
       printf("%s", inbuff);
       (*env)->ReleaseStringChars(env, s, inbuff); // Explicitly free native resources – avoid memory leaks
	
	// return string to java
       char outbuff[256];
       scanf("%s", outbuff);
       return (*env)->NewString(env, outbuff); // construct new java.lang.String instance in native method 

}


Passing Array Params

JNIprimative arrays are represented by j<T>array types eg jintArray. use JNI functions to access primitive array elements

JNIEXPORT jint JNICALL Java_MyClass_doSomething(JNIEnv *env, jobject obj, jintArray arr){
	const jsize length = env->GetArrayLength(arr);
      jint inbuffer[length];
      (*env)->GetIntArrayRegion(env, arr, 0, length, inbuffer); // copy to buffer array for native access
	//or:
	jint *inptr  = (*env)->GetIntArrayElements(env, arr, NULL); // copy to ptr array for native access
	...
	(*env)->ReleaseIntArrayElements(env, arr, inptr, 0); 

JNI Primitive Array Functions

  • Get/Set<T>ArrayRegion - Copy contents of primitive arrays to / from preallocated C buffer with bounds checking. Stack based – fast, can specify range, preffered.
  • Get/Release<T>ArrayElements - Get a pointer to contents of a primative array (or possibly a copy of it)
  • GetArrayLength - Returns  number of elements in array. 
  • New<T>Array -	Creates array with specified length. 
  • Get/ReleasePrimitiveArrayCritical	- Get / release a pointer to the contents (or copy of) of a primitive array – fast – disables GC for duration of the op

JNI Object Arrays - separate pair of functions – Get/SetObjectArrayElement - access arrays of strings or multi-d arrays. note: a multidimensional jagged array of primitives is considered an object array.

Eg a static native method to create a 2D int array

java:

static native int[][] doSomething(int size1, int size2);
...
int[][] aa = initInt2DArray(4,4);
c:
JNIEXPORT jobjectArray JNICALL Java_MyClass_doSomething(
JNIEnv *env,  jclass cls, int size1, int size2) {
      jclass intArrCls = (*env)->FindClass(env, "[I"); //  "[I" argument - JNI class descriptor for Java int[] type 
	//  allocate the 1st dimension of an array of element type specified by the jclass reference
      jobjectArray aa = (*env)->NewObjectArray(env, size1, intArrCls, NULL);
      for (int i = 0; i < size1; i++) {
           jint buff[size2];  // make sure it is large enough
           jintArray a = (*env)->NewIntArray(env, size2);  // allocate individual array elements
           for (int j = 0; j < size2; j++) {
               buff[j] = i * j;
           }
	            // copies buffer contents into the newly allocated 1D inner arrays. 
           (*env)->SetIntArrayRegion(env, a, 0, size1, buff);
           (*env)->SetObjectArrayElement(env, aa, i, a); // put the cell values
           (*env)->DeleteLocalRef(env, a);

}

      return aa;
}


Accessing Java Fields

call Get/SetStaticFieldID for static fields, as opposed to Get/SetFieldID for instance fieldsboth have the same return type jfieldID. Pass the class reference, as opposed to an object reference, to the appropriate static field access function.

  • obtain the jclass class reference from the jobject instance 
jclass cls = (*env)->GetObjectClass(env, obj);
  • obtain the field ID from the class reference, field name, and field descriptor:
       jfieldID id = (*env)->GetFieldID(env, cls, "myField", "Ljava/lang/String;");


       // Read the instance field 
       jstring jstr = (*env)->GetObjectField(env, obj, id);
       const char *cstr = (*env)->GetStringChars(env, jstr, NULL);
       (*env)->ReleaseStringChars(env, jstr, cstr);

       // Create a new string write it to the instance field 
       jstr = (*env)->NewStringUTF(env, "blah");
       (*env)->SetObjectField(env, obj, id, jstr);

	  // access static fields
       id = (*env)->GetStaticFieldID(env, cls, "myStaticField", "I");
       jint i = (*env)->GetStaticIntField(env, cls, id);
       (*env)->SetStaticIntField(env, cls, id, i+1);

Calling Java Methods

Java:

private void doSomething() {
	System.out.println("blah");
}
  • GetMethodID - lookup method name + type descriptor of the method. If the method does not exist, returns NULL.

jmethodID id = (*env)->GetMethodID(env, cls, "doSomething", "()V");
  • Call<Type>Method family of functions - pass the object, method ID, and arguments. Eg CallVoidMethod - invokes an instance method with return type void. can invoke interface methods as well.

(*env)->CallVoidMethod(env, obj, id);
  • Calling static methods - use GetStaticMethodID, CallStatic<Type>Method instead of GetMethodID, Call<Type>Method.

jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "doSomething", "()V");

(*env)->CallStaticVoidMethod(env, cls, mid); }

  • Call instance methods of superclass - CallNonvirtual<Type>Method functions eg CallNonvirtualVoidMethod, CallNonvirtualBooleanMethod

  • Call ctors - pass "<init>" as the method name and "V" as the return type in the method descriptor - then invoke the constructor by passing the method ID to NewObject.

jclass  stringClass = (*env)->FindClass(env, "java/lang/String");
jmethodID id = (*env)->GetMethodID(env, stringClass,  "<init>", "([C)V");
jcharArray  buffer = (*env)->NewCharArray(env, length);
(*env)->SetCharArrayRegion(env, buffer, 0, length, chars);
jstring stringObj = (*env)->NewObject(env, stringClass, id, buffer);  // Construct a java.lang.String object
// Free local references 
(*env)->DeleteLocalRef(env, buffer);
(*env)->DeleteLocalRef(env, stringClass);
  • the built-in string functions are more efficient than calling the java.lang.String API from native code.

  • create an uninitialized object using AllocObject then invoke the ctor using CallNonvirtualVoidMethod this approach is more error prone:

jstring stringObj = (*env)->AllocObject(env, stringClass);
if (stringObj) {
       (*env)->CallNonvirtualVoidMethod(env, stringObj, stringClass, cid, buffer);
       if ((*env)->ExceptionCheck(env)) { // check for possible exceptions 
           (*env)->DeleteLocalRef(env, stringObj);
           stringObj = NULL;

Caching Field and Method IDs - Obtaining field and method IDs - expensive symbol lookups based on member name + descriptor – best to cache at the point of use, or in the class static initializer

  • Caching lazy init: race condition --> harmless duplicate checks and initialization of same field / method ID in multiple threads

static jfieldID id = NULL; // cached field ID for s

if (id == NULL) {

id = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");

  • Caching once in class initializer:

In Java:

private static native void initIDs();
...

static {

	System.loadLibrary("...");

initCachedIDs(); // native implementation calls GetMethodID for any callback functions, & caches

}

Performance costs of JNI member Ops - different calling convention, JVM cannot inline JNI method calls.

Object References

instance and array types (such as jobject, jclass, jstring, and jarray) are treated as opaque reference pointers - native code cannot directly inspect their contents – must instead use JNI functions to access the data structure – abstracts details of JVM internal memory layout. 3 kinds of references: local references, global references, and weak global references.

  • Local - automatically freed vs global / weak - global must be explicitly freed

  • local / global - keeps referenced object from being GC'd vs weak global - allows referenced object to be GC'd.

Local References - Most JNI functions create local references eg NewObject. valid only within the scope of the native method that creates it, and only within that one invocation - must not write native methods that store a local reference in a static variable for use in subsequent invocations. After a native method returns, the JVM frees all local references created during its execution. Can optionally explicitly manage the lifetime of local references using JNI functions such as DeleteLocalRef. may be passed through multiple native functions before it is destroyed. only valid in the thread that creates them.

Global References - can use across multiple invocations of a native method and across multiple threads. created by NewGlobalRef. To create: pass the local reference returned from FindClass to NewGlobalRef, which creates a global reference

static jclass _gs = NULL;
if (_gs == NULL) {
	jclass ls = (*env)->FindClass(env, "java/lang/String");
	_gs = (*env)->NewGlobalRef(env, ls); // Create a global reference 

Weak Global References - Created / freed using NewGlobalWeakRef / DeleteGlobalWeakRef. Like global references, weak global references remain valid across native method calls and across different threads. Unlike global references, weak global references do not keep the underlying object from being GC'd. Check whether cached weak references still point to a live or GC'd class object.

Comparing References - check whether they refer to the same object using the IsSameObject function. returns JNI_TRUE (1) / JNI_FALSE (0) otherwise.

(*env)->IsSameObject(env, obj1, obj2)
(*env)->IsSameObject(env, obj, NULL) // determine if  obj is  null: (eg check if a weak global has been GC'd)

Freeing Local References

  • NewLocalRefuse for functions that return a local reference from a cached global reference (so that it can be accessed in multiple native method invocations / threads).

static jstring _gs = NULL;
if (_gs == NULL) {
	// create cached string for 1st time & cache it in a global reference:
	_gs = (*env)->NewGlobalRef(env, s);

}

return (*env)->NewLocalRef(env, _gs);

  • DeleteLocalRef: use for error check cleanup branches

(*env)->DeleteLocalRef(env, s);

  • JVM guarantees that a native method can create at least 16 local references – beyond this, call EnsureLocalCapacity jboolean function. Compile with -verbose:jni --> JVM reports excessive local reference creation beyond reserved capacity.

  • Push/PopLocalFrame - lifetime management: create nested scopes of local references:

for (i = 0; i < arrLen; i++) {
	// create: a new scope for specific number of local references
	if ((*env)->PushLocalFrame(env, NUM_REFERENCES) < 0) {
		... // if you get here, its because out of memory 
	}

	jstring s = (*env)->GetObjectArrayElement(env, arr, i);
 	... // do something with the string here
	
	// destroy the topmost scope, freeing all local references in that scope:
	(*env)->PopLocalFrame(env, NULL);

}

Freeing Global References

  • DeleteGlobalRefcauses a GC collect on a global reference. If you fail to call this function, the JVM will not GC the corresponding object.

  • DeleteWeakGlobalRef - If you fail to call this function the JVM will still be able to GC the underlying object, but will not be able to reclaim the memory consumed by the weak global reference itself.

Beware of excessive local reference creation in loops, or global / weak global reference leaks


Exception Handling

Catching / throwing Exceptions in native code - must detect and recover from JNI function call error conditions.

In Java, a native method or a callback method can specify that it throws a specific exception - this can be caught in a vanilla Java try … catch block.

       native void doSomething() throws IllegalArgumentException;
	...
       private void callback() throws NullPointerException {

the native method implementation:

       jclass cls = (*env)->GetObjectClass(env, obj);
       jmethodID id = (*env)->GetMethodID(env, cls, "callback", "()V");
       (*env)->CallVoidMethod(env, obj, id);
       jthrowable ex = (*env)->ExceptionOccurred(env); // detect exception
       if (ex) {
           (*env)→ExceptionDescribe(env); //  outputs a descriptive message
           (*env)->ExceptionClear(env);
           jclass  cex = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
           (*env)->ThrowNew(env, cex, "thrown from C");
       }

A pending exception raised through the JNI by calling ThrowNew does not immediately unwind the native method call stack or halt its execution - must explicitly implement the control flow after an exception has occurred – deallocate objects !!!.

Checking for Exceptions:

  • ExceptionOccurred returns a reference to an exception object.

  • ExceptionCheck returns a jboolean whereby JNI_TRUE / JNI_FALSE indicates wether a pending exception occured.

jint errorval = (*env)->CallIntMethod(env, func, param);
if ((*env)->ExceptionCheck(env))   /* check if an exception was raised */ {

Native code can handle a pending exception in 2 ways:

  • return immediately --> exception must be handled in Java caller.

  • clear exception using ExceptionClear and then execute native exception handling code.

Always free resources when exceptions occur.

JVM Threads

JNIEnv pointer and local references - only valid in thread associated with it – do not cache for multi-thread sharing or directly pass it to another thread. (convert local to global references if this is required).

Native calls JNI functions can access JVM synchronization primitives - via MonitorEnter / MonitorExit functions - take a jobject as an argument and blocks if another thread has already entered the critical section

   if ((*env)->MonitorEnter(env, obj) != JNI_OK) {
       ... // handle errors
   }
   /* CRITICAL SECTION */
   if ((*env)->MonitorExit(env, obj) != JNI_OK) {
       ... // handle errors
   }

Java intrinsic thread synchronization mechanisms Object.wait, / notify / notifyAll require the use of JNI method calls to Java via cached jMethodID objects.

obtain the JNIEnv pointer for the current thread by calling the AttachCurrentThread function of the invocation interface.

the JVM runs in a different thread model, and is not informed if native method executing thread is blocked --> deadlocks as no other threads will be scheduled. Be careful !

Registering Native Methods

In Java, System.loadLibrary locates and loads the named native library --> JVM searches for the native method implementation in one of the loaded native libraries. can manually link native methods by registering a function pointer with a class reference, method name, and method descriptor:

eg register a native function as the implementation of a native method:

   JNINativeMethod m;
   m.name = "doSomething";
   m.signature = "()V";
   m.fnPtr = doSomething_impl;
   (*env)->RegisterNatives(env, cls, &m, 1);

no need to declare the native function using JNIEXPORT – doesn't need to be exported. Still need to follow the JNICALL calling convention.

   void JNICALL doSomething_impl(JNIEnv *env, jobject self);

RAII

Load and unload handlers allow native lib to export two functions: one to be called when System.loadLibrary loads the native library, the other to be called when the virtual machine unloads the native library.

  • JNI_OnLoad – use to caching class references, or method and field IDs

   JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
  • JNI_OnUnload - JVM calls this when it unloads a JNI native lib - use to delete cached global references

JNI for C++

slightly simpler than for C:

   jclass c = env->FindClass("java/lang/String");

instead of in C:

   jclass c = (*env)->FindClass(env, "java/lang/String");

no inherent performance difference

jni.h defines a set of C++ class stubs to enforce type safety for jobject derived types jclass and jstring.


Posted on Monday, February 16, 2015 4:56 AM Java , Android | Back to top


Comments on this post: JNI – the Java Native Interface

# re: JNI – the Java Native Interface
Requesting Gravatar...
This gives a clear explanation of the process. - Mark Zokle
Left by George Thomas on Dec 20, 2016 6:05 PM

Your comment:
 (will show your gravatar)


Copyright © JoshReuben | Powered by: GeeksWithBlogs.net