JNI is a way to connect Java code to other native code, mostly C/C++. It's a "bridge" between two different languages.
JNI stands for Java Native Interface.
Java has to know which calls are coming from native code and which do not. To make this difference, the native keyword has to be used for methods that are implemented in native code.
Example:
public native float getTemperature();
public native TemperatureData getDetailedTemperature();
The former method is a "simple" one since it returns a type that is also known in the JNI world: a float.
The latter one is a more complex one since that one returns an object. To be able to return an object, you need to have a class of that object (here: TemperatureData.java) which contains the primitives inside it. That object will later on be used in the C/C++ implementation to fill in the fields internally and return a jobject of type TemperatureData.
We'll see a complete example shortly.
JDK has tools to generate the C/C++ header file from Java files that contain native methods.
Prior to Java 1.8 there was a separate executable called javah that did the job. However, since Java 1.8 the Java compiler itself has now an option to tell it to compile the C/C++ header files: -h. So, now the command is javac -h <output_dir> <rest_of_the_command>. <output_dir> is the directory where the Java compiler will put the generated header files. This location will be needed when running the C/C++ make process.
Below is an example of both a simple as well as a complex data type that is to be processed and returned by the native code. The example simulates a temperature scaler application where the temperature sampling and scaling is done in native code. All the rest is done in the Java world.
To start with, we must first create our hierarchy structure. We will have two types of files we have to make ourselves:
Java files which will reside in a package com.jni.example and the package itself will be in a subdirectory java. So we need a directory structure ./com/jni/example within the subdirectory java. The subdirectory java is arbitrarily chosen and could be whatever we want.
In the subdirectory ./com/jni/example we will have 3 Java files:
TemperatureSampler.java: the entry Java class containing the main method and also 2 methods indicated with native, next to other methods
TemperatureData.java: the data structure for the sampler class. This will contain the following 3 variables:
timestamp of type String
temperature of type float
scale of type TemperatureScale
TemperatureScale.java: an enum class containing the following 3 temperature scales:
Kelvin
Celcius
Fahrenheit
C++ files which will reside in a directory cpp
Lastly, we need an output directory in which all the generated stuff will be located: out. Next to the shared library that we will create and the compiled version of the C/C++ file, this directory will also contain a few subdirectories like so:
jniHeaders: this directory will contain the generated header files with the prototypes to be implemented in the C/C++ file
com/jni/example: this directory will contain the compiled Java classes with the extension .class
So, the hierarchy of our project will look like this:
./cpp
./java/com/jni/example
./out
Let's start with the main Java class TemperatureSampler.java. This is the content of the file:
package com.jni.example;
public class TemperatureSampler {
static {
System.loadLibrary("temperaturesampler");
}
public static void main(String[] args) {
TemperatureSampler temperatureSampler = new TemperatureSampler();
System.out.println("---- Back in Java land: sampled temperature = " + temperatureSampler.getTemperature());
TemperatureData temperatureData = temperatureSampler.getDetailedTemperature();
if (temperatureData != null) {
System.out.println(temperatureData);
}
}
public TemperatureScale getPreferredScale() {
return TemperatureScale.KELVIN;
}
private native float getTemperature();
public native TemperatureData getDetailedTemperature();
}
There's two important things to mention here about this code:
We're about to load a library which is called temperaturesampler. In reality it will be called libtemperaturesampler.so in a Linux environment and temperaturesampler.dll in a Windows environment. We're only looking at a Linux environment here, since it has to run on a Raspberry Pi or a Le Potato SBC.
The last two lines contain methods with the keyword native, so they're candidates for a (generated) C/C++ header file. I've given the two flavours here, private and public.
As mentioned before, this class contains the data representation for the temperature sampler and looks like so:
package com.jni.example;
public class TemperatureData {
public String timestamp;
public float temperature;
public TemperatureScale scale;
@Override
public String toString() {
return String.format("\n------ FINAL OUTPUT ------\n\nTimestamp = %s\nTemperature = %f\nScale = %s", timestamp, temperature, scale);
}
}
Important to mention here is that the name of the variables are very important in the continuation of the code. They have to match 1-to-1!
This class contains only an enumeration with the 3 types of temperature scales.
package com.jni.example;
enum TemperatureScale {
KELVIN,
CELCIUS,
FAHRENHEIT
}
Next, it's time to generate our code. Although we're still missing our C/C++ implementation, we first have to have our generated header file done by the Java compiler to know exactly what we have to implement in the C/C++ file.
To make life easy, we can make a Makefile that will contain all the necessary commands to compile our project.
Below is the content of the Makefile (yes, capital 'M': the make process is quite picky on the correctness of file names, including case sensitiveness):
export JAVA_HOME=/home/pi/mystuff/jdk11
export OUT_DIR=${PWD}/out
export MY_JNI_HEADERS=${OUT_DIR}/jniHeaders
export PROJECT_PACKAGE=${PWD}/java/com/jni/example
export CPP_FOLDER=${PWD}/cpp
temperaturesampler: clean
mkdir -p ${OUT_DIR} ${MY_JNI_HEADERS}
javac -h ${MY_JNI_HEADERS} -d ${OUT_DIR} ${PROJECT_PACKAGE}/*.java
g++ -c -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -I${MY_JNI_HEADERS} ${CPP_FOLDER}/TemperatureSampler.cpp -o ${OUT_DIR}/temperatureSampler.o
g++ -shared -fPIC -o ${OUT_DIR}/libtemperaturesampler.so ${OUT_DIR}/temperatureSampler.o -lc
run: temperaturesampler
java -cp ${OUT_DIR} -Djava.library.path=${OUT_DIR} com.jni.example.TemperatureSampler
go:
java -cp ${OUT_DIR} -Djava.library.path=${OUT_DIR} com.jni.example.TemperatureSampler
clean:
rm -rf ${OUT_DIR} ${MY_JNI_HEADERS}
The first thing we do is to declare a few exports to make the rest of the Makefile easier to implement.
First of all, the Java installation directory has to be known. We only have to give the path upto - but not including - the bin directory. Therefor, the JAVA_HOME environment variable is assigned the path of the Java installation.
Next to this, we're defining an environment variable for the output directory.
Next, a path environment variable is defined to indicate where the Java generator must put the generated header files.
Next, the location of the Java files is assigned to an environment variable.
And finally the location of the C/C++ files is assigned to an environment variable.
As one can see, a next environment variable can use previously defined environment variables using the syntax ${...}.
The next big thing we can see in the Makefile are the targets. By default, when invoking make on the command line without any arguments, the first target encountered will be executed: temperaturesampler.
If you want another target to be executed, you have to be passed as argument to the make command. Example: make run.
The following targets are defined:
temperaturesampler: this is the default target that will do most - if not all - of the compilation work here. As you can see, it depends on another target clean which will be executed first before the "body" of the temperaturesampler target will be executed.
The following tasks will be performed by the body of this target:
make the directory where the generated JNI headers will be placed
invoke the Java compiler including the JNI header generation with the -h option. Thanks to the environment variables, the locations where to put or get files is known to the Java compiler.
Note that the generated header file will be prefixed with the package name: com_jni_example followed by an underscore _, then followed by the name of the C++ file TemperatureSampler (still to be defined in a moment) and finally ended with the header file extension .h.
So, the full header name file will be com_jni_example_TemperatureSampler.h.
Also note that I could have named all Java files to be compiled individually like so:
${PROJECT_PACKAGE}/TemperatureSampler.java ${PROJECT_PACKAGE}/TemperatureData.java ${PROJECT_PACKAGE}/TemperatureScale.java
However, the very annoying thing about this approach is that you have to change the Makefile each and every time files are added and/or removed.
The better approach is like given in the Makefile: ${PROJECT_PACKAGE}/*.java
The wildcard * is substitute for all the Java files into that directory which makes the build system much more flexible and less (in fact: not at all) dependend on changing Java files.
invoke the GNU C++ compiler to create the C/C++ object from the C++ file TemperatureSampler.cpp. The resulting object file will be called temperatureSampler.o and will be used in the next step to generate the library.
Note that we also have a path to both the ./include as well as the ./include/linux locations in the Java JDK tree:
./include:
this subdirectory contains the jni.h file which has all the JNI structures, datatypes, constants, function pointer prototypes,... defined and is needed to satisfy the C++ linker
./include/linux:
contains jni_md.h which has extra #define and typedef definitions. This header file is indirectly included by the jni.h header file.
There's another header file into that subdirectory but that's related to a long-gone feature of Java, called AWT, and is not used AFAICS.
invoke the GNU C++ compiler to generate the library libtemperaturesampler.so from the object file generated by the step just above.
run: this target will run the final Java application with the necessary class and library path inputs. You can see that this target has a dependency on the target temperaturesampler. So, prior to running the executable, the build will be started first.
go: this target will to the same as the run target, except that it will not invoke the whole compilation process first.
clean: this target will delete all the generated files recursively and forced (hence, -rf)
If we now run this Makefile by running the command make in the directory where the Makefile is located, it will fail. It will fail since we don't have our C/C++ file yet.
However, all the Java classes and (very important!) the generated header file should be in place. As said before, we first need the generated header file before we can start implementing the C/C++ file.
Finally, we come to the C/C++ implementation part. The first thing to see is the content of the generated header file com_jni_example_TemperatureSampler.h. It is like so (remember, this is generated, do not modify!!!):
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jni_example_TemperatureSampler */
#ifndef _Included_com_jni_example_TemperatureSampler
#define _Included_com_jni_example_TemperatureSampler
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jni_example_TemperatureSampler
* Method: getTemperature
* Signature: ()F
*/
JNIEXPORT jfloat JNICALL Java_com_jni_example_TemperatureSampler_getTemperature
(JNIEnv *, jobject);
/*
* Class: com_jni_example_TemperatureSampler
* Method: getDetailedTemperature
* Signature: ()Lcom/jni/example/TemperatureData;
*/
JNIEXPORT jobject JNICALL Java_com_jni_example_TemperatureSampler_getDetailedTemperature
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Now we know what methods must be implemented. The method names of the generated header files will always have the following template:
JNIEXPORT <return_type> JNICALL Java_<package_name>_<class_name>_<function_name>(<JNI_arguments>)
as can be seen in the generated header file above.
Example:
<package_name> <class_name> <function_name> <JNI arguments>
JNIEXPORT jobject JNICALL Java_com_jni_example_TemperatureSampler_getDetailedTemperature (JNIEnv *, jobject)
Two JNI arguments are always present:
JNIEnv *: a pointer to a structure storing all JNI function pointers (see section below: JNI structure). The structure is a quite exhaustive list of JNI function pointers.
jobject: the "this" pointer from the Java world
However, if the native method needs input parameters more arguments will be present. An example of such a method is:
In Java:
public native GpioChip getChip(int fd, String name, GpioLine line);
Generated prototype:
JNIEXPORT jobject JNICALL Java_com_jni_example_TemperatureSampler_getChip(JNIEnv *, jobject, jint, jstring, jobject);
Note that we have two jobject arguments here: one for the "this" pointer (is always the first jobject in the list) and another one for the type GpioLine which is the last parameter of the arguments list for this example method.
Signature:
(ILjava/lang/String;Lcom/jni/example/GpioLine;)Lcom/jni/example/GpioChip;
Indeed, the first non-standard parameter (third one) is an integer, represented by I. Next non-standard parameter is a String, represented by Ljava/loang/String;. Finally, the last parameter is a GpioLine object, represented by Lcom/jni/example/GpioLine;.
An object of type GpioChip is returned, so the parameter on the right of the closing parenthesis is Lcom/jni/example/GpioChip;.
See the section Java VM Type Signatures below for a more detailed explanation of the above.
Coming back to our generated header file, we have the following two methods to be implemented in a C++ file:
JNIEXPORT jfloat JNICALL Java_com_jni_example_TemperatureSampler_getTemperature
(JNIEnv *, jobject);
JNIEXPORT jobject JNICALL Java_com_jni_example_TemperatureSampler_getDetailedTemperature
(JNIEnv *, jobject);
As you can see, the first method simply returns a float, represented by the JNI datatype jfloat.
The second method, however, has to return something of type TemperatureData (see main Java implementation file above):
TemperatureData temperatureData = temperatureSampler.getDetailedTemperature();
Therefore, since JNI doesn't know any specific Java objects, it returns the general jobject type that has to be interpreted correctly by the C/C++ implementation.
Finally, the C++ file! The content of this file is given below:
#include <jni.h>
#include <iostream>
#include <ctime>
#include "com_jni_example_TemperatureSampler.h"
JNIEXPORT jfloat JNICALL Java_com_jni_example_TemperatureSampler_getTemperature
(JNIEnv * env, jobject thisObject)
{
float simpleTemp = 27.8;
std::cout << "\n-----------\nEntering getTemperature() method..." << std::endl;
std::cout << "Returning simple temperature [" << simpleTemp << "] from getTemperature()..." << std::endl;
return simpleTemp;
}
JNIEXPORT jobject JNICALL Java_com_jni_example_TemperatureSampler_getDetailedTemperature
(JNIEnv * env, jobject thisObject)
{
std::cout << "\n-----------\nEntering getDetailedTemperature() method..." << std::endl;
jclass temperatureDataClass = env->FindClass("com/jni/example/TemperatureData");
jobject temperatureData = env->AllocObject(temperatureDataClass);
jclass temperatureScaleClass = env->FindClass("com/jni/example/TemperatureScale");
jfieldID timestamp = env->GetFieldID(temperatureDataClass, "timestamp", "Ljava/lang/String;");
jfieldID temperature = env->GetFieldID(temperatureDataClass, "temperature", "F");
jfieldID scale = env->GetFieldID(temperatureDataClass, "scale", "Lcom/jni/example/TemperatureScale;");
jfieldID scaleEnumID = env->GetStaticFieldID(temperatureScaleClass, "CELCIUS", "Lcom/jni/example/TemperatureScale;");
jobject celciusScale = env->GetStaticObjectField(temperatureScaleClass, scaleEnumID);
jclass callerClass = env->GetObjectClass(thisObject);
jmethodID preferredScaleMethodID = env->GetMethodID(callerClass, "getPreferredScale", "()Lcom/jni/example/TemperatureScale;");
jobject preferredScale = env->CallObjectMethod(thisObject, preferredScaleMethodID);
if (!env->IsSameObject(preferredScale, celciusScale)) {
std::cout << "Preferred scale is not supported, using CELCIUS instead!" << std::endl;
}
time_t now = time(0);
char* dt = ctime(&now);
//std::cout << "The local date and time is: " << dt << std::endl;
env->SetObjectField(temperatureData, timestamp, env->NewStringUTF(dt));
env->SetFloatField(temperatureData, temperature, 36.15);
env->SetObjectField(temperatureData, scale, celciusScale);
std::cout << "Returning detailed temperature from getDetailedTemperature()..." << std::endl;
return temperatureData;
}
The implementation of the first method, Java_com_jni_example_TemperatureSampler_getTemperature, is quite straightforward. We simply return a float number and that's it.
The second method, however, has a much bigger complexity. This method has to return a jobject of type TemperatureData and therefore many more steps are needed. We'll go over them one by one.
First, we need to find the class TemperatureData. this is done through Java reflection using one of the functions from the JNIEnv structure called FindClass(). This returns an object of type jclass and is assigned to the variable temperatureDataClass of type jclass.
Note that the parameter passed to the function, "com/jni/example/TemperatureData" (= the package name + the Java class name), must not start with a slash /!!!
Next, we create a jobject variable called temperatureData that is a reference to the TemperatureData class. This is done through the AllocObject() function from the JNIEnv structure.
We now can indirectly refer to the fields of the class TemperatureData, which are again:
public String timestamp
public float temperature
public TemperatureScale scale
Next, we create a new jclass variable called temperatureScaleClass, this time referring to the Java class TemperatureScale. Same mechanism as for the other Java class is applied.
So now we have two jclass variables that are referring to our two Java classes TemperatureData.java and TemperatureScale.java.
Next we want to create 3 "field" variables that will be linked to the 3 public variables in the TemperatureData class. Those variables are of type jfieldID:
timestamp
temperature
scale
The variables are linked to the Java variables using the JNIEnv function GetFieldID().
Important here are the following 2 things:
The second parameter for the function GetFieldID(), a String, must exactly match the name of the public field in the corresponding Java file (here: TemperatureData.java)
The third parameter must mention the datatype.
Here, the first variable of the Java class TemperatureData.java is of type string, so "Ljava/lang/String;" must be given as parameter (mind the semicolumn!!!)
The second variable of the Java class TemperatureData.java is of type float, so "F" must be given as parameter
The third variable of the Java class TemperatureData.java is of type TemperatureScale, so "Lcom/jni/example/TemperatureScale;" must be given as parameter. The mechanism is the same as for String, but now we have to use the package name followed by the Java class.
See again the below section Java VM Type Signatures for an overview of the possible signatures that can be returned.
Next, we need a jfieldID variable scaleEnumID for the scaler enum items located in TemperatureScale.java. This is done with the JNIEnv function GetStaticFieldID().
Here, we assign the enum constant CELCIUS to scaleEnumID. That will be used later on in the code to do a comparison with the default temperature scale defined in TemperatureSampler.java.
Note that this has to be treated as a static item (not sure why, though...), hence GetStaticFieldID().
Next we need a jobject variable to the enumeration class itself called celciusScale. This is done through the JNIEnv function GetStaticObjectField(). Again, a static method.
Once more we need a jclass variable called callerClass, this time referring to the calling object. This is done through the JNIEnv function GetObjectClass(). We will use this variable shortly to compare things locally with things which are part of the calling object.
Since we need to call a method from the caller class we need yet another variable, this time of type jmethodID, named preferredScaleMethodID. This will be connected to the public method getPreferredScale() from the TemperatureSampler.java class using the JNIEnv function GetMethodID(). Since this method is defined as public TemperatureScale getPreferredScale(), the third parameter of that call must be a type signature like so:
"()Lcom/jni/example/TemperatureScale;"
Indeed, we don't have any input parameters, hence (). But we do return an object of type TemperatureScale, so the type signature after the closing parenthesis has to be Lcom/jni/example/TemperatureScale;.
Once you get a bit acquainted with the JNI mechanism, all looks "logic"... Although....
Finally, we need yet another jobject variable named preferredScale. This variable is linked to the preferredScaleMethodID through the JNIEnv function CallObjectMethod().
We have all the info we need to start filling in variables with the values we want.
First, however, we check if the preferred temperature scale, coming from the Java world through the method getPreferredScale() is the same as the one we "think" it is in our C++ file, namely CELCIUS (see TemperatureSampler.cpp file, jfieldID scaleEnumID).
If the comparison fails we print out a message to the console.
Next, we fill the 3 variables from the TemperatureData class through the JNIEnv function SetObjectField(). Each time, we pass 3 paramaters:
The local jclass variable, linked to the TemperatureData Java class
The local jfieldID (timestamp, temperature or scale)
The value we want to put into the fields of the TemperatureData Java class that correspond to the 3 jfieldID parameters given before
This way, we fill in the properties of the TemperatureData object.
And finally, finally... We can return an "object" which contains a TemperatureData object.
This ends the explanation of the implementation of the native Java method getDetailedTemperature() in the C++ world.
To quote Michel van Biezen (a great guy on YouTube who knows everything about... well... everything!): "and that's... how it's done".
This is how JNI works to communication between Java and another native implementation (here: C++).
Below is the structure that contains a list of all JNI functions:
const struct JNINativeInterface ... = {
NULL,
NULL,
NULL,
NULL,
GetVersion,
DefineClass,
FindClass,
FromReflectedMethod,
FromReflectedField,
ToReflectedMethod,
GetSuperclass,
IsAssignableFrom,
ToReflectedField,
Throw,
ThrowNew,
ExceptionOccurred,
ExceptionDescribe,
ExceptionClear,
FatalError,
PushLocalFrame,
PopLocalFrame,
NewGlobalRef,
DeleteGlobalRef,
DeleteLocalRef,
IsSameObject,
NewLocalRef,
EnsureLocalCapacity,
AllocObject,
NewObject,
NewObjectV,
NewObjectA,
GetObjectClass,
IsInstanceOf,
GetMethodID,
CallObjectMethod,
CallObjectMethodV,
CallObjectMethodA,
CallBooleanMethod,
CallBooleanMethodV,
CallBooleanMethodA,
CallByteMethod,
CallByteMethodV,
CallByteMethodA,
CallCharMethod,
CallCharMethodV,
CallCharMethodA,
CallShortMethod,
CallShortMethodV,
CallShortMethodA,
CallIntMethod,
CallIntMethodV,
CallIntMethodA,
CallLongMethod,
CallLongMethodV,
CallLongMethodA,
CallFloatMethod,
CallFloatMethodV,
CallFloatMethodA,
CallDoubleMethod,
CallDoubleMethodV,
CallDoubleMethodA,
CallVoidMethod,
CallVoidMethodV,
CallVoidMethodA,
CallNonvirtualObjectMethod,
CallNonvirtualObjectMethodV,
CallNonvirtualObjectMethodA,
CallNonvirtualBooleanMethod,
CallNonvirtualBooleanMethodV,
CallNonvirtualBooleanMethodA,
CallNonvirtualByteMethod,
CallNonvirtualByteMethodV,
CallNonvirtualByteMethodA,
CallNonvirtualCharMethod,
CallNonvirtualCharMethodV,
CallNonvirtualCharMethodA,
CallNonvirtualShortMethod,
CallNonvirtualShortMethodV,
CallNonvirtualShortMethodA,
CallNonvirtualIntMethod,
CallNonvirtualIntMethodV,
CallNonvirtualIntMethodA,
CallNonvirtualLongMethod,
CallNonvirtualLongMethodV,
CallNonvirtualLongMethodA,
CallNonvirtualFloatMethod,
CallNonvirtualFloatMethodV,
CallNonvirtualFloatMethodA,
CallNonvirtualDoubleMethod,
CallNonvirtualDoubleMethodV,
CallNonvirtualDoubleMethodA,
CallNonvirtualVoidMethod,
CallNonvirtualVoidMethodV,
CallNonvirtualVoidMethodA,
GetFieldID,
GetObjectField,
GetBooleanField,
GetByteField,
GetCharField,
GetShortField,
GetIntField,
GetLongField,
GetFloatField,
GetDoubleField,
SetObjectField,
SetBooleanField,
SetByteField,
SetCharField,
SetShortField,
SetIntField,
SetLongField,
SetFloatField,
SetDoubleField,
GetStaticMethodID,
CallStaticObjectMethod,
CallStaticObjectMethodV,
CallStaticObjectMethodA,
CallStaticBooleanMethod,
CallStaticBooleanMethodV,
CallStaticBooleanMethodA,
CallStaticByteMethod,
CallStaticByteMethodV,
CallStaticByteMethodA,
CallStaticCharMethod,
CallStaticCharMethodV,
CallStaticCharMethodA,
CallStaticShortMethod,
CallStaticShortMethodV,
CallStaticShortMethodA,
CallStaticIntMethod,
CallStaticIntMethodV,
CallStaticIntMethodA,
CallStaticLongMethod,
CallStaticLongMethodV,
CallStaticLongMethodA,
CallStaticFloatMethod,
CallStaticFloatMethodV,
CallStaticFloatMethodA,
CallStaticDoubleMethod,
CallStaticDoubleMethodV,
CallStaticDoubleMethodA,
CallStaticVoidMethod,
CallStaticVoidMethodV,
CallStaticVoidMethodA,
GetStaticFieldID,
GetStaticObjectField,
GetStaticBooleanField,
GetStaticByteField,
GetStaticCharField,
GetStaticShortField,
GetStaticIntField,
GetStaticLongField,
GetStaticFloatField,
GetStaticDoubleField,
SetStaticObjectField,
SetStaticBooleanField,
SetStaticByteField,
SetStaticCharField,
SetStaticShortField,
SetStaticIntField,
SetStaticLongField,
SetStaticFloatField,
SetStaticDoubleField,
NewString,
GetStringLength,
GetStringChars,
ReleaseStringChars,
NewStringUTF,
GetStringUTFLength,
GetStringUTFChars,
ReleaseStringUTFChars,
GetArrayLength,
NewObjectArray,
GetObjectArrayElement,
SetObjectArrayElement,
NewBooleanArray,
NewByteArray,
NewCharArray,
NewShortArray,
NewIntArray,
NewLongArray,
NewFloatArray,
NewDoubleArray,
GetBooleanArrayElements,
GetByteArrayElements,
GetCharArrayElements,
GetShortArrayElements,
GetIntArrayElements,
GetLongArrayElements,
GetFloatArrayElements,
GetDoubleArrayElements,
ReleaseBooleanArrayElements,
ReleaseByteArrayElements,
ReleaseCharArrayElements,
ReleaseShortArrayElements,
ReleaseIntArrayElements,
ReleaseLongArrayElements,
ReleaseFloatArrayElements,
ReleaseDoubleArrayElements,
GetBooleanArrayRegion,
GetByteArrayRegion,
GetCharArrayRegion,
GetShortArrayRegion,
GetIntArrayRegion,
GetLongArrayRegion,
GetFloatArrayRegion,
GetDoubleArrayRegion,
SetBooleanArrayRegion,
SetByteArrayRegion,
SetCharArrayRegion,
SetShortArrayRegion,
SetIntArrayRegion,
SetLongArrayRegion,
SetFloatArrayRegion,
SetDoubleArrayRegion,
RegisterNatives,
UnregisterNatives,
MonitorEnter,
MonitorExit,
GetJavaVM,
GetStringRegion,
GetStringUTFRegion,
GetPrimitiveArrayCritical,
ReleasePrimitiveArrayCritical,
GetStringCritical,
ReleaseStringCritical,
NewWeakGlobalRef,
DeleteWeakGlobalRef,
ExceptionCheck,
NewDirectByteBuffer,
GetDirectBufferAddress,
GetDirectBufferCapacity,
GetObjectRefType
};
Type Signature Java Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
Lfully-qualified-class; fully-qualified-class
[type type[]
(arg-types)ret-type method type
Example:
Java method:
long f (int n, String s, int[] arr);
has the following type signature
(ILjava/lang/String;[I)J
Explanation:
Everything between the parenthesis are input parameters, the one next to the right parenthesis is the return type. If void is returned, this parameter is V.
If no input parameters are given the two parenthesis must still be written: ()
I represents int n
Ljava/lang/String; represents String s (mind the semicolumn!!!)
[I represents int[] arr
J represents long (the return value of the method)
Example for a method without input parameters and returning void:
Java method:
void getTest();
has the following signature
()V
Enum values are public static fields of the Enum class, so you can use this official manual to write the code. Just get the field from JNI and return it as jobject.
Here's the JNI implementation that should work with the example above:
jclass clSTATUS = env->FindClass("MyClass$STATUS");
jfieldID fidONE = env->GetStaticFieldID(clSTATUS , "ONE", "LMyClass$STATUS;");
jobject STATUS_ONE = env->GetStaticObjectField(clSTATUS, fidONE);
return STATUS_ONE;