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 🙂

2048 in C / C++

2048 is perhaps one of the most addictive mathematical puzzle games. For starters, it is played on a 4 × 4 game board which begins with an initial configuration of two tiles, of value 2 or 4, placed at arbitrary locations on the grid.

The player then selects to move Up, Down, Left, or Right. Each move shifts all tiles on the grid in the direction chosen. If two adjacent tiles have the same value, they combine, and the single resulting tile after the combination will be the sum (twice). Following the player’s move, a tile of value 2 or 4 will be generated at a random position on the board.

The goal of the game is to combine equally-valued tiles to reach the 2048 tile without getting stuck.

HOW I CODED 2048  ( Source code at the end of this page)

9

THE LOOP

  1. Display the grid.
  2. Take direction input.
  3. Check for game-over.
  4. Update the grid.
  5. Spawn a new tile.
  6. Refresh the grid.

LAYOUT

First thing you notice is that the game is basically a 4×4 matrix, a two dimensional array. Initialize all elements in it to zero. But then the empty tiles do not display ‘0’ on the game, so while printing the array elements, if the element is zero, print a space.

INPUT WITHOUT PRESSING ENTER KEY

You don’t want to press enter after every key press, a quick and easy way would be to modify the Linux Terminal behavior using 2 system functions. ( Beware, System() functions are vulnerable. Read here )

system("stty raw");
cin>>control;
system("stty cooked");

ESSENTIAL VARIABLES AND FUNCTIONS

> We require 2 grids, one that keeps the current state of the game ( current grid ) and another that keeps the previous state , i.e a move before ( backup grid ). The backup grid serves 2 purposes:

  1. For the UNDO functionality, resuming to the previous state.
  2. To check if the grid moves.

> Random function to generate random positions ( rows and columns ) for the initial two tiles on the board and for the further tiles which will be generated on each move. The thing to note here is that probability of occurrence of 4 is less than that of 2.

> Greatest function to find the greatest tile in the grid at any state ( max tile ).

MOVING TILES IN THE GRID

// Case of Moving UP
for(int i=0; i<4 ;i++)       // Traverse from Top to bottom of a column
   for(int j=0; j<4 ;j++)
   {
      if(!grid[j][i])    // If tile is empty
      {
         for(int k=j+1; k<4 ;k++)  // Traverse below to find a non-zero element
            if(grid[k][i])
            {
               grid[j][i]=grid[k][i]; // Move the non-zero element to the empty tile
               grid[k][i]=0;          // Assign the non-zero element with zero
               break;
            }
      }
  }

10

UPDATING THE GRID

  • Check if adjacent tile is equal.
  • Sum / double the tile(s) if they are equal.
// When moving UP 

if(grid[j][i]&&grid[j][i]==grid[j+1][i]) // Check Tile is non zero and
{                                        // adjacent tile is equal
   grid[j][i]+=grid[j+1][i];             // add to first element or double
   grid[j+1][i]=0;                       // assign second element with zero
}

11

GAME OVER LOGIC

12

13

The game ends when

  • Grid is full and a new tile cannot be spawned.
  • The game is won, 2048 has been created.

To check whether the grid is full, check if all the elements are non-zero. Spawn mentioned below.

14

To check whether the game is won, check If the max tile is equal to the win (2048), display the win screen and update the win to win*2 ( 4096, 8192, 16384 …)

SPAWNING A NEW TILE

With every move, a new tile should be spawned in a random position under the condition that the selected direction moves at least 1 tile in the grid. So this is where the backup grid comes into use, before updating the grid :

  • Check if the current grid is equal to the backup grid.

So that we know if at least one tile in the grid has moved. Then spawn a new tile in an empty cell.

SCORING ALGORITHM

Merging two lower tier blocks together will give you the score of the higher tier block (score of +8 gained from merging two 4’s). For any specific tile score, you have to add up all the scores from the lower tiers.

  • Creating a 2 tile = +0pts
  • Creating a 4 tile = 4 = +4pts
  • Creating a 8 tile = 8 + 2×4 = +16pts
  • Creating a 16 tile = 16 + 2×8 + 4×4 = +48pts
  • Creating a 32 tile = 32 + 2×16 + 4×8 + 8×4 = +128pts …

Which can be simplified as

  • Tile 21 = 0 x 21 = 0pts
  • Tile 22 = 1 x 22 = 4pts
  • Tile 23 = 2 x 23 = 16pts
  • Tile 24 = 3 x 24 = 48pts

i.e tile-value times ( one less than ( log of tile-value to the base 2 ))

So the algorithm would be:

score+=(((log2(grid[j][i]))-1)*grid[j][i]);

 

UNDO

To perform UNDO operation, copy the backup grid to the current grid.

 

RANDOM GENERATION

Make the computer play the game on its own ! For that, remove the input call and  create a char array with all the four direction keys ( w,a,s,d ) and choose a key in random. But if you’ve played 2048 long enough, you’d know that skipping a direction is the best strategy, so choose any 3 directions.

char keys[]="wad";
input=keys[rand()%3+0;]; // this function chooses 0,1 or 2 randomly
                           // assigns key in that index to input variable

 

SOURCE CODE

CPP here
C will be posted soon