Step by step Java Native Interface (JNI) guide and tutorial
What are JNI and this guide
Java Native Interface (JNI) is a simple tool that allows calling native functions from Java code. In addition, a native function which is called by Java may be able to call back Java functions but we shall not discuss this functionality in this tutorial.
There are plenty of existing guides (some of them are listed below), and I am not competing with them.
Rather, my purpose is to complement them with my experiences: when I tried to use JNI myself and followed these tutorials, I found a few difficulties which were not covered in them, and I wanted explain how I dealt with these difficulties which included packages and mangling conventions. This step-by-step guide explains exactly how I dealt with the difficulties I encountered.
My environment
I have been writing code that was supposed to work on Windows while running myself a Linux Ubuntu with Eclipse Java development environment.
Of course, I needed a Windows machine to compile and run Windows code, and for this purpose I had access to a Windows XP 32-bit computer on which I ran Eclipse 4.3.2, both for Java and for C/C++ native code; for C/C++ I used MinGW C/C++ toolchain.
Note that the dll I produced on the 32 bit computer, cannot be called from 64-bit code. Practically, this means that if I run 32-bit JRE, whether on 32-bit computer or an AMD64 one, I can call this dll from it; however, if I run 64-bit JRE (this can be done only on an AMD64 computer), I cannot call this library.
A side note: if you need to cover both 32-bit and 64-bit JRE, you need to compile two different dlls, write Java code that determines whether your JRE is 32-bit or 64-bit, and load the appropriate one of the two, e.g.:
if(Platform.osArch.contains("64")) {
load your 64-bit dll
}
else {
load your 32-bit dll
}
First, read the existing tutorials
I suggest starting with reading a nice introduction and a step-by-step guide written in 2004 by Govind Seshadri. We shall follow the same steps below, although I shall add some details of mine. If you want, you can complement it with the Wikipedia article.
Other resources: there is a faster and more advanced tutorial and a JavaWorld article by Tal Liron.
How to run javah to create a C header for JNI functions
This is the first step in implementing JNI.
Create a java file with a native function declaration (as explained in the above tutorials), and compile it.
Open a terminal.
If your java files were created by Eclipse, go to the bin/
directory of the project (i.e., cd
to this directory).
Otherwise, go to the directory where the package tree of the class files starts (the bin/
directory of an Eclipse project is a particular case of it).
E.g., if MyClass
is declared in the package Pkg1.Pkg2.Pkg3
and MyClass.class
lies in MyDir/Pkg1/Pkg2/Pkg3
then cd
in your terminal to MyDir
.
After that run javah:
javah Pkg1.Pkg2.Pkg3.MyClass
This creates your header file:
Pkg1_Pkg2_Pkg3_MyClass.h
It is placed in the same directory, MyDir
.
This file contains the C/C++ declarations of the native functions to be called from Java. It starts with the lines
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
The header file referenced here, jni.h
, is provided by java; I have found it in the directory
<Java installation directory>/jdk1.7.0_51/include
On my Windows computer, this is
C:\Program Files\Java\jdk1.7.0_51\include
This directory needs to be added to the include path, together with its subdirectory win32
<Java installation directory>/jdk1.7.0_51/include/win32
that contains another header file, jni_md.h
which is #include
‘d by jni.h
.
(Note that jdk1.7.0_51
in the directory path reflects the fact that I was running jdk 1.7.0 Release 51; if you update your jdk to a later version, while doing that you change the directory name jdk1.7.0_51
accordingly and want to build your dll again, you would have to update your include path.)
Still, Eclipse with MinGW C/C++ compliler could not find this include even though it could display jni.h
in its include list. The only thing that helped, was to replace the line
#include <jni.h>
by
#include "jni.h"
(Regretfully, I had to disregard the warning not to edit this file.)
After that the file successfully compiled.
Calling this code from java
The next step is to create a .c file that implements the declarations in this header (assuming you want to implement it in C as I did).
Compile it to create a Windows dll; if its name is hello.dll then do the following.
Place hello.dll on the Java build path of this project; in Eclipse, open Project Properties (by right-clicking on the project in the project explorer) → Java build path → Source → native library location → Edit, and make sure that the folder containing hello.dll is included.
Insert the following code in the class that calls the native function:
static {
System.loadLibrary("hello"); // Load native library at runtime
// hello.dll (Windows) or libhello.so (Unixes)
}
Note that this code must appear before you actually call your native function; usually this code is put in the same java class in which you have declared your native function in java. For an example, see the Wikipedia article referenced above.
If you want your program to deal with the case the library cannot be loaded, embed the System.loadLibrary
call in a try/catch
block. (The alternative approach that I followed, was to package the dll together with the Java class files and deal with all problems related to this packaging – including with the possibility that the native library cannot be loaded – manually during debugging.)
JNI unsatisfied link error
Still, as I tried to run my program, I got a exception of the type java.lang.UnsatisfiedLinkError
.
It was thrown on the line on which I called my native function; Java could not find it.
I found some good advice on the net; I followed it and installed dumpbin on my Windows computer, see here for details. (The alternative approach is to use pexports utility of MinGW.)
I ran
dumpbin /exports MyDLLname.dll
(according to the description of dumbin) and found that @8
was added to the name of my exported function, which prevented it from being found.
As I studied it further, I found out the following.
The C/C++ header file that was created by javah (see above), defined my function with JNICALL
(see an example in the Wikipedia article). As I looked for the definition of JNICALL
, I found it in the header file jni_md.h
which is include
‘d in the header file jni.h
(both mentioned above). The header jni_md.h
defines JNICALL
as __stdcall
. This is a calling convention and according to Microsoft documentation __stdcall
prefixes an underscore _ to the name and suffixes it with @ followed by the decimal number of bytes in the argument list.
So the conclusion is that @8
is correct (8 being the number of bytes in the two arguments) but the leading underscore was missing.
I have found a solution to add the underscore _ before Java_...
in the name of the C function that implements the native Java call. This needs to be done in the header file which was generated by javah, Pkg1_Pkg2_Pkg3_MyClass.h – see above – and in the .c file that implements this function.
This solution worked successfully.
The remaining question is, whether MinGW compiler operates legitimately or not; this question is not entirely academic since if this is a bug of MinGW then some next version of MinGW may be expected to fix it, and at this point the workaround I implemented, would break down.
The Wikipedia article on Name mangling says: “The mangling scheme was established by Microsoft, and has been informally followed by other compilers including Digital Mars, Borland, and GNU GCC, when compiling code for the Windows platforms.” This means that, on one hand, we cannot complain to MinGW that this is a bug in the technical sense, but we can expect MinGW to fix it eventually.
©Baruch Youssin 2014
Bruscino Domenico Francesco says:
I solve my JNI unsatisfied link error following your tip. Another solution is using the -Wl,–kill-at as shown here: http://www.mingw.org/wiki/JNI_MinGW_DLL. Thank you.
sri says:
thank you for your hint and pointing me in the right direction.
I could compile with jdk11, and native file with 64bit gcc without the underscore prefix. When i tried to compile with java jdk1.8 and native code with 32bit gcc, it wont work. Prefixing the c function with _Java helped fix the problem for jdk1.8.