package com.marakana.jniexamples; public class Hello { public static native void sayHi(String who, int times); // static { System.loadLibrary("hello"); // } public static void main(String[] args) { sayHi(args[0], Integer.parseInt(args[1])); // } }
티스토리 뷰
JNI Overview
In this module, we’ll explore the following topics:
JNI Components
JNI By Example
Native Method Arguments
Primitive Type Mapping
Reference Type Mapping
Global and Local References
Using Strings
Arrays
Reflection
Registering Native Methods On Load
Exceptions
JNI: What it is and why you’d care
JNI is an interface that allows Java to interact with code written in another language
Motivation for JNI:
- Access low-level OS-specific features (e.g. ioctl, poll, etc.)
- Reuse existing/legacy code written in C/C++ from Java
- Improve the performance by writing CPU-bound code natively - but modern just-in-time (JIT) compilers make this a moot point
JNI code is not portable! |
JNI can also be used to invoke Java code from within natively-written applications - such as those written in C/C++. In fact, the java command-line utility is an example of one such application, that launches Java code in a Java Virtual Machine. |
JNI Components
javah - JDK tool that builds C-style header files from a given Java class that includes native methods
Adapts Java method signatures to native function prototypes
jni.h - C/C+ header file included with the JDK that maps Java types to their native counterparts
javah automatically includes this file in the application header files
JNI By Example
We start by creating a Java class with one or more native methods
src/com/marakana/jniexamples/Hello.java:The method sayHi(String, int) will be implemented in C/C+ in separate files, which will be compiled into a shared library. Load the shared library by its logical name. The actual name is system-dependent: libhello.so (on Linux/Unix), hello.dll (on Windows), and libhello.jnilib (Mac OSX). Here we call our native method as a regular Java method. Compile the Java code
$ mkdir -p bin $ javac -d bin/ src/com/marakana/jniexamples/Hello.java
Using the javah tool, we generate the C header file from the compiledcom.marakana.jniexamples.Hello class:
$ mkdir -p jni $ javah -jni -classpath bin -d jni com.marakana.jniexamples.Hello
Observe the generated C header file:
jni/com_marakana_jniexamples_Hello.h:/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_marakana_jniexamples_Hello */ #ifndef _Included_com_marakana_jniexamples_Hello #define _Included_com_marakana_jniexamples_Hello #ifdef __cplusplus extern "C" { #endif /* * Class: com_marakana_jniexamples_Hello * Method: sayHi * Signature: (Ljava/lang/String;I)V */ JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi (JNIEnv *, jclass, jstring, jint); #ifdef __cplusplus } #endif #endif
Method names resolve to C functions based on a pre-defined naming strategy: the prefix Java_, followed by a mangled fully-qualified class name, followed by an underscore ("_") separator, followed by a mangled method name. For overloaded native methods, two underscores ("__") followed by the mangled argument signature. Provide the C implementation:
jni/com_marakana_jniexamples_Hello.c:#include "com_marakana_jniexamples_Hello.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_Hello_sayHi (JNIEnv *env, jclass clazz, jstring who, jint times) { const char *name = (*env)->GetStringUTFChars(env, who, NULL); if (name != NULL) { jint i; for (i = 0; i < times; i++) { printf("Hello %s\n", name); } (*env)->ReleaseStringUTFChars(env, who, name); } }
Most of the time, we cannot just use Java data types directly in C. For example, we have to convertjava.lang.String to char * before we can effectively use it in C. This code assumes: #define NULL ((void *) 0) Compile the shared library
$ mkdir -p libs $ gcc -o libs/libhello.jnilib -lc -shared \ -I/System/Library/Frameworks/JavaVM.framework/Headers \ jni/com_marakana_jniexamples_Hello.c $ file libs/libhello.jnilib libs/libhello.jnilib: Mach-O 64-bit dynamically linked shared library x86_64
On Unix/Linux, compile as:
gcc -o libs/libhello.so -lc -shared -fPIC -I$JAVA_HOME/include jni/com_marakana_jniexamples_Hello.cRun our code
$ java -Djava.library.path=libs -classpath bin com.marakana.jniexamples.Hello Student 5 Hello Student Hello Student Hello Student Hello Student Hello Student
Instead of specifying -Djava.library.path=libs, we could have preceded our javacommand with export LD_LIBRARY_PATH=libs. Common mistakes resulting in java.lang.UnsatisfiedLinkError usually come from incorrect naming of the shared library (O/S-dependent), the library not being in the search path, or wrong library being loaded by Java code.
Native Method Arguments
A C/C++ implementation of a native Java method accepts the following function arguments:
JNIEnv *env, an interface pointer (pointer to a pointer) to a function table, where each entry in the table is a pointer to a function that enables Java-to-C/C++ integration (e.g. type conversion)
The second argument varies depending on whether the native method is a static method or an instance (i.e. non-static) method:
In case of instance methods, the second argument is a jobject obj, which is a reference to the object on which the method is invoked
In case of static methods, the second is a jclass clazz, which is a reference to the class in which the method is defined
The remaining arguments correspond to regular Java method arguments (subject to the mapping described below)
The native function can pass its result back to Java via the return value (or return void)
Primitive Type Mapping
Java primitives have well-known size and are signed by default
C/C++ primitives vary in size, depending on the platform (e.g. the size of the long depends on the size of the word)
To provide inter-operability with C/C++, jni.h defines the following mappings:
Java Language Type Native Type Description typedef (C99) typedef(otherwise) boolean
jboolean
unsigned 8 bits
uint8_t
unsigned char
byte
jbyte
signed 8 bits
int8_t
signed char
char
jchar
unsigned 16 bits
uint16_t
unsigned short
short
jshort
signed 16 bits
int16_t
short
int
jint
signed 32 bits
int32_t
int
long
jlong
signed 64 bits
int64_t
long long
float
jfloat
32-bit IEEE 754
float
float
double
jdouble
64-bit IEEE 754
double
double
void
void
N/A
N/A
N/A
N/A
jsize
used to describe cardinal indices and sizes
jint
jint
Because booleans are treated as unsigned bytes, the following definitions are also useful
Java Boolean Value Native Boolean Type Definition false
JNI_FALSE
#define JNI_FALSE 0
true
JNI_TRUE
#define JNI_TRUE 1
Primitive data types are always copied between Java and native code
Reference Type Mapping
The JNI includes a number of pre-defined reference types (passed as opaque references in C) that correspond to different kinds of Java object types:
Java Language Type Native Type typedef in C java.lang.Object
jobject
void*
java.lang.Class
jclass
jobject
java.lang.Throwable
jthrowable
jobject
java.lang.String
jstring
jobject
java.lang.ref.WeakReference
jweak
jobject
N/A
jarray
jobject
java.lang.Object[]
jobjectArray
jarray
boolean[]
jbooleanArray
jarray
byte[]
jbyteArray
jarray
char[]
jcharArray
jarray
short[]
jshortArray
jarray
int[]
jintArray
jarray
long[]
jlongArray
jarray
float[]
jfloatArray
jarray
double[]
jdoubleArray
jarray
A note about reference types in C++These reference types in C++ are defined as proper classes:
class _jobject {}; class _jclass : public _jobject {}; class _jstring : public _jobject {}; class _jarray : public _jobject {}; class _jobjectArray : public _jarray {}; class _jbooleanArray : public _jarray {}; class _jbyteArray : public _jarray {}; class _jcharArray : public _jarray {}; class _jshortArray : public _jarray {}; class _jintArray : public _jarray {}; class _jlongArray : public _jarray {}; class _jfloatArray : public _jarray {}; class _jdoubleArray : public _jarray {}; class _jthrowable : public _jobject {}; typedef _jobject* jobject; typedef _jclass* jclass; typedef _jstring* jstring; typedef _jarray* jarray; typedef _jobjectArray* jobjectArray; typedef _jbooleanArray* jbooleanArray; typedef _jbyteArray* jbyteArray; typedef _jcharArray* jcharArray; typedef _jshortArray* jshortArray; typedef _jintArray* jintArray; typedef _jlongArray* jlongArray; typedef _jfloatArray* jfloatArray; typedef _jdoubleArray* jdoubleArray; typedef _jthrowable* jthrowable; typedef _jobject* jweak;
A tip about NULL in Android NDKAndroid’s native development kit (NDK) does not define NULL in its jni.h, so the following definition can be useful when working with pointers and reference types:
#define NULL ((void *) 0)
Opaque references are C pointer types that refer to internal data structures in the JVM. It is an error to try to dereference opaque references and try to use them directly.
Global and Local References
Unlike primitives, arbitrary Java objects are always passed by reference
The VM must keep track of all objects that have been passed to the native code, so that these objects are not freed by the GC (i.e. these references are considered object roots while in use)
Consequently, the native code must have a way to tell the VM that it no longer needs references to objects
Additionally, the GC must be able to relocate an object referred to by native code (in the case of a copying GC), which means that these references must not be dereferenced by the native code
We will access the fields/methods on these references via JNI accessor functions
All objects passed to native code and returned from JNI functions are considered local references
Automatically "freed" after the native function call returns
The VM creates a table of references before every function call, and frees it when the call completes
Consequently, the native function calls are more expensive than regular method calls
Also possible to explicitly free using:
void DeleteLocalRef(JNIEnv *env, jobject localRef);
In practice, we only really want to use DeleteLocalRef(JNIEnv *, jobject) if we know we are not going to need the reference in the rest of the function body, say before we start executing code that may take a long time. This allows the referenced memory to be freed by GC, assuming nobody else is using it. Only valid in the thread in which they are created
If we end up using more than 16 local references (default) within a native function scope, we can ask the VM for more:
jint EnsureLocalCapacity(JNIEnv *env, jint capacity); jint PushLocalFrame(JNIEnv *env, jint capacity); jobject PopLocalFrame(JNIEnv *env, jobject result);
To hold on to an object beyond a single native function call, we can convert a local reference to a global reference
jobject NewGlobalRef(JNIEnv *env, jobject obj);
Now we could save the result of NewGlobalRef(JNIEnv *, jobect) to a global/static variable and use it later, in another funciton call. Becomes our responsibility to explicitly delete the reference when no longer in use:
void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
Also supported are the weak global references
jweak NewWeakGlobalRef(JNIEnv *env, jobject obj); void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);
JNI accessor functions do not make a distinction between the different reference types
Using Strings
In C, a string is a pointer to a \0-terminated character array where each character is one byte
In Java, an instance of java.lang.String is an immutable object which wraps a character array, which itself is an object (i.e. it knows its length so it is not \0-terminated), and each character is represented in UTF16 as two bytes
String Operations are provided by JNIEnv:
/* On Unicode (UTF-16) Characters */ jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize len); jsize GetStringLength(JNIEnv *env, jstring string); const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy); void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars); void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf); /* On (modified) UTF-8 Characters */ jstring NewStringUTF(JNIEnv *env, const char *bytes); jsize GetStringUTFLength(JNIEnv *env, jstring string); const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf); void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
A note about GetString[UTF]Chars(…) functionsThe pointer resulting from GetString[UTF]Chars(…) is valid until ReleaseString[UTF]Chars(…) is called.
If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy is made. When *isCopy == JNI_FALSE, the returned string is a direct pointer to the characters in the original java.lang.String instance, which is then pinned in memory. The native code must ensure not to modify the contents of the returned string, otherwise, it would be modifying the private data of the immutable java.lang.String object!
Regardless of isCopy, we have to call ReleaseString[UTF]Chars(…) when we are done using the character array, either to free the memory (when *isCopy == JNI_TRUE) or to un-pin the original string in memory (when*isCopy == JNI_FALSE).
A note about modified UTF-8 strings:JNI’s UTF string functions (that work with char *) return/assume \0-terminated character arrays that are encoded as UTF-8 character sequences, except that if the string contains a \u0000 character, it is represented by a pair of bytes 0xc0 0x80 (1100000010000000) instead of 0x00. When working with regular ASCII characters, each character is represented by exactly one byte.
Examples:
What NOT to do:
#include <stdio.h> #include "com_marakana_jniexamples_HelloName.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name) { printf("Hello %s", name); /* */ }
This example would not work (would likely crash the VM) since the jstring type represents strings in the Java virtual machine. This is different from the C string type (char *). Convert Java string to C string:
#include <stdio.h> #include "com_marakana_jniexamples_HelloName.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name){ const char *cName = (*env)->GetStringUTFChars(env, name, NULL); /* */ if (cName == NULL) { return; /* OutOfMemoryError already thrown */ } else { printf("Hello %s\n", cName); (*env)->ReleaseStringUTFChars(env, name, cName); /* */ } }
This returns a pointer to an array of bytes representing the string in modified UTF-8 encoding; or NULL if we ran out of memory, in which case java.lang.OutOfMemoryError would also be thrown (upon returning back to the Java runtime). Calling ReleaseStringUTFChars indicates that the native method no longer needs the UTF-8 string returned by GetStringUTFChars, thus the memory taken by the UTF-8 string can be freed. Failure to do this would result in a memory leak, which could ultimately lead to memory exhaustion. Convert C string to Java string:
#include <stdio.h> #include "com_marakana_jniexamples_GetName.h" JNIEXPORT jstring JNICALL Java_com_marakana_jniexamples_ReturnName_GetName(JNIEnv *env, jclass class) { char buf[20]; fgets(buf, sizeof(buf), stdin); return (*env)->NewStringUTF(env, buf); }
Print each character of a Java string
#include <stdio.h> #include "com_marakana_jniexamples_HelloName.h" JNIEXPORT void JNICALL Java_com_marakana_jniexamples_HelloName_sayHelloName(JNIEnv *env, jclass clazz, jstring name) { char buf[4]; jint i; jsize len = (*env)->GetStringUTFLength(env, name); fputs("Hello ", stdout); for (i = 0; i < len; i++) { (*env)->GetStringUTFRegion(env, name, i, 1, buf); putc(buf[0], stdout); /* assumes ASCII */ } putc('\n', stdout); }
Arrays
As with Strings, Java arrays are different than C arrays; the former are objects (which know their length) whereas the latter are just pointers to memory addresses
So, int[] in Java is not the same as jint[] in C/C++
Instead, int[] in Java, becomes jintArray in C/C++
JNI provides functions for
Creating arrays:
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); jbooleanArray NewBooleanArray(JNIEnv *env, jsize length); jbyteArray NewByteArray(JNIEnv *env, jsize length); jcharArray NewCharArray(JNIEnv *env, jsize length); jshortArray NewShortArray(JNIEnv *env, jsize length); jintArray NewIntArray(JNIEnv *env, jsize length); jlongArray NewLongArray(JNIEnv *env, jsize length); jfloatArray NewFloatArray(JNIEnv *env, jsize length); jdoubleArray NewDoubleArray(JNIEnv *env, jsize length);
Getting the length of an array
jsize GetArrayLength(JNIEnv *env, jarray array);
Getting/Setting individual elements of object arrays
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
Converting Java arrays of primitives to C/C++ arrays:
jboolean* GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy); jbyte* GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy); jchar* GetCharArrayElements(JNIEnv *env, jcharArray array, jboolean *isCopy); jshort* GetShortArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy); jint* GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy); jlong* GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean *isCopy); jfloat* GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy); jdouble* GetDoubleArrayElements(JNIEnv *env, jdoubleArray array, jboolean *isCopy); void ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode); void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode); void ReleaseCharArrayElements(JNIEnv *env, jcharArray array, jchar *elems, jint mode); void ReleaseShortArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode); void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode); void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode); void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode); void ReleaseDoubleArrayElements(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode);
Copying Java primitive arrays to and from C/C++ arrays:
void GetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize len, jboolean *buf); void GetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); void GetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); void GetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); void GetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); void GetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); void GetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf); void SetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize len, jboolean *buf); void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf); void SetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf); void SetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf); void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf); void SetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf); void SetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf); void SetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf);
For example, to sum the contents of a Java int array in C, we could:
JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_Math_sum(JNIEnv *env, jclass clazz, jintArray array) { jint *cArray = (*env)->GetIntArrayElements(env, array, NULL); if (cArray == NULL) { return 0; } else { jini len = (*env)->GetArrayLength(env, array); jint i; jint result = 0; for (i = 0; i < len; i++) { result += cArray[i]; } (*env)->ReleaseIntArrayElements(env, array, cArray, JNI_ABORT); return result; } }
Alternatively, we could also extract the individual elements:
JNIEXPORT jint JNICALL Java_com_marakana_jniexamples_Math_sum(JNIEnv *env, jclass clazz, jintArray array) { jint buf[1]; jini len = (*env)->GetArrayLength(env, array); jint i; jint result = 0; for (i = 0; i < len; i++) { (*env)->GetIntArrayRegion(env, array, i, 1, buf); result += buf[0]; } return result; }
To do the opposite, copy a C array to Java array, we would do:
JNIEXPORT jintArray JNICALL Java_com_marakana_jniexamples_Foo_getData(JNIEnv *env, jclass class) { jint cArray[10]; jsize len = sizeof(cArray); jintArray jArray = (*env)->NewIntArray(env, len); if (jArray != NULL) { jint i; /* "get" the data */ for (i = 0; i < len; i++) { cArray[i] = i; } (*env)->SetIntArrayRegion(env, jArray, 0, len, cArray); } return jArray; }
To avoid any chance of memory copying, we could also use direct memory buffers (java.nio.ByteBuffer):
jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity); void* GetDirectBufferAddress(JNIEnv* env, jobject buf); jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf);
For example
In Javapublic class Foo { public static void main(String[] args) { … ByteBuffer buf = ByteBuffer.allocateDirect(1024); // populate buf processData(buf); … } public native static void processData(ByteBuffer buf); }
In CJNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processData(JNIEnv *env, jclass clazz, jobject buf) { char *cBuf = (*env)->GetDirectBufferAddress(env, buf); /* process cBuf from 0 to (*env)->GetDirectBufferCapacity(env, buf) */ }
A note about memory The pointer resulting from GetTypeArrayElements(…) is valid untilReleaseTypeArrayElements(…) is called (unless mode == JNI_COMMIT). If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy is made. When *isCopy == JNI_FALSE, the returned array is a direct pointer to the elements of the Java array, which is then pinned in memory. Regardless of isCopy, we have to call ReleaseTypeArrayElements(…, int mode) when we are done using the native array, either to un-pin the Java array in memory when *isCopy == JNI_FALSE, or, when *isCopy == JNI_TRUE, to:
JNI also supports a critical version of these functions: void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy); void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode); The function GetPrimitiveArrayCritical(…) is similar to GetTypeArrayElements(…), except that the VM is more likely to return the pointer to the primitive array (i.e. *isCopy == JNI_FALSE is more likely). However, this comes with some caveats. The native code betweenGetPrimitiveArrayCritical(…) and ReleasePrimitiveArrayCritical(…) must not call other JNI functions, or make any system calls that may cause the current thread to block and wait for another Java thread. This is because the VM may temporarily suspend the garbage collection - in case it does not support memory pinning. |
Reflection
JNI supports getting a reference to a Java class in native code:
jclass FindClass(JNIEnv *env, const char *name);
The name argument is a fully-qualified classname (with / as the package separators)
/* load the java.lang.String class */ (*env)->FindClass(env, "java/lang/String");
For arrays, we use [Lclassname; format
/* load the java.lang.String[] class */ (*env)->FindClass(env, "[Ljava/lang/String;");
Given a reference to a class, we can find out its super class, as well as whether it is assignable from another class:
jclass GetSuperclass(JNIEnv *env, jclass clazz); jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
Given a reference to an object, we can find out its class, as well as whether it is an instance of another class:
jclass GetObjectClass(JNIEnv *env, jobject obj); jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz);
Once we have a class, JNI offers functions to lookup, get/set fields and invoke methods of that class - based on the following field and method signatures:
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
For fields, JNI offers functions to:
Lookup a field ID based on its name/signature:
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Get/set the value of a field based on the field ID:
jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID); jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID); jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID); jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID); jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID); jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID); jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID); jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID); jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID); void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject); void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean); void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte); void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar); void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort); void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint); void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong); void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat); void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble); jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID); jchar (*GetCharField)(JNIEnv*, jobject, jfieldID); jshort (*GetShortField)(JNIEnv*, jobject, jfieldID); jint (*GetIntField)(JNIEnv*, jobject, jfieldID); jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID); jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID); void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject); void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte); void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar); void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort); void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint); void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat); void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble);
For example:
In Javapublic class Foo { private String bar; … public native void processBar(); }
In CJNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processBar(JNIEnv *env, jobject object) { /* Same as object.getClass() */ jclass clazz = (*env)->GetObjectClass(env, object); if (clazz != NULL) { /* cannot be null in this case */ /* Same as clazz.getField("bar") */ jfieldID field = (*env)->GetFieldID(env, clazz, "bar", "Ljava/lang/String;"); if (field != NULL) { /* make sure we got the field */ /* Same as field.get(object) */ jstring jString = (*env)->GetObjectField(env, object, field); if (jString != NULL) { /* Convert the value to a C (UTF-8) string */ const char *cString = (*env)->GetStringUTFChars(env, jString, NULL); if (cString == NULL) { return; /* Out of memory */ } printf("Value of \"bar\" before the change: \"%s\"\n", cString); (*env)->ReleaseStringUTFChars(env, jString, cString); } /* Create a new String */ jString = (*env)->NewStringUTF(env, "Bar2"); if (jString != NULL) { /* make sure we are not out of memory */ /* Same as field.set(object, jString) */ (*env)->SetObjectField(env, object, field, jString); } } } }
For methods, JNI offers functions to:
Lookup a method ID based on its name and signature:
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
Invoke a method based on the method ID:
jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
JNI also offers Call…(…) functions that take args in a form of va_list as well as an array of jvalue-s:
<Type> (*Call<Type>MethodV)(JNIEnv*, jobject, jmethodID, va_list); <Type> (*CallStatic<Type>MethodV)(JNIEnv*, jobject, jmethodID, va_list); <Type> (*Call<Type>MethodA)(JNIEnv*, jobject, jmethodID, jvalue*); <Type> (*CallStatic<Type>MethodA)(JNIEnv*, jobject, jmethodID, jvalue*);
For example:
In Javapublic class Foo { private String bar; public void setBar(String bar) { this.bar = bar; } … public native void processBar(); }
In CJNIEXPORT void JNICALL Java_com_marakana_jniexamples_Foo_processBar(JNIEnv *env, jobject object) { /* Same as object.getClass() */ jclass clazz = (*env)->GetObjectClass(env, object); if (clazz != NULL) { /* Same as clazz.getMethod("setBar", String.class) - assuming non-static */ jmethodID method = (*env)->GetMethodID(env, clazz, "setBar", "(Ljava/lang/String;)V"); if (method != NULL) { /* make sure we found the method */ /* Create a new Java String */ jstring jString = (*env)->NewStringUTF(env, "Bar2"); if (jString != null) { /* Same as method.invoke(object, jString) */ (*env)->CallVoidMethod(env, object, method, jString); } } } }
Registering Native Methods On Load
JNI supports JNI_OnLoad function, which if exported by the library, will automatically be called when the library is loaded by System.loadLibrary(String)
jint JNI_OnLoad(JavaVM *vm, void *reserved);
When used with JNI’s RegisterNatives, we can pre-bind native methods as soon as our library is first loaded, and consequently, we no longer need to use javah:
#include <jni.h> static void sayHi(JNIEnv *env, jclass clazz, jstring who, jint times) { const char *name = (*env)->GetStringUTFChars(env, who, NULL); if (name != NULL) { jint i; for (i = 0; i < times; i++) { printf("Hello %s\n", name); } (*env)->ReleaseStringUTFChars(env, who, name); } } static JNINativeMethod method_table[] = { { "sayHi", "(Ljava/lang/String;I)V", (void *) sayHi } }; static int method_table_size = sizeof(method_table) / sizeof(method_table[0]); jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_6) != JNI_OK) { return JNI_ERR; } else { jclass clazz = (*env)->FindClass(env, "com/marakana/jniexamples/Hello"); if (clazz) { jint ret = (*env)->RegisterNatives(env, clazz, method_table, method_table_size); (*env)->DeleteLocalRef(env, clazz); return ret == 0 ? JNI_VERSION_1_6 : JNI_ERR; } else { return JNI_ERR; } } }
Notice that we JNI function that will be bound to the native Java method is declared as static. It won’t even be exported to the symbol table. It does not need to be because RegisterNatives will bind it via a function pointer to the Java method. JNI supports JNI_OnUnload, but since this is only invoked when the class-loader that loaded the library is GC’ed, it’s not commonly used
Exceptions
JNI permits us to throw exceptions from native code
Assuming we have a Throwable object:
jint Throw(JNIEnv *env, jthrowable obj);
Assuming we have a Throwable class, and that it has a constructor that takes a string message:
jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
For example:
static void ThrowExceptionByClassName(JNIEnv *env, const char *name, const char *message) { jclass clazz = (*env)->FindClass(env, name); if (clazz != NULL) { (*env)->ThrowNew(env, clazz, message); (*env)->DeleteLocalRef(env, clazz); } } … if (invalidArgument == TRUE) { ThrowExceptionByClassName(env, "java/lang/IllegalArgumentException", "This argument is not valid!"); }
Throwing an exception from native code just registers the throwable (as an exception pointer) with the current thread - it does not interrupt the current flow of the code!
The pending exception gets "thrown" upon returning to the Java code
While an exception is pending, we can only call the following JNI functions:
DeleteGlobalRef
DeleteLocalRef
DeleteWeakGlobalRef
ExceptionCheck
ExceptionClear
ExceptionDescribe
ExceptionOccurred
MonitorExit
PopLocalFrame
PushLocalFrame
Release<PrimitiveType>ArrayElements
ReleasePrimitiveArrayCritical
ReleaseStringChars
ReleaseStringCritical
ReleaseStringUTFChars
Calling methods on Java objects (e.g. via CallObjectMethod) can fail with an exception, but since exceptions don’t automatically abort our function calls, we must explicitly check for existence of pending exceptions (e.g. with ExceptionCheck)
Native code can "catch" an exception by calling ExceptionCheck(…) or ExceptionOccurred(…), print its stack trace (to stderr) with ExceptionDescribe(…), and "handle" it (i.e. clear it) withExceptionClear(…)
jthrowable ExceptionOccurred(JNIEnv *env); /* NULL if no exception is currently being thrown */ jboolean ExceptionCheck(JNIEnv *env); void ExceptionDescribe(JNIEnv *env); void ExceptionClear(JNIEnv *env);
To get to the message of a throwable object we would do something like:
… (*env)->CallObjectMethod(env, …); /* this can throw an exception */ if ((*env)->ExceptionCheck(env)) { jthrowable throwable = (*env)->ExceptionOccurred(env); (*env)->ExceptionDescribe(env); /* optionally dump the stack trace */ (*env)->ExceptionClear(env); /* mark the exception as "handled" */ jclazz clazz = (*env)->GetObjectClass(env, throwable); jmethodID getMessageMethod = (*env)->GetMethodID(env, clazz, "getMessage", "()Ljava/lang/String;"); jstring message = (*env)->CallObjectMethod(env, throwable, getMessageMethod); const char *cMessage = (*env)->GetStringUTFChars(env, message, NULL); if (cMessage) { printf("ERROR: %s\n", cMessage); (*env)->ReleaseStringUTFChars(env, message, cMessage); } (*env)->DeleteLocalRef(env, clazz); } …
- Total
- Today
- Yesterday
- 남설 팔찌
- 루미큐브 종류
- 화분벌레
- 별이 빚나는 밤
- 르세라핌
- node.js
- 증권정보포털
- 배당급
- 유가바이트디비
- 별잉 빛나는 밤
- JMW 헤어드라이기기
- 솔리드 쿨론
- 매직 트랙패드2
- yugabyteDB
- 오미크론
- GKRS
- 카카오 에드
- Life Chair
- 톡토기
- VARIDESK
- weka
- 빈센트 반 고흐
- 고체 향수
- 홈 오피스
- 코라나 19
- Sybase IQ
- 브리다 정수기
- Pixel Pals
- 파나소닉 비데 DL-EH10KWS
- 로니카 BCS
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |