JNI: Let's make friends with Java and C ++
Introduction
There are times when in Java some actions are performed outside the normal Java classes. For example, you need to execute code written in C / C ++ or some other language.
In this article, we will consider this issue from a practical point of view, namely, we will write a simple example of the interaction of Java code with C ++ code using JNI . The article does not contain something supernatural, but rather a reminder for those who have not worked with it.
For our purposes, there is the possibility of dynamically loading native libraries, called by the method
System.load()
, which can be found in more detail here .Formulation of the problem
Let us need to implement a class that contains a native method that displays the “Hello world” screen.
JNIHelloWorld.java
package ru.forwolk.test;
public class JNIHelloWorld {
native void printHelloWorld();
}
Header Generation
We will generate the headers of this class for C ++.
First, create a folder in the root of the project, where we will collect the binaries:
mkdir bin
Then, compile our class into this directory
javac -d bin/ src/ru/forwolk/test/JNIHelloWorld.java
In the bin folder, we have a class file. Rather, in bin / ru / forwolk / test /. Let's move to the bin folder and generate headers.
cd bin/
javah ru.forwolk.test.JNIHelloWorld
As you can see, the ru_forwolk_test_JNIHelloWorld.h file appeared in our bin folder. For simplicity, rename it to JNIHelloWorld.h
mv ru_forwolk_test_JNIHelloWorld.h JNIHelloWorld.h
Opening it, we see the following picture:
JNIHelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class ru_forwolk_test_JNIHelloWorld */
#ifndef _Included_ru_forwolk_test_JNIHelloWorld
#define _Included_ru_forwolk_test_JNIHelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: ru_forwolk_test_JNIHelloWorld
* Method: printHelloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_ru_forwolk_test_JNIHelloWorld_printHelloWorld
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
C ++ implementation
Create a file with the source code JNIHelloWorld.cpp. For this purpose, I created a project in Clion, into which I inserted the necessary file. We implement our method.
JNIHelloWorld.cpp
#include
#include "JNIHelloWorld.h"
JNIEXPORT void JNICALL Java_ru_forwolk_test_JNIHelloWorld_printHelloWorld
(JNIEnv *, jobject) {
std::cout << "Hello world!";
}
For Clion to work correctly, you need to add Java libraries to the CMakeLists.txt file:
// Вместо $JAVA_HOME -- путь до Java
include_directories($JAVA_HOME/include)
include_directories($JAVA_HOME/include/linux)
link_directories($JAVA_HOME/include)
link_directories($JAVA_HOME/include/linux)
Next we compile
g++ -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -fPIC JNIHelloWorld.cpp -shared -o helloworld.so -Wl,-soname -Wl,--no-whole-archive
Download in Java
The helloworld.so file appeared in the project root folder. Move it to the bin / folder of the Java project.
Now you need to download our library. It is good practice to statically load the library directly in the class. Add the download directly to the JNIHelloWorld class
static {
// $PROJECT_ROOT -- абсолютный путь до библиотеки
System.load("$PROJECT_ROOT/bin/helloworld.so");
}
Now we can fully use this class. Let's check.
public static void main(String[] args) {
JNIHelloWorld p = new JNIHelloWorld();
p.printHelloWorld();
}
At the output we get
Hello world!
Process finished with exit source 0
Passing parameters
But what if we need to not only execute some code, but also pass parameters and receive an answer? Consider another method that multiplies two numbers. Add a method to the JNIHelloWorld class
native int multiply(int a, int b);
We perform the same actions described above for generating headers. As you can see, the following was generated
/*
* Class: ru_forwolk_test_JNIHelloWorld
* Method: multiply
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_ru_forwolk_test_JNIHelloWorld_multiply
(JNIEnv *, jobject, jint, jint);
We implement the method in JNIHelloWorld.cpp
JNIEXPORT jint JNICALL Java_ru_forwolk_test_JNIHelloWorld_multiply
(JNIEnv *, jobject, jint a, jint b) {
return a * b;
}
Again, we perform the above steps to pull up the library, add a line to main to output the result of the product of two numbers and run
public static void main(String[] args) {
JNIHelloWorld p = new JNIHelloWorld();
System.out.println(p.multiply(2, 2));
p.printHelloWorld();
}
What do we get in the console
4
Hello world!
Process finished with exit source 0
Conclusion
We have examined the possibility of Java using code written in C / C ++. This can be used for various purposes, for example, to increase the speed of code execution, to protect the code from direct interference and for other purposes. I really hope that this article will help you understand the basics of JNI.
All the code I posted in the public domain . In the cpp directory, I placed the C ++ class without the extra Clion project files.
additional literature
Also, for a broader perspective on this topic, I recommend paying attention to the following articles:
JNI, loading native libraries. Changing java.library.path on the fly Is the native method expensive
? JNI Secret Extension