Once we write and compile Java code, the result of this process is platform-independent bytecode. However, sometimes we do actually need to use code that’s natively-compiled for a specific architecture like C/C++
.
When making a native executable program, we can choose to use static or shared libs:
The latter is what makes sense for JNI as we can’t mix bytecode and natively compiled code into the same binary file.
private native void aNativeMethod();
With the main difference that instead of being implemented by another Java class, native method will be implemented in a separated native shared library.
A table with pointers in memory to the implementation of all of our native methods will be constructed so they can be called from our Java code.
package com.baeldung.jni;
public class HelloWorldJNI {
static {
System.loadLibrary("native");
}
public static void main(String[] args) {
new HelloWorldJNI().sayHello();
}
// Declare a native method sayHello() that receives no arguments and returns void
private native void sayHello();
}
Within C++ the definition and the implementation are usually stored in .h and .cpp files respectively.
To create the definition of the method, we have to use the -h flag of the Java compiler:
javac -h . HelloWorldJNI.java
This will generate a com_baeldung_jni_HelloWorldJNI.h
file with all the native methods included in the class passed as a parameter:
JNIEXPORT void JNICALLJava_com_baeldung_jni_HelloWorldJNI_sayHello(JNIEnv *, jobject);
Now, we have to create a new .cpp file for the implementation of the sayHello()
.
JNIEXPORT void JNICALL Java_com_baeldung_jni_HelloWorldJNI_sayHello
(JNIEnv* env, jobject thisObject) {
std::cout << "Hello from C++ !!" << std::endl;
}
We use G++
compiler to build our shared library from the C++ code and run it.
MacOS version:
g++ -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/darwin com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o
Once we have the code compiled into com_baeldung_jni_HelloWorldJNI.o
, we have to include it in a new shared library.
Whatever we decide to name the shared library is the argument passed into the method System.loadLibrary
. We named ours “native”, and we’ll load it when running our Java code.
The G++
linker then links the C++
object files into our bridged library.
MacOS version:
g++ -dynamiclib -o libnative.dylib com_baeldung_jni_HelloWorldJNI.o -lc
We can now run our program from the command line.
We need to add the full path to the directory containing the library we’ve just generated. This way Java will know where to look for our native libs:
java -cp . -Djava.library.path=/NATIVE_SHARED_LIB_FOLDER com.baeldung.jni.HelloWorldJNI