JNI - krishnaramb/cplusplus GitHub Wiki
-
The very important documentation of java https://www3.ntu.edu.sg/home/ehchua/programming/java/JavaNativeInterface.html
-
The Java Native Interface (JNI) is a Java layer that allows Java code running in the Java Virtual Machine (JVM) to call and be called by native applications and libraries written in other languages, such as C, C++, and assembly.
-
Developers use the JNI to write native methods to handle situations when an application cannot be written entirely in Java, such as when the standard Java class library does not support the platform-dependent features or program library
-
JNI defines the following JNI types in the native system 👊 🍀 🌺 that correspond to Java types:
- Java Primitives: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean for Java Primitive of int, byte, short, long, float, double, char and boolean, respectively
-
Java Reference Types:
- jobject 👊 for java.lang.Object. It also defines the following sub-type
- jclass for java.lang.Class
- jstring for java.lang.String.
- jthrowable for java.lang.Throwable
- jarray for Java array. Java array is a reference type with eight primitive array and one Object array. Hence, there are eight array of primitives jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray and jbooleanArray; and one object array jobjectArray
-
The native functions receives argument in the above JNI types and returns a value in the JNI type (such as jstring, jintArray) 🌺
-
However, native functions operate on their own native types (such as C-string, C's int[]). Hence, there is a need to convert (or transform) between JNI types and the native types.
-
The native programs:
- Receive the arguments in JNI type (passed over by the Java program).
- For reference JNI type, convert or copy the arguments to local native types, e.g., jstring to a C-string, jintArray to C's int[], and so on. Primitive JNI types such as jint and jdouble do not need conversion and can be operated directly.
- Perform its operations, in local native type.
- Create the returned object in JNI type, and copy the result into the returned object.
- Return.
-
The most confusing and challenging task in JNI programming is the conversion (or transformation) between JNI reference types (such as jstring, jobject, jintArray, jobjectArray) and native types (C-string, int[]). The JNI Environment interface provides many functions to do the conversion.
-
JNI is a C interface, which is not object-oriented. It does not really pass the objects.
- You can construct
jobject
andjobjectArray
inside the native code, viaNewObject()
andnewObjectArray()
functions, and pass them back to the Java program.
- Callback the constructor is similar to calling back method. First, get the Method ID of the constructor by passing "" as the method name and "V" as the return-type. You can then use methods like
NewObject()
to call the constructor to create a new java object.
-
The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not be impossible in Android.
-
Note however that the
jclass
is a class reference and must be protected with a call to 'NewGlobalRef' -
Every argument passed to a native method, and almost every object returned by a JNI function is a "local reference". This means that it's valid for the duration of the current native method in the current thread. Even if the object itself continues to live on after the native method returns, the reference is not valid.
-
This applies to all sub-classes of jobject, including jclass, jstring, and jarray. (The runtime will warn you about most reference mis-uses when extended JNI checks are enabled.
-
The only way to get non-local references is via the functions
NewGlobalRef
and NewWeakGlobalRef. -
If you want to hold on to a reference for a longer period, you must use a "global" reference. The
NewGlobalRef
function takes the local reference as an argument and returns a global one. The global reference is guaranteed to be valid until you callDeleteGlobalRef
. -
This pattern is commonly used when caching a
jclass
returned fromFindClass
, e.g.:
jclass localClass = env->FindClass("MyClass");
jclass globalClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
- why do we need to delete the local ref. look here
-
The JavaVM provides the "invocation interface" functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but Android only allows one
-
The JNIEnv provides most of the JNI functions. Your native functions all receive a JNIEnv as the first argument
-
The JNIEnv is used for thread-local storage. For this reason, you cannot share a JNIEnv between threads. 👊 👊
-
If a piece of code has no other way to get its
JNIEnv
, you should share theJavaVM
, and useGetEnv
to discover the thread'sJNIEnv
🙏 . (Assuming it has one; seeAttachCurrentThread
below.)
-
All threads are Linux threads, scheduled by the kernel. They're usually started from managed code (using
Thread.start()
), but they can also be created elsewhere and then attached to theJavaVM
. For example, a thread started withpthread_create()
orstd::thread
can be attached using theAttachCurrentThread()
orAttachCurrentThreadAsDaemon()
functions. Until a thread is attached, it has noJNIEnv
, and cannot make JNI calls. 👊 👊 -
The JNI interface pointer (
JNIEnv
) is valid only in the current thread. -
Should another thread need to access the Java VM, it must first call
AttachCurrentThread()
to attach itself to the VM and obtain a JNI interface pointer. 👊 -
Once attached to the VM, a native thread works just like an ordinary Java thread running inside a native method.
-
The native thread remains attached to the VM until it calls
DetachCurrentThread()
to detach itself. -
The attached thread should have enough stack space to perform a reasonable amount of work. The allocation of stack space per thread is operating system-specific. For example, using pthreads, the stack size can be specified in the pthread_attr_t argument to pthread_create
-
Attaching a natively-created thread causes a
java.lang.Thread
object to be constructed and added to the "main" ThreadGroup, 👊 making it visible to the debugger. CallingAttachCurrentThread()
on an already-attached thread is a no-op. -
why do we need to call AttachCurrentThread() is explained HERE
- Passing Primitives
- Passing Java primitives is straight forward. A
jxxx
type is defined in the native system, i.e,.jint
,jbyte
,jshort
,jlong
,jfloat
,jdouble
,jchar
andjboolean
for each of the Java's primitivesint
,byte
,short
,long
,float
,double
,char
andboolean
, respectively.
-
In the JNI, native functions are implemented in a separate .c or .cpp file. (C++ provides a slightly cleaner interface with the 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 look like this:
JNIEXPORT void JNICALL Java_ClassName_MethodName (JNIEnv *env, jobjectobj) { //Method native implemenation }
-
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 and from Java arrays, converting native strings to and from Java strings, instantiating objects, throwing exceptions, etc
-
To be more formal, native code accesses JVM features by calling JNI functions, which are available through an interfacepointer (this is a pointer to a pointer) 💘. This pointer points to an array of pointers, each of which points to an interface function 👊
-
Every interface function is at a predefined offset inside the array. Native methods receive the JNI interface pointer as an argument 💓
-
The JVM is guaranteed to pass the same interface pointer to a native method when it makes multiple calls to the native method from the same Java thread
-
However, a native method can be called from different Java threads, and therefore may receive different JNI interface pointers.
-
Native methods are loaded with the
System.loadLibrary
method -
In the following example, the class initialization method loads a platform-specific native library in which the native method
f
is definedpackagepkg; class Cls { native double f(inti, String s); static { System.loadLibrary("pkg_Cls"); } }
-
The argument to
System.loadLibrary
is a library name chosen arbitrarily by the programmer. The system follows a standard, platform-specific approach to convert the library name to a native library name- For example, a Solaris system converts the name
pkg_Cls
tolibpkg_Cls.so
, while aWin32
system converts the samepkg_Cls
name topkg_Cls.dll
- For example, a Solaris system converts the name
-
Dynamic linkers resolve entries based on their names. A native method name is concatenated from components, which include: the prefix
Java_
, a mangled fully-qualified class name, and a mangled method name.
-
Primitive types, such as integers, characters, and so on, are copied between Java and native code. Arbitrary Java objects, on the other hand, are passed by reference.
-
This table below shows the mapping of types between Java and native code. These types are interchangeable.
-
You can use
jint
where you normally use anint
, 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
where achar *
would be, your code could crash the JVM. -
This is an example of how you should work correctly with strings:
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobjectobj, jstringjavaString)
{
//Get the native string from Java string
const char *nativeString = env->GetStringUTFChars(env,javaString, 0);
printf("%s", nativeString);
env->ReleaseStringUTFChars(env,javaString, nativeString);
}
-
You always manipulate Java objects using the interface pointer
env
👊