Java Native Interface (JNI) is a way for Java to interface with native code, in our case C. For this to work a
Java method that is implemented in C/native code needs to be labeled with the keyword native
. Next
the Java code needs to be compiled into Java byte code and a C header file produced that defines the interface
of
the C functions that will be called from Java. In the past you have used Eclipse to compile and run Java
programs, but this can also be done with the command line. The commands
javac [options]
compiles a .java
file and
java [options]
runs a Java program. Note, do not include the .class
when
running your program. Create a directory called R5.1 and copy the following code into a file called JNIDemo.java
public class JNIDemo{
static {
System.loadLibrary("sum");
}
private static native int sum(int[] x);
public static void main(String[] args){
int x[] = {1, 2, 3, 4};
System.out.println("Java: sum() returned: " + sum(x));
}
}
Lets take a look at whats happening in this code. First the System.loadLibrary("sum");
tells java to
load a library called sum. The actual name of the library in linux will be libsum.so
. The
.so
means the library is a shared library, you can read more about shared libraries here: here. We will actually create
this library in C. To compile this java file and create the .h
C file we will need, we can use the
command:
javac -h . JNIDemo.java
The -h .
tells the Java compiler to generate a .h
file and place it in the current
directory. Now you
should have a file called JNIDemo.h
. Use your favorite text editor to open the file an get a feel
for how the
Java functions that you declared as native
have been translated into C function declarations.
Now it is time to create the .c
file that will implement the functions declared in the
.h
file. Copy the
following code into a file called sum.c:
#include <jni.h>
#include <stdio.h>
#include "JNIDemo.h"
JNIEXPORT jint JNICALL Java_JNIDemo_sum(JNIEnv * env, jclass j_class, jintArray j_arr){
jint *arr = (*env)->GetIntArrayElements(env, j_arr, 0);
jint size = (*env)->GetArrayLength(env, j_arr);
printf("C: in sum method\n");
jint sum = 0;
for (int i = 0; i < size; i++){
sum += arr[i];
}
return sum;
}
There is a lot going on here so lets see if we can break down what is happening. First we are including the file
jni.h this header file is included with the JDK and contains information about functions and types that are
needed to access Java based data in C. Also we will need to tell the C compiler were to find this
.h
file since
it is not part of the standard C library and not located in the current directory. JNIEXPORT
and
JNICALL
are macros that ensure the function is visible to code using the shared library that the
.c
file
will be compiled into. The function returns a jint which is a typedef that ensures the integer type in the C
code is the same size as a Java int. Take a few minutes to look through this tutorial and
the Oracle docs here
to try and understand the rest of the code in sum.c
.
Now we can compile and run the code. Use the following command to generate the .so
file from
sum.c
. The
-I/usr/... -I/usr...
tells GCC were to find jni.h
and associated library files that
contain the JNI functions
used in sum.c
gcc -fPIC -I/usr/lib/jvm/java/include -I/usr/lib/jvm/java/include/linux -shared -o libsum.so sum.c
Now to run the code we just need to tell Java where to find the library with the native code. In our case the
library file we just generated is in the current directory so we can just use the .
java -Djava.library.path=. JNIDemo
So far we have called a function in C from Java. Now lets call a Java function from C. First we need to give our
C code a way to reference our Java class and environment. This can be done in a similar way to the Java code
that called the C sum
function above. In this case we can make a Java native method called
link
.
Then in our C code the generated link
will be passed a pointer to a struct that represents the java
environment. Now we can use a JNI library function to access other methods in the Java code, in this case the
print
function. Once we have the Java method ID we can use it to call the corresponding Java method
from C. Now
JNIDemo.java
should look like this:
public class JNIDemo{
static {
System.loadLibrary("sum");
}
private static native int sum(int[] x);
private static native void link();
private static void print(String s){
System.out.println("Java: received message: " + s);
}
public static void main(String[] args){
int x[] = {1, 2, 3, 4};
System.out.println("Java: sum() returned: " + sum(x));
link();
}
}
And sum.c
should look like this:
#include <jni.h>
#include <stdio.h>
#include "JNIDemo.h"
JNIEXPORT jint JNICALL Java_JNIDemo_sum(JNIEnv * env, jclass j_class, jintArray j_arr){
jint *arr = (*env)->GetIntArrayElements(env, j_arr, 0);
jint size = (*env)->GetArrayLength(env, j_arr);
printf("C: in sum method\n");
jint sum = 0;
for (int i = 0; i < size; i++){
sum += arr[i];
}
return sum;
}
JNIEXPORT void JNICALL Java_JNIDemo_link(JNIEnv * env, jobject class){
printf("C: accessing callback method\n");
jmethodID j_print = (*env)->GetStaticMethodID(env, class, "print", "(Ljava/lang/String;)V");
jstring j_string = (*env)->NewStringUTF(env, "Hello from C");
(*env)->CallStaticVoidMethod(env, class, j_print, j_string);
}
To get the code to run you will need to repeat the earlier steps that compile the Java code and generate the
.h
file, compile sum.c
into libsum.so
, and finally run the Java code with
the location of libsum.so
specified.
If everything is working correctly your output should look like this:
C: in sum method
Java: sum() returned: 10
C: accessing callback method
Java: received message: Hello from C
Now its your turn, use the links above and/or google to figure out how to add a Java native method that takes a
String argument
and returns the integer value of the String. Next try and change the value of a variable, either static or
instance, from C using a similar technique to the link method that our C code used to call the Java
print
method.