November 11, 2020

How to call c or cpp function from java, JNI native function implementation

I am running ubuntu 18.04 and java version 15.

You can watch me work on this code on YouTube.

Java provides a feature where we can call C or C++ functions. This should be used in the cases, where you need more performance.
In that cases, you can just skip the JVM and call the low level libraries by using JNI.

To mark a function, that this function should be calling a c/c++ function, we use native keyword of java.
Interfaces in C programming is defined by header files. This tells the C/C++ application and java application about the name of the function and datatypes.

So, let us define a java class.

public class PrintName{
    static{
        System.loadLibrary("name");
    }
    //the function is defined in a c-file
    private static native void printName(final String name);

    public static void main(final String ... args){
        printName("Anurag Anand");
    }
}

 

The function marked with native keyword is the function that we going to implement in C.

//the function is defined in a c-file
private static native void printName(final String name);

Before implementation we need to generate an interface i.e a header file .

You need to run this command javac -h . PrintHello.java 

Here . (dot) , is folder location, where the header file needs to be put. You can specify your own folder for that.

This command generates a header file.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class PrintName */

#ifndef _Included_PrintName
#define _Included_PrintName
/*
 * Class:     PrintName
 * Method:    printName
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_PrintName_printName
  (JNIEnv *, jclass, jstring);

#endif

I above code, I have removed the part which allowed this header to be used by a cpp applicaiton, I did it for readability and simplicity.

Next, you need to include this header file in you c-file.

#include<stdio.h>
#include "PrintName.h"

JNIEXPORT void JNICALL Java_PrintName_printName
  (JNIEnv *env, jclass this, jstring name){

  const char * myName = (*env)->GetStringUTFChars(env, name, 0);
  printf("My Name is :- %s.\n", myName);
  (*env)->ReleaseStringUTFChars(env, name, myName);
    
}

Now, we need to compile above c-code.

gcc -fPCI -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libname.so PrintName.c

In above command, $JAVA_HOME   is the location where jdk folder is put. JDK comes with header files, which needs to be included.

On linux, you need to prepend lib word before the name of the library. Because of that, we have named it libname.so otherwise we should have named it name.so .

Once, that is done, we need to tell java code to load this library.

static{
    System.loadLibrary("name");
}

We use System.loadLibrary to do that. You should notice that,  we are referring to that library with name, we are not saying libname.so or libname. 

Before running our java application, we need to tell the java where the library is located at.

java -Djava.library.path=clib PrintName

In above command, we are telling JVM that our library is located in clib folder.