Java Native Interface (JNI) is a foreign function framework designed to call native applications. In other words, via JNI one can invoke a function written in C or C++ and vice versa. In this article, we discuss how to use JNI and interface between a C program and a Java program. So the Java program can call functions written in C.
If you ever read JDK source code, you often find methods like below:
public static native long currentTimeMillis();
As you can see methods currentTimeMillis
Hence, if we currentTimeMillis
In the following section, we explain in step by step details on how to use JNI to create native functions implemented in C. Then invoke them in a Java program
For the purpose of this tutorial, we create two methods, printHello
multiple
Java implementation
First we start with writing our small Java program as follows:
public class JniExample {
static {
System.loadLibrary("hello");
}
public native void printHello();
public native int multiple(int x, int y);
public static void main(String[] args) {
JniExample jniExample = new JniExample();
jniExample.printHello();
int result = jniExample.multiple(2, 9);
System.out.println(String.format("What got from native: %s", result));
}
}
The above code is very straightforward. In the static block, we load a binary library which is basically the compiled version of the C program in the library form. Then we declared two method prototypes that represent of the C functions.
Header file generation
In order to start writing the C program, we need to generate the appropriate header file for it. That is the main reason why we started with writing the Java code first. To generate the C header file, need to run the following command:
$ javac JniExample.java -h .
After running the above content, JniExample.h
should be generated with the following content:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JniExample */
#ifndef _Included_JniExample
#define _Included_JniExample
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JniExample
* Method: printHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniExample_printHello
(JNIEnv *, jobject);
/*
* Class: JniExample
* Method: multiple
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_JniExample_multiple
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
As you can see the function names have changed in the header files. Java has added, Java_JniExample_
Another interesting thing to note both declared functions have two additional parameters added in the generated header which are JNIEnv *env, jobject thisObj
.
env
The argument obj
is a reference to the Java object inside which this native method has been declared.
For this tutorial we don’t need to bother with env
and obj
as the C program is not interacting with the Java code.
C code implementation
Now that we have the header file, the next step is to implement the C program as follows:
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include "JniExample.h"
JNIEXPORT void JNICALL Java_JniExample_printHello(JNIEnv *env, jobject thisObj)
{
printf("Hello World from Native method\n");
return;
}
JNIEXPORT jint JNICALL Java_JniExample_multiple(JNIEnv *env, jobject thisObj, jint x, jint y)
{
int result = x * y;
printf("In native function. The result is: %d\n", result);
return result;
}
Gluing all pieces together
Now that both C and Java implementations are ready, the only remaining thing is to compile and run the project. This could be tricky as this step is very dependent on the OS and its configuration. Here we cover how to compile the project for macOS and Linux.
But before jumping to OS-specific configuration the first thing is to find whether JAVA_HOME
echo $JAVA_HOME
$ export JAVA_HOME=/jdj/path
MacOS
To compile the code in macOS, we need to generate .dylib
$ gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/darwin" -dynamiclib -o libhello.dylib Example.c
Keep in mind that in the Java code I’m hello
libhello
And finally to make sure everything is working, we need to run the Java code:
$ java -Djava.library.path=. JniExample
Linux
For Linux, we need to generated .so
file. To do so need to compile the C code like below:
$ gcc -fPIC -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libhello.so Example.c
And then run the Java code:
$ java -Djava.library.path=. JniExample
References
- https://en.wikipedia.org/wiki/Java_Native_Interface
- https://en.wikipedia.org/wiki/Foreign_function_interface
- https://medium.com/@bschlining/a-simple-java-native-interface-jni-example-in-java-and-scala-68fdafe76f5f