JNI Java Native Interface JOHANNES KEPLER UNIVERSITY LINZ Research and teaching network Pratikum SWE 2 © Institut für Systemsoftware, Johannes Kepler Universität Linz Probleme bei nativem Code in Java Methodenaufruf Wie kann man native Methoden aus Java-Code aufrufen? Wie kann man Java-Methoden aus nativem Code aufrufen? Parameterübergabe und Rückgabewärte Wie werden Parameterwerte und Rückgabewerte übergeben Zugriff auf Java Memory Wie kann man Daten in einem Java-Objekt oder Array lesen und schreiben? Wie kann man ein Java-Objekt anlegen? Garbage Collection Wann kann ein Objekt freigegeben werden, das von nativem Code benutzt wird? Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 2 Beispiel: Native Methoden in Java-Code Class Out definiert native Methode für Ausgabe Implementierung erfolgt in einer externen Bibliothek Laden der Library mit der nativen Implementierung vor dem ersten Aufruf Lädt Library mit Implementierung nativer Methoden class Out { static { System.loadLibrary("libout"); } Methode mit Keyword native (wie abstract ,d.h. keine Implementierung) public static native void print(String text); } Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 3 Workflow Native Methoden in Java deklarieren (in Out.java) public static native void print(String text); Java Klassen kompilieren (erzeugt Out.class) javac Out.java Header Dateien generieren (erzeugt Out.h) javah Out Header Dateien inkludieren und native Methoden implementieren (in out.c) #include "Out.h" JNIEXPORT void JNICALL Java_Out_print(JNIEnv* env, jclass clazz, jstring text) {...} Nativen Code compilieren (erzeugt libout.so) Windows: libout.dll gcc ‐shared ‐fPIC ‐I<path‐to‐jdk‐install‐dir>/include ‐o libout.so out.c Programmstart mit Angabe des Pfads zur nativen Library java ‐Djava.library.path=. Out Native Library vor dem ersten Aufruf einer native Methode laden (in Out.java) static { System.loadLibrary("libout"); } Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 4 JNI Headers javah generiert Header-Dateien aus kompilierten Java-Dateien /* DO NOT EDIT THIS FILE ‐ it is machine generated */ #include <jni.h> /* Header for class Out */ #ifndef _Included_Out #define _Included_Out Pro native Methode in Java gibt es eine Methode #ifdef __cplusplus im Header mit Namen: extern "C" { Java_<Klassenname>_<Methodenname> #endif /* * Class: Out * Method: print * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_Out_print (JNIEnv*, jclass, jstring); #ifdef __cplusplus } #endif #endif Macro für Library-Export Macro für C++ Calling conventions Pratikum SWE 2 Parameter der Methode: Parameter werden in native Typen übersetzt JNI Environment: Schnittstelle zur Virtuellen Maschine Klasse der Methode (bei nichtstatischen Methoden jobject (this) © Institut für Systemsoftware Johannes Kepler Universität Linz 5 JNI Type-Mapping Java Typ JNI Typ 32-bit Typ 64-bit Typ Signatur void void void void V byte jbyte signed char signed char B short jshort short short S int jint int int I long jlong long long long J float jfloat float float F double jdouble double double D boolean jboolean unsigned char unsigned char Z char jchar unsigned short unsigned short C java.lang.Object jobject * * Ljava/lang/Object; java.lang.String jstring * * Ljava/lang/String; java.lang.Class jclass * * Ljava/lang/Class; * * Ljava/lang/Throwable; java.lang.Object[] jobjectArray * * [Ljava/lang/Object; int[] jintArray * * [I ? jobject * * L<full‐class‐name>; java.lang.Throwable jthrowable Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 6 Weitere JNI-Typen JNI Typ Verwendung jsize Array Längen jweak Weak References jvalue Basis für alle primitiven Typen jfieldID ID für Felder jmethodID ID für Methoden JNIEnv Interface zur Java VM Typen: Typen sind definiert in jni.h Geänderte Semantik von Typen, z.B.: • • • Precision von float und double gleiche oder größere Wertebereiche kein Java-Overflow tatsächliche Typen sind architekturabhängig! Vererbungshierarchien gleich zu Java, z.B. jstring erbt von jobject Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 7 Object Handles Jedes Objekt (jobject, jstring, …) ist nur ein Handle für ein Java-Objekt Nativer Code bekommt nie Pointer in Java Memory 3 Arten von Handles mit unterschiedlichen Lebenszeiten local lebt bis der native Frame zerstört wird, in dem der Handle angelegt wurde Parameter von nativen Methoden sind immer local kann explizit mit (*env)‐>DeleteLocalRef(env, obj) zerstört werden global lebt über native Frame-Grenzen hinweg muss explizit erzeugt und zerstört werden (NewGlobalRef, DeleteGlobalRef) weak werden explizit erzeugt und zerstört (NewGlobalRef, DeleteGlobalRef) Lebenszeiten wie global aber wie WeakReferece Objekt vom Garbage Collector freigegeben, wenn keine Referenzen in Java VM existieren Handle wird dann null Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 8 Beispiel Object Handle void Java_Foo_bar(JNIEnv* env, jclass clazz, jobject obj) { static jobject last_obj = NULL; if(last_obj != NULL) { (*env)‐>DeleteGlobalRef(env, last_obj); } last_obj = (*env)‐>NewGlobalRef(env, obj); printf(“obj %s last_obj\n”, obj == last_obj ? “==” : “!=”); printf(“obj is %s the same as last_obj\n”, (*env)‐>IsSameObject(obj, last_obj) ? “indeed” : “not”); } Output: obj != last_obj obj is indeed the same as last_obj Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 9 Zugriff auf Java Memory: Felder Suche Klasse jclass GetObjectClass(JNIEnv*, jobject obj) Ähnlich zu Reflection jclass FindClass(JNIEnv*, char* name) Suche Feld (mit Klasse und Name) jfieldID GetFieldID(JNIEnv*, jclass clazz, char* name, char* sig) Lese / schreibe Feld <T> Get<T>Field(JNIEnv*, jobject obj, jfieldID field); void Set<T>Field(JNIEnv*, jobject obj, jfieldID field, <T> value); <T> … für unterschiedliche Typen JNIEnv* env = ... jobject person = ... jclass clazz = (*env)‐>GetObjectClass(env, person); jfieldID field = (*env)‐>GetFieldID(env, clazz, "age", "I"); jint age = (*env)‐>GetIntField(env, person, field); (*env)‐>SetIntField(env, person, field, age + 1); (*env)‐>DeleteLocalRef(clazz); Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 10 Zugriff auf Java Memory: Array 3 Zugriffsarten Elementweiser Zugriff <T> … für unterschiedliche Typen <T> Get<T>ArrayElement(JNIEnv*, j<T>Array array, jsize index) void set<T>ArrayElement(JNIEnv*, j<T>Array array, jsize index, <T> value) Regionsweiser Zugriff Get<T>ArrayRegion(JNIEnv*, j<T>Array array, jsize start, jsize length, <T>* buffer) Set<T>ArrayRegion(JNIEnv*, j<T>Array array, jsize start, jsize length, <T>* buffer) Arrayweiser Zugriff <T>* get<T>ArrayElements(JNIEnv*, j<T>Array array) void Release<T>ArrayElements(JNIEnv*, j<T>Array array, <T>* elems, jint mode) Pratikum SWE 2 0 kopiere Elemente in array und gib Buffer frei JNI_COMMIT kopiere Elemente in array und gib Buffer nicht frei JNI_ABORT kopiere Elemente nicht zurück und gib Buffer frei © Institut für Systemsoftware Johannes Kepler Universität Linz 11 Beispiele: Zugriff auf Arrays JNIEnv* env = ...; jintArray array = …; //Access each element individually for(jsize i = 0; i < (*env)‐>GetArrayLength(env, array); i++) { jint value = (*env)‐>GetIntArrayElement(env, array, i); (*env)‐>SetIntArrayElement(env, array, i, value + 1); } //Access the entire array at once jint* native_array = (*env)‐>GetIntArrayElements(env, array); for(jsize i = 0; i < (*env)‐>GetArrayLength(env, array); i++) { native_array[i]++; } (*env)‐>ReleaseIntArrayElements(env, array, native_array, 0); //Access chunks of the array jint* chunk = (jint*) calloc(16, sizeof(jint); for(jsize i = 0; i < (*env)‐>GetArrayLength(env, array) / 16; i++) { (*env)‐>GetIntArrayRegion(env, array, i*16, 16, chunk); for(int i = 0; i < 16; i++) { chunk[i]++; } (*env)‐>SetIntArrayRegion(env, array, i*16, 16, chunk); } free(chunk); Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 12 Zugriff auf Java Memory: Strings Lesender Zugriff wie auf Arrays jsize GetStringLength(JNIEnv*, jstring string) Inhalt wird nicht zurückgeschrieben! jchar* GetStringChars(JNIEnv*, jstring string) void ReleaseStringChars(JNIEnv*, jstring string, jchar* chars) JNIEXPORT void JNICALL Java_Out_print(JNIEnv* env, jclass clazz, jstring text) { if(text != NULL) { jsize length = (*env)‐>GetStringLength(env, text); jchar* characters = (*env)‐>GetStringChars(env, text); char* native_characters = calloc(length + 1, sizeof(char)); for(jsize i = 0; i < length; i++) { native_characters[i] = (char) characters[i]; //assume ASCII only } native_characters[length] = '\0'; (*env)‐>ReleaseStringChars(env, text, characters); printf("%s", native_characters); free(native_characters); } } clazz und text sind local Handles und müssen deswegen nicht explizit gelöscht werden Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 13 Aufrufe von Java aus Native-Code Suche Klasse wie bei Feldzugriff Suche Methode "(" ParamSig0 ParamSig1 … ")" ReturnSig zB: "(JJ)Z" jmethodID GetMethodID(JNIEnv*, jclass clazz, char* name, char* sig) Parameters Aufruf <T> Call<T>Method(JNIEnv*, jobject thiz, jmethodID method, ...) <T> CallNonvirtual<T>Method(JNIEnv*, jobject thiz, jclass clazz, jmethodID method, ...) <T> CallStatic<T>Method(JNIEnv*, jclass clazz, jmethodID method, ...) Spezielle Funktionen zur Objekterzeugung jobject NewObject(JNIEnv*, jclass clazz, jmethodID constructor, ...) jstring NewString(JNIEnv*, jchar* chars, jsize length) j<T>Array New<Type>Array(JNIEnv*, jsize length) jobject AllocObject(JNIEnv*, jclass clazz) Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz Erzeugt Objekt ohne Konstruktor aufzurufen! 14 Beispiel: Aufrufe von Java aus Native-Code JNIEnv* env = ...; jobject person1 = ...; jobject person2 = ...; jclass object_class = (*env)‐>FindClass(env, " Ljava/lang/Object");(Object): boolean const char* sig = "(Ljava/lang/Object;)Z"; jmethodID equals_method = (*env)‐>GetMethodID(env, object_class, "equals", sig); jboolean equals = (*env)‐>CallBooleanMethod(env, person1, equals_method, person2); (*env)‐>DeleteLocalRef(env, object_class); boolean equals(Object) Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 15 Exceptions JNIEXPORT void JNICALL Java_Person_raiseSalary (JNIEnv* env, jclass clazz, jobject person, jdouble factor) { if(person == NULL) { char* exception_name = "java/lang/NullPointerException"; jclass exception_class = (*env)‐>FindClass(env, exception_name); (*env)‐>ThrowNew(env, exception_class, “person must not be null”); return; } ... } ThrowNew wirft Exception in Java VM erst, wenn die native Methode beendet ist! Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 16 Weitere JNI-Methoden Methode Beschreibung ExceptionOccurred Prüft, ob der zuletzt ausgeführte Aufruf eine Exception ausgelöst hat ExceptionDescribe Erzeugt einen String, der die Exception beschreibt IsSameObject Prüft Objekte auf Referenzgleichheit IsInstanceOf Prüft, ob ein Objekt von einem bestimmten Typ ist http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/functions.html Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 17 Zusammenfassung JNI ermöglich native Code in Java einzubinden Für jeden Java-Typ gibt es einen nativen JNI-Typ Nativer Code kann Java-Objekte benutzen und Java-Methoden aufrufen Nativer Code bekommt nie einen direkten Pointer auf Java Memory Manuelle Speicherverwaltung bei Java-Objekten (Gefahr von Memory Leaks!) Pratikum SWE 2 © Institut für Systemsoftware Johannes Kepler Universität Linz 18