*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
#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 strings –
specify 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 fields
– both have
the same return type jfieldID.
Pass the class
reference, as opposed to an object reference, to the appropriate
static field access function.
jclass cls = (*env)->GetObjectClass(env, obj);
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");
}
jmethodID id = (*env)->GetMethodID(env, cls, "doSomething", "()V");
(*env)->CallVoidMethod(env, obj, id);
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
static
jfieldID id =
NULL; // cached field ID for s
if
(id == NULL) {
id
= (*env)->GetFieldID(env,
cls, "s", "Ljava/lang/String;");
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 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
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);
(*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
-
DeleteGlobalRef
– causes 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:
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:
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.
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
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.