JNI ( Java Native Interface ) for C/C++ with examples

When writing applications in Java, there are times when Java alone fails to meet the needs of an application. You might want to use a feature not present in the standard Java class library or you might just want to use an existing library written in some other language. That’s where JNI comes in.

I found that most of the online documentation on JNI seems pretty scattered and obsolete. Therefore the scope of this post is to show you how to implement JNI with simple examples for :

  • Writing a HelloWorld in C and calling it from Java
  • Passing Integers and Strings from C to Java
  • Passing object Arrays from C to Java

The same can be implemented with C++ too. Note the modification mentioned in step 4 below.

Clone all the examples from Git

 

HelloWorld from C

1. Write the Java code

//HelloWorld.java

public class HelloWorld 
{
  native void cfunction();//Declaring the native function
                            
  static
  {
     System.loadLibrary("forhelloworld");//Linking the native library
  }                                      //which we will be creating.

  public static void main(String args[]) 
  {
     HelloWorld obj = new HelloWorld();
     obj.cfunction();//Calling the native function
  }
}

2. Compile the Java code and generate the class file


javac HelloWorld.java

3. Generate a Header file from the class file


javah HelloWorld

This will generate a file HelloWorld.h which contains :

//HelloWorld.h

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

#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloWorld
 * Method:    cfunction
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloWorld_cfunction
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

3. Obtain the JNI function signature from the header file

  1. These 2 lines make up the function header :
JNIEXPORT void JNICALL Java_HelloWorld_cfunction
(JNIEnv *, jobject);

Even if we declare native functions with no arguments, the JNI function signature still holds 2 arguments, they are:

  • JNIEnv * : A pointer referencing all the JNI functions
  • jobject     : An equivalent of this pointer

4. Write the native code using the function signature

  • Add the header file jni.h
  • Instead of the main() function, use the function signature obtained from the previous step.
  • Add argument variables for JNIEnv (env) and Jobject (jobj).
  • IMPORTANT :: If the native code is in C++, please note the only modification to be made is that the JNI functions should be called as env->func_name() instead of (*env)->func_name(). That is because C uses structures while C++ uses classes.
//HelloWorld.c
 
#include <jni.h>
#include <stdio.h>
 
JNIEXPORT void JNICALL Java_HelloWorld_cfunction
(JNIEnv *env, jobject jobj)
{
   printf("\n > C says HelloWorld !\n");
}

5. Generate the library file from the native code

gcc -o libforhelloworld.so -shared -fPIC -I (PATH TO jni.h header) HelloWorld.c -lc 
  •     libforhelloworld.so is the name of the native library you are going to create, it should be named as “lib”+(the library name used in the load library statement within the java code)
  •     -fPIC is some sort optimization for loading the machine code into the RAM, gcc requested this flag to be set. Might not be required for all systems.

If you don’t specify the path correctly you will encounter this error :

HelloWorld.c:1:17: fatal error: jni.h: No such file or directory
 #include <jni.h>
                 ^
compilation terminated.

It is usually present inside

/usr/lib/jvm/default-java/include

or

/usr/lib/jvm/java-1.7.0-openjdk-amd64/include

depending upon the version of Java you have installed in your system.

6. Place the library file in the standard /usr/lib folder

If the previous command executed successfully, it would have generated a file libforhelloworld.so . Conventionally this library file need not be placed anywhere else, it needs to reside in the current working directory.

But for that to work you need to have set the JAVA_PATH variables correctly, in most cases they won’t be set correctly. An easy hack to this would be to just place it inside /usr/lib

sudo cp libforhelloworld.so /usr/lib

If you don’t place the library file, you would encounter this error :

Exception in thread "main" java.lang.UnsatisfiedLinkError: no forhelloworld in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886)
        at java.lang.Runtime.loadLibrary0(Runtime.java:849)
        at java.lang.System.loadLibrary(System.java:1088)
        at HelloWorld.<clinit>(HelloWorld.java:9)

7. Execute the Java application

java HelloWorld

If you’ve followed all the steps correctly, C would greet the world 😀

 > C says HelloWorld !

 

PASS INTEGERS FROM C TO JAVA

Example : Write a Java program to find the factorial of a number. Pass the number as an argument from Java to a native function in C which returns the factorial as an integer.

Java code

//factorial.java
 
import java.util.Scanner;
 
public class factorial
{
  native int fact(int num);
 
  static
  {
        System.loadLibrary("forfact");
  }
 
  public static void main(String args[])
  {
        Scanner inp = new Scanner(System.in);
 
        System.out.println(" > Enter number :: ");
        int num = inp.nextInt();
 
        factorial obj = new factorial();
 
        System.out.println(" > The factorial of "+num+" is "+obj.fact(num));
  }
}                                                                                                         

C code

//factorial.c
 
#include <jni.h>
#include <stdio.h>
 
JNIEXPORT jint JNICALL Java_factorial_fact
(JNIEnv *env, jobject jobj, jint num)
{
  jint result=1;
 
  while(num)
  {
        result*=num;
        num--;
  }
 
return result;
}

 

PASS STRINGS FROM C TO JAVA

Example : Write a Java program to reverse a given string. Pass the given string as an argument from Java to a native function in C which returns the reversed string.

Java code

//reverse.java
 
import java.util.Scanner;
 
public class reverse
{
  native String reversefunc(String word);
 
  static
  {
        System.loadLibrary("forreverse");
  }
 
  public static void main(String args[])
  {
        Scanner inp = new Scanner(System.in);
 
        System.out.println(" > Enter a string :: ");
        String word = inp.nextLine();
 
        reverse obj = new reverse();
 
        System.out.println(" > The reversed string is :: "+obj.reversefunc(word));
  }
}

C code

//reverse.c
 
#include <jni.h>
#include <stdio.h>
 
JNIEXPORT jstring JNICALL Java_reverse_reversefunc
(JNIEnv *env,jobject jobj,jstring original)
{
  const char *org;
  char *rev;
 
  org = (*env)->GetStringUTFChars(env,original,NULL);
 
  int i;
  int size = (*env)->GetStringUTFLength(env,original);
   
  for(i=0;i<size;i++)
        rev[i]=org[size-i-1];
 
  rev[size]='\0';
 
return (*env)->NewStringUTF(env,rev);
}

 

PASS INTEGER ARRAYS FROM C TO JAVA

Example : Write a program that generates the first n Fibonacci numbers. Pass ‘n’ as an argument from Java to a native function in C that returns the Fibonacci numbers as an integer array.

Java code

//fibonacci.java
 
import java.util.Scanner;
 
public class fibonacci
{
  native int[] returnfibo(int n);
 
  static
  {
        System.loadLibrary("fibonacci");
  }
 
  public static void main(String args[])
  {
        Scanner inp = new Scanner(System.in);
 
        System.out.println(" > Enter n :: ");
        int n = inp.nextInt();
 
        fibonacci obj = new fibonacci();
        int[] Fibo = obj.returnfibo(n);
 
        System.out.println(" > The first "+n+" fibonacci numbers are :: ");
 
        for(int i=0;i<n;i++)
          System.out.print(Fibo[i]+",");
  }
}

C code

//fibonacci.c
 
#include <jni.h>
#include <stdio.h>
 
JNIEXPORT jintArray JNICALL Java_fibonacci_returnfibo
(JNIEnv *env,jobject jobj,jint n)
{
  jintArray fiboarray  = (*env)->NewIntArray(env,n);
 
  int first=0;
  int second=1;
  int next;
  int i;
  int fibo[n];
 
  for(i=0;i<n;i++)
  {
        if(i<=1)
          next = i;
        else
        {
          next = first + second;
          first = second;
          second = next;
        }
 
        fibo[i] = next;
  }
 
  (*env)->SetIntArrayRegion(env,fiboarray,0,n,fibo);
 
return fiboarray;
}

 

PASS STRING ARRAYS FROM C TO JAVA

Example : Write a Java program that displays the days of the week, which are passed from a native function in C.

Java code

//daysofweek.java
 
public class daysofweek
{
  native String[] returndays();
 
   static
   {
      System.loadLibrary("daysofweek");
   }
 
   static public void main(String args[])
   {
 
      daysofweek obj = new daysofweek();
      String[] days = obj.returndays();
 
      System.out.println(" > The days of the week are :: ");
      for(String name: days)
        System.out.println(name);
   }
}

C code

//daysofweek.c
 
#include <jni.h>
#include <stdio.h>
 
JNIEXPORT jobjectArray JNICALL Java_daysofweek_returndays(JNIEnv *env, jobject jobj)
{
 
  char *days[]={"Sunday",
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday",
                "Friday",
                "Saturday"};
 
  jstring str;
  jobjectArray day = 0;
  jsize len = 7;
  int i;
 
  day = (*env)->NewObjectArray(env,len,(*env)->FindClass(env,"java/lang/String"),0);
   
  for(i=0;i<7;i++)
  {
    str = (*env)->NewStringUTF(env,days[i]);
    (*env)->SetObjectArrayElement(env,day,i,str);
  }
   
return day;
}

As you can see, the C code is returning an array of char pointers (C strings), while the Java code is expecting an array of Java Strings. But you don’t have to worry about that, the good old implicit conversion comes to the rescue 🙂

2 thoughts on “JNI ( Java Native Interface ) for C/C++ with examples

  1. Nice post.

    It has become a default behavior to add a “lib” prefix before a library name, to actually differentiate between packages and libraries. And as for the -fPIC it is a compiler flag passed when a library is been built to generate Position Independent Code, meaning it doesn’t matter where you load the library in the memory, it will work. -shared flag is passed to make it a shared library.

    These things are particularly useful in Makefiles and cmake build tools, you can lookup some examples to know more.

    Liked by 1 person

Leave a reply to Jackson Isaac Cancel reply