Java Native Interface
Lecture 7
JNI intro: https://www.youtube.com/watch?v=0FI7AitQJQ4
Introduction
Java Native Interface (JNI) is a programming framework
that is part of the Java Developer Kit (JDK).
JNI interoperate with:
Native applications (program specific to a hardware and
operating system platform)
Libraries written in other programming languages (such as C,
C++)
General Workflow of JNI
Source: http://electrofriends.com/articles/jni/jni-part1-java-native-interface/
Java and JNI
Java applications do not run directly on the hardware, but
actually run in a virtual machine.
Java source code is compiled to a bytecode file and it is
executed by the virtual machine. This makes Java cross-
platform capability.
However, the cross-platform capability limits its
interaction with the local machine's internal components,
making it difficult to use the local machine instructions to
run an existing software library.
JNI make Java code and native code (C/C++) collaborate
and share resources.
Java Compilation
Step 1: Write Java source code Editor
Source code (xxx.java)
Step 2: Compile java source
code into bytecode file Java Compiler
Bytecode file (xxx.class)
Step 3: Execute bytecode file Java Virtual
Machine (JVM)
Output
JNI Compilation
Step 1: Write Java source code Editor
Source code with native declaration (xxx.java)
Step 2: Compile Java source code
to generate the bytecode file and
Java Compiler
C++ header file • Bytecode file (xxx.class)
• C++ header file (xxx.h)
Step 3: Write C++ native code
C++ native code (cxx.cpp)
Step 4: Build shared library file
Shared library file (cxx.dll)
Step 5: Execute bytecode file Java Virtual Output
(need shared library file) Machine (JVM)
Usage of JNI
To leverage platform specific features outside a JVM.
To communicate with device driver written in C/C++ or
assembler that used to access the hardware
To utilize existing code libraries in C/C++ programs.
For example, there might be a really good file compression
library written in C/C++. Why try to rewrite it in Java when it
can be accessed by JNI?
JNI defines a way for the bytecode that Android OS
compiles from managed code (written in the Java or
Kotlin programming languages) to interact with native
code (written in C/C++).
Drawback of JNI
The program is no longer platform independent
The program is not robust
If there is a null pointer exception in the native code, the
JVM can not display a helpful message. It might even lock
up.
Difficult to debug runtime error in native code
JNI Type
JNI is a two-way interface that allows Java
applications to invoke native code (C/C++) and vice
versa.
There are two types of JNI: focus point
calling C/C++ code from Java main program
calling Java code from C/C++ main program
Tools and Components
Java Compiler (Java SE Development Kit –
version 8 and above – 64 bit)
Two main compilers for JNI
C/C++ Compiler (MinGW-w64)
Text Editor (Notepad/Notepad++) The place to write the
source code
Command Prompt (Windows) / Terminal The place to perform the
(MacOs, Ubuntu) program compilation and
execution
Calling C++ from Java (using JNI)
1. Write the Java source code (.java file)
The Java source code consist of native method declaration.
2. Compile the Java source code
Generate the Java bytecode file (.class file)
Generate the C++ header file (.h file)
The C++ header file defines native method signature.
3. Write the C++ native code (.cpp file)
To write the code for body of the native method.
4. Build the shared library file (.dll file)
It create shared library file from C++ native code in step 3
5. Execute the Java program
All the above files must be located in the same directory
Native Method/Function
A method that is native is implemented in platform-
dependent code.
The code is written in another programming language
such as C, C++, or others.
To use native methods in Java code:
To write a native method declaration for each native method
in Java source code.
To load the native code library explicitly in Java code.
To provide the implementation of native method in C++
code.
JNI Example
// Happy.java
class Happy {
public native void printText (); 1 [native method declaration]
static {
[load 2 System.loadLibrary ("happy");
the sam
} en
native am
code e
public static void main (String[] args) {
library] Happy p1 = new Happy ();
p1.printText ();
}
} // happy.cpp 3 [implementation of native method in C++ code]
#include <iostream>
#include "Happy.h"
JNIEXPORT void JNICALL Java_Happy_printText(JNIEnv *env, jobject
obj) {
std::cout << "Happy New Year !\n";
return;
Native Method/Function cont..
In the JNI framework, the native method is implemented
in separate C++ file (with .cpp extension).
The native method is called in the Java file (with .java
extension).
When the JVM invokes the native method, it passes
a JNIEnv pointer, a jobject pointer, and any Java
arguments declared by the Java method.
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj) {
/*Implement Native Method Here*/
}
Implementation of Native Method in the C++ File
JNI Example: Hello World!
Write Java
Hello.java
source code
JNI Example: Hello World!
Write Java
Hello.java javac
source code
Hello.h Hello.class
JNI Example: Hello World!
Write Java
Hello.java javac
source code
iostream.h Hello.h Hello.class
Write C++
native code
CHello.cpp
JNI Example: Hello World!
Write Java
Hello.java javac
source code
iostream.h Hello.h Hello.class
Write C++
native code
CHello.cpp g++ CHello.dll
JNI Example: Hello World!
Write Java
Hello.java javac
source code
iostream.h Hello.h Hello.class
Write C++ java “Hello, World!”
native code
CHello.cpp g++ CHello.dll
JNI “Hello World!” Program
1. Write a Java source file (Hello.java) that declares the
native method.
2. Use javac to compile Hello.java, and it generates class file
(Hello.class) and C++ header file (Hello.h) containing the
native function signature.
3. Write the C++ native code (CHello.cpp) to implement the
native method.
4. Build the shared library file (CHello.dll) from C++ native
code in step 3.
5. Execute the Hello program using the java runtime
interpreter.
Java class file (Hello.class) and native shared library (CHello.dll) are
loaded at the runtime.
Step 1: Create Java source code (Hello.java)
Java file defines a class named Hello that contains a call to
the native method named as print().
class Hello {
// declare a native method print( ) that receives nothing and returns void
// without any implementation
public native void print( );
static // static initializer code
{
System.loadLibrary("CHello"); // load shared library at runtime
} // CHello.dll (Windows)
public static void main(String[] args) {
Hello hw = new Hello( ); // create Java object
hw.print( ); // use Java object to invoke the native method
}
}
Step 1: Create Java source code (Hello.java)
Static initializer invokes System.loadLibrary( ) to load the
native shared library "CHello" (which contains the native
method print( )) during the class loading.
It will be mapped to "CHello.dll" in Windows platform.
The method print( ) is a native method, via keyword
native, which denotes that this method is implemented in
another language.
a native method cannot have a body.
The main( ) method create an instance/object of Hello and
invoke the native method print( ).
Step 2: Compile Java source code
Place the Java source code in the Desktop.
In the command prompt, change the directory into Desktop,
use command “cd”
To compile the Java source file, use command “javac”:
javac -h . Hello.java
No error message means the compilation is success.
The Hello.class is produced and placed in the Desktop (same
place with the Java source code)
Step 2: Compile Java source code cont..
The content of the C++ header file
The header declares a C/C++ function
Java_Hello_print as follows:
JNIEXPORT void JNICALL
Java_Hello_print(JNIEnv *, jobject);
native method name The naming convention for C/C++ function
is Java_{classname}_{method_name} (JNI
arguments).
Java class name
Step 2: Compile Java source code cont..
The arguments:
JNIEnv*: reference to JNI environment, which lets the C/C++
file
access all the JNI functions.
jobject: reference to "this" Java object, the object created in
Java file
The macros of JNIEXPORT and JNICALL:
Both enable the related functions to be exported in the
shared library with the appropriate linkage.
Step 3: Write C++ native code (CHello.cpp)
Write C++ native code using Notepad
Copy the native method signature from the C++ header file.
JNIEXPORT void JNICALL Java_Hello_print (JNIEnv *, jobject);
Write the content of the native method.
#include "Hello.h" // link to the C++ header file
#include <iostream> // implements the std::cout function
// To implement the native method of Hello class
JNIEXPORT void JNICALL Java_Hello_print(JNIEnv *env, jobject obj) {
std::cout << "Hello World!\n";
return;
}
The std::cout function is used to display the string “Hello
World!”.
Save the C++ native code as "CHello.cpp".
Step 4: Generate shared library file
(CHello.dll)
Build the shared library file from C++ implementation code.
The shared library file has a DLL extension if the C++ native code
run under Windows platform.
In command prompt, type the command as below:
The compiler options used are:
- I: to specify the header files directories.
- shared: to generate share library.
- o: for setting the output filename "CHello.dll".
Assume Java compiler is located at C:\Program Files\Java\jdk-17.0.3.1
Step 5: Execute Hello Program
Place the Hello.class and CHello.dll in the same folder.
Execute the Java class (main method) as shown below:
java HelloWorld
You should see “Hello World!” appear on the screen!
If the error message “java.lang.UnsatisfiedLinkError”
appears, this means Java could not find the shared library
(.dll file) OR mismatch in native functions.
Error Message of JNI
What is the main cause to have such error messages below ?
Error message 1:
Error message 2:
Primitive Types and Native Equivalents
Each element in Java language must have a corresponding
native counterpart.
Object Types and Native Equivalents
Object Mappings Example
In the Java source code:
class Book {
private native String read(String prompt);
... da t
at
yp
} eo
data type of return value f in
pu
tp
a ra
me
te r
In the C implementation code:
JNIEXPORT jstring JNICALL Java_Book_read(JNIEnv *, jobject, jstring);
“Java_” prefix + class name + “_” + method name
Accessing Java Strings
class Book {
private native String read (String prompt);
...
}
// To display the input argument of read function in the C++ implementation code
JNIEXPORT jstring JNICALL Java_Book_read(JNIEnv *env, jobject obj, jstring
text)
{
std::cout << text;
...
}
It is wrong to use the jstring as C-string directly in native code.
The jstring type in JNI is not equivalent to the string type in C/C++.
jstring must be converted to C-string using appropriate JNI function.
Accessing Java Strings
The jstring type is strings type used in Java virtual
machine, and is different from the regular C-string type.
jstring cannot be used as a normal C-string.
The jstring need to be converted to C-string through JNI
functions.
Two type of character encoding format:
Unicode : 16-bit characters
UTF-8 : encoding scheme that is compatible with 7-bit ASCII
strings
Accessing Java Strings cont..
/* Right way */
JNIEXPORT jstring JNICALL Java_Book_read(JNIEnv *env, jobject obj, jstring text) {
// to convert the jstring format to a C-style string format and
// the value of text (jstring) is stored to the str (C-style string)
const char* str = env->GetStringUTFChars(text, NULL);
std::cout << "The book title is " << str << "\n";
/* release the memory allocated for the string operation */
env->ReleaseStringUTFChars(text, str);
string title;
cout << "\nEnter the new book title: ";
getline(cin, title);
jstring result = env->NewStringUTF(title.c_str()); // to create a new jstring instance and
// store it into variable result
return result;
}
JNI String Functions
The JNI provides functions to obtain the Java string
format (Unicode and UTF-8) as shown below.
JNI Function Description
GetStringChars Obtains or releases a pointer to the contents of a string in
ReleaseStringChars Unicode format. May return a copy of the string.
GetStringUTFChars Obtains or releases a pointer to the contents of a string in
ReleaseStringUTFChars UTF-8 format.
NewString Creates a java.lang.String instance that contains the same
sequence of characters as the given Unicode C string.
NewStringUTF Creates a java.lang.String instance that contains the same
sequence of characters as the given UTF-8 encoded C string.
GetStringLength Returns the number of Unicode characters in the string.
GetStringUTFLength Returns the number of bytes needed to represent a string in
the UTF-8 format.
Extra Exercise (Q1)
// Q1.java
import java.util.Scanner;
class Q1 {
public native void printTest(int num);
static {
System.loadLibrary("CQ1");
}
public static void main(String[] args) {
Scanner ab = new Scanner(System.in);
System.out.printf("Please enter your magic number: ");
int num = ab.nextInt();
Q1 ww = new Q1();
ww.printTest(num);
}
}
Extra Exercise (Q1)
// CQ1.cpp
#include "Q1.h"
#include <iostream>
JNIEXPORT void JNICALL Java_Q1_printTest(JNIEnv *env, jobject obj, jint
nn) {
std::cout << "The magic number is " << nn << "\n";
return;
}
Extra Exercise (Q2)
// Q2.java
import java.util.Scanner;
class Q2 {
public native void printPyramid(int num);
static {
System.loadLibrary("CQ2");
}
public static void main(String[] args) {
Scanner ab = new Scanner(System.in);
System.out.printf("Please enter number of rows: ");
int num = ab.nextInt();
Q2 ww = new Q2();
ww.printPyramid(num);
}
}
Extra Exercise (Q2)
#include "Q2.h"
#include <iostream>
JNIEXPORT void JNICALL Java_Q2_printPyramid(JNIEnv *env, jobject obj, jint nn)
{
for (int i = 1; i <= nn; ++i) {
for (int j = 1; j <= i; ++j) {
std::cout << "* ";
}
std::cout << "\n";
}
return;
}
Extra Exercise (Q2)