While writing Android application or other generic java application you sometimes need to write code in C/C++. Java is a dynamic language with a lot of security checks for example on every array access, every type cast , on function call and return and more. those checks affect performance and if you want to manipulate an image you better write it in C.
JNI – Java Native Interface
JNI is an extension to java for interfacing native C/C++ code. You should use it for the following situations:
- Running algorithms – with better performance (no security checks and no dynamic features)
- Working with pointers – for example when you need to access hardware
- Calling functions not available in Java – for example some system calls like ioctl and more
Same Address Space – Different heaps
Java is working with automatic memory management – Garbage collector. C/C++ use manual memory management (malloc-free / new-delete)
The garbage collector manages the memory, moves objects to minimize fragmentation and deallocate unneeded memory regions. This is why the system creates 2 different heaps in the process address space – one for java and one for native.Sending array from C code to C code is always done by address, Sending Array from Java code to Java code is always done by reference. On a trivial JNI implementation sending array from java to C is done by value means if we have a java array (in the java heap) and we want to send it to a native function, we first need to copy it to the native heap and after the function returns copy it again. On a smarter implementation, the garbage collector pin the array until the function returns and send only address.
Another problem we have is in strings manipulation , C string is a simple array of characters ending with null terminated char (‘\0’). In Java it is an object with properties and ,methods so while sending a string from java to C you need to add the null terminated char and sometimes you need to copy the string to the native heap.
In my examples I’m using Android Studio to write android application with native code. In Android you need to install NDK to use JNI.
Simple Example:
First we need to add native support to the Gradle scripts:
we are using cmake for our build system, all other declarations are in the CMakeLists.txt file
To add native support we first need to load the library (shared object or DLL):
This code loads the file libnative-lib.so (Or native-lib.dll on windows application).
Now We need to declare the native function in the java class – You need to call this function to invoke the C function:
The symbol name in C must be Java_[package]_[class]_function so if we declare the native function in MainActivity class in package com.devarea.jnitest you have to write the C code exactly this way:
The extern “C” is required if the file extension is cpp so the symbol name will be the name of the function only.
The function gets 4 parameters:
- JNIEnv – pointer to JNI interface – a class with many function pointers
- jobject – the java object
- jint a ,jint b – typedefs to java integers
Note that if we declare the function as static we will get jclass as second parameter represents the Java class object
We can use jobject and jclass to access java properties and methods from C code – see below
Primitive types
Java primitives (byte, short, int, long, float, double , char, boolean) are passed by value so its easy to work with them – just use the correct typedef in your C code (starting with j)
Strings
String are different in Java and C so you need special handling:
Sending Java string to C:
Java code:
C code:
First we copy the java string to the native heap, then calculate length and release the native string
Note that it is sometimes possible to pin the string in the java heap and send only pointer (ReleaseStringUTFChars is required for unpinning)
If you need to know if the VM really copied the string you can send jboolean variable by address to the second argument:
Returning a string from C to Java:
Java code:
C code:
Arrays
If we want to send a java array to C code:
Java code
C code:
We first copy the Java Array to C using GetIntArrayElements , then ask the array size , manipulate the c array and release it
It is similar to string handling
Return Java Array from C:
Java code:
C code:
Global and Local references:
It is very important to understand the difference between local and global reference. When you pass and object to the native code , the VM creates a local reference , the object will not move or freed until the function returns
What if we want to save the reference in a global variable in the native code and use it during another call for example:
Java code:
On saveArray we just save the reference and on addArray we use it:
C code:
This is a Wrong Code!!!
We need to tell the VM that we are saving a reference to the object so it won’t move it or free it between the calls. For that we need to declare a global reference:
The correct C code:
Direct Buffer Access
Sometimes we need to work with a large amount of data and be sure that the VM won’t copy it. We can use ByteBuffer for this:
Java code:
C code:
It can be very useful if you are using the same buffer in more than one function. You can also create the buffer inside the native code (using malloc) and return it to the java code using env->NewDirectByteBuffer
Reflection
The only supported type in JNI are strings and arrays, Other objects passed as jobject and if you want to access their data , properties and methods you need to use reflection:
Example – java code:
“func” is a member function, you need to create a Nlib object and use it to call func:
obj (this) is passed as jobject
C code:
In this example we retrieve the Class object for obj , then we use reflection to get the method object of a function getNum which get an integer as a parameter and returns integer. The last thing we do is call that function using object obj and send a+b as parameter
Using this method we can call to any function but it is complex to write (note that we can also call to a private function like this example)
JNI_OnLoad
You can write a function to initialize the native library , one use of this function is to load a function pointers table to eliminate the long functions name for example:
Java code:
C++ code:
As you can see from the code , in this way we don’t need to declare the functions with the long symbol names because we are using a functions pointers.
Working with Native threads
Sometimes you want to create a thread in the native code , for example to make the task asynchronous , you can just use the native system call or function to do:
Now simply write the function threadfn. But if you want to access the JNI interface its not trivial – the parameter env is created for each thread and if you want to use it on a custom thread you need to create one. This is done using AttachCurrentThread:
You can access javaVM by saving it as a global variable in JNI_OnLoad or retrieving it from env on the function that creates the thread:
Exception Handling
You can use exceptions in 2 cases:
Calling a native code that generate java exception:
java code:
C code:
The second case is calling a java code from native and check if it throws exception:
Hi there. You are very wrong about java and array-bounds-checks. They are optimized out of the inner loop. There are not checks performed on every typecast. Please read up on the subject… 이 게시물을...