This blog is a fork of http://stuf.ro/calling-c-code-from-java-using-jni with some errors fixed and C++ support added.
In this tutorial we’ll be creating a Java application calling code from a native library. We’ll have a Java application called HelloWorld
which will call the function helloFromC
from a shared library named ctest
, using Java Native Interface.
First off, we’ll create a file named HelloWorld.java
to contain the HelloWorld
class.
1 2 3 4 5 6 7 8 9 10 |
|
- Make the JVM aware of a function defined externally, named
helloFromC
- Load an external library called
ctest
(which will need to define this function) - Call the function we talked about
Even though we didn’t write any library yet, we can still compile the Java application, because this is a dependency that will be resolved at runtime. So, let’s compile the application:
javac HelloWorld.java
This will generate a HelloWorld.class file containing the application. Running the application will now result in an error, as we expect, because the library is not created yet:
java -cp . HelloWorld
Exception in thread "main" java.lang.UnsatisfiedLinkError: no ctest in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1754)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1045)
at HelloWorld.(HelloWorld.java:6)
Alright, let’s now start writing the ctest library in C. To do that, we must first generate a header file from the .class file we created earlier. This header file will contain the definition of the function as it must be present in the C file.
javah -cp . HelloWorld
This command will generate a HelloWorld.h
file in the same directory, containing the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
We’ll leave this file exactly as is, as the comment suggests, but we need to copy the function definition. Copy the definition and put it in a new file, named ctest.c:
1 2 3 4 5 6 |
|
Note that we gave names to the parameters. Now let’s implement the function. Aside from our own includes, we also need to include jni.h for this to work. So, modify the ctest.c file to contain something like:
1 2 3 4 5 6 7 8 9 10 |
|
Now that we have the file, let’s compile it and create a native library. This part is system dependent, but the only things that change really are the extension of the generated library file and the path to the jni.h include.
gcc -shared -fpic -I$JAVA_HOME/include -I$JAVA_HOME/include/linux ctest.c -o libctest.so
Replace .so with .dylib if you’re on a Mac, or .dll if you’re on Windows (remove the lib part from the file name as well if you’re on Windows). Also, replace /path/to/jdk/headers with the full path to the directory containing the file jni.h. If you don’t know where that is, you can use the locate jni.h command on UNIX-like systems.
Once you successfully run the above command, you will see a libctest.so file in the current directory (or libctest.dylib or libctest.dll). If you remember from the Java code you wrote earlier, the virtual machine will expect a library named ctest to reside in the current directory (point 2). By that, it means that a file with the name libctest.so should be here, which you just created.
To see this in action, run the application:
java -cp . -Djava.library.path=. HelloWorld
If everything works correctly, you should see:
Hello from C!
If we want to call C++ function, we need to make two changes.
First, rename the file ctest.c
to ctest.cpp
and append extern "C"
to every function that we want to call from Java code.
1 2 3 4 5 6 7 8 9 10 |
|
Second, use g++
instead of gcc
, and link the Standard C++ library by adding the option -lstdc++
to g++.
g++ -shared -fpic -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -lstdc++ ctest.cpp -o libctest.so
To see if it works, run the application:
java -cp . -Djava.library.path=. HelloWorld
If everything works correctly, you should see:
Hello from C++!