JNI

06 Jul 2024 . java .

Java Native Interface

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++.

Libs

When making a native executable program, we can choose to use static or shared libs:

  • Static libs – all library binaries will be included as part of our executable during the linking process. Thus, we won’t need the libs anymore, but it’ll increase the size of our executable file.
  • Shared libs – the final executable only has references to the libs, not the code itself. It requires that the environment in which we run our executable has access to all the files of the libs used by our program.

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.

Example

Create Java Class

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();
}

Implement a Method in C++

Within C++ the definition and the implementation are usually stored in .h and .cpp files respectively.

  1. 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);
    
  2. 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;
     }
    
  1. 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

  2. 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.

  3. 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
    
  4. 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