Java Native Interface Eine Einführung
Motivation Java: abstrahiert von Hardware und Betriebssystem + hohe Portabilität + hohe Sicherheit (Sandbox-Prinzip) - verminderte Performanz (mit heutigen JIT-Compilern i.A. kein Problem mehr) - kein direkter Zugriff auf Hardware-/Betriebssystem C/C++: Programmiersprache für OS-Entwicklung (UNIX) + maschinennah, hohe Performanz + alt, weit verbreitet, umfangreiche Sammlung von Legacy-Code - Programmierung möglicherweise umständlich (GUI in C?) - hohe Anforderungen an Programmierer
Das Java Native Interface (JNI) Schnittstelle, um C/C++ -Code in Java einzubinden und umgekehrt Beispiele - C/C++ in Java: Treiber, Betriebssystemfunktionen ansprechen (SWT) Vorhandene Bibliotheken einbinden Zeitkritische Funktionen in C oder Assembler einbinden Möglicherweise Verlust der Portabilität! Beispiel – Java in C/C++: Browser, der Applets ausführt
JNI - Architektur Schnittstelle definiert Datentypen und Funktionen… zum Aufruf nativer Funktionen in Java zur Abbildung von Java-Datentypen in C/C++ zur Erzeugung und Mani- pulation von Java-Objekten in C/C++ (OO in C?) zum Aufruf einer JVM aus C/C++ heraus
C in Java : Hallo Welt Minimal-Beispiel: Aufruf einer nativen Methode, die „Hallo Welt!“ auf der Konsole ausgibt. Prinzipielles Vorgehen (immer gleich):
Hallo Welt – Java-Code Definition einer nativen Methode ohne Rumpf Dynamisches Laden der Bibliothek, die Methode bereitstellt Kompilieren mit: javac HelloWorld.java class HelloWorld { private native void print(); public static void main(String[] args) { new HelloWorld().print(); } static { System.loadLibrary("HelloWorld"); }
Hallo Welt – C/C++-Header Die C-Methode, die in HelloWorld aufgerufen wird, muss gewisse Konventionen erfüllen javah –jni HelloWorld.class erzeugt Header-Datei HelloWorld.h mit Prototypen aller nativen Methoden: Methoden-Name muss in C eindeutig sein, daher: Java_packageName_className_methodName Parameterlose Funktion print() erhält in C zwei Parameter !? include <jni.h> […] JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *env, jobject); […]
Hallo Welt – C-Code Implementierung der in Header-Datei definierten Funktion in Datei HelloWorld.c: Übersetzen in gemeinsam genutzte Bibliothek gcc -fPIC -c HelloWorld.c ld -shared -soname libHelloWorld.so.l -o libHelloWorld.so HelloWorld.o #include "HelloWorld.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_print (JNIEnv *env, jobject obj){ printf("Hallo Welt!\n"); }
Hallo Welt - Finale Ausführen des Java-Programms: java HelloWorld java.lang.UnsatisfiedLinkError: no HelloWorld in library path at java.lang.Runtime.loadLibrary(Runtime.java) at java.lang.System.loadLibrary(System.java) at HelloWorld.main(HelloWorld.java) Pfad zur Bibliothek noch unbekannt! Entweder Bibliothek in /usr/lib verschieben (root-Rechte!), setenv LD_LIBRARY_PATH .:${LD_LIBRARY_PATH}, oder Bibliothekpfad explizit an Java übergeben: java –Djava.library.path=. HelloWorld
Abbildung der Datentypen Bisher: kein Datenaustausch zwischen Java und C Datentypen in Java: Primitive Datentypen, Klassen, Objekte, Arrays Unterschiede Java vs. C: Java: Einheitliche Spezifikation primitiver Datentypen auf allen Plattformen C: plattformabhängig (z.B. 2Byte int, 4Byte int) Java: Objektorientierung C: keine Objekte, aber strukturierte Datentypen und Funktionszeiger
Abbildung primitiver Datentypen Java-Typ Nativer Typ Beschreibung boolean jboolean 8 Bit ohne Vorzeichen byte jbyte 8 Bit mit Vorzeichen char jchar 16 Bit ohne Vorzeichen short jshort 16 Bit mit Vorzeichen int jint 32 Bit mit Vorzeichen long jlong 64 Bit mit Vorzeichen float jfloat 32 Bit double jdouble 64 Bit void
Abb. primitiver Datentypen – Beispiel public class Sum{ static{ System.loadLibrary("Sum"); } public static native int sum(int a, int b); public static void main(String[] args){ System.out.println("sum(3,4)=" + sum(3,4)); }} #include <jni.h> […] JNIEXPORT jint JNICALL Java_Sum_sum (JNIEnv *env, jclass, jint, jint); […] JNIEXPORT jint JNICALL Java_Sum_sum(JNIEnv *env, jclass cl, jint a, jint b){ jint result = a + b; return result; }
Abbildung von Referenz-Datentypen Objekte, Klassen und Arrays werden in Java über Referenzen angesprochen An C wird ein „transparenter“ Pointer übergeben, der auf die Objekte in der JVM zeigt. Zugriff auf die Objekte erfolgt immer über die JNIEnv-Umgebung, die jeder nativen Methode übergeben wird:
Abb. von Referenztypen – z.B. Arrays import java.util.Random; public class Sort{ static{ System.loadLibrary("QSort"); } public static native void sort(double[] arr); public static void main(String[] args){ […] double[] arr = new double[Integer.parseInt(args[0])]; Random r = new Random(); for(int i=0; i< arr.length; arr[i++] = r.nextInt(10000)); long s = System.currentTimeMillis(); sort(arr); long e = System.currentTimeMillis(); System.out.println("Sortierung dauerte "+(e-s)+"ms"); }
Abb. von Referenztypen – z.B. Arrays Prototyp: Zugriff auf Array-Elemente: JNIEXPORT void JNICALL Java_Sort_sort (JNIEnv *env, jclass, jdoubleArray); JNIEXPORT void JNICALL Java_Sort_sort (JNIEnv *env, jclass cl, jdoubleArray arr){ jdouble *cArray = (jdouble*) arr; // FALSCH!!! // arr ist lediglich ein Pointer auf eine JVM- // Datenstruktur. Der Zugriff auf die Array-Elemente // muss über die JNIEnv Umgebung erfolgen! […] }
Abb. von Referenztypen – z.B. Arrays int compare(const void *a, const void *b){ return *((jdouble*) a) < *((jdouble*) b) ? -1 : +1; } JNIEXPORT void JNICALL Java_Sort_sort (JNIEnv *env, jclass cl, jdoubleArray arr){ jboolean isCopy; jdouble* cArray = (*env)->GetDoubleArrayElements(env, arr, isCopy); jint length = (*env)->GetArrayLength(env, array); qsort(cArray, length, sizeof(jdouble), &compare); if(isCopy) (*env)->ReleaseDoubleArrayElements(env, array, cArray, 0);
Array-Zugriffsmethoden Get<Type>ArrayRegion, Set<Type>ArrayRegion Kopiert Bereich aus oder in ein Array mit primitivem Datentyp Get<Type>ArrayElements, Release<Type>ArrayElements Erhält einen Pointer auf ein Array (ggf. auch Kopie) und gibt ihn wieder frei (ansonsten Speicherleck!) GetArrayLength Ermittelt die Länge eines Arrays New<Type>Array Legt ein neues Array an Nur auf primitive Arrays anwendbar!
Zugriff auf Objekt-Arrays Beispiel: String[] strArray; int[][] intArray; Object[] objArray; Unmöglich, gesamtes Array abzufragen Lediglich einzelne Einträge zugreifbar GetObjectArrayElement SetObjectArrayElement
Beispiel: Anlegen eines 2D-Feldes private static native int[][] initInt2DArray(int size); Zuerst: Anweisung an JVM, ein int[] array anzulegen: JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass c, int s){ jobjectArray result; int i; jclass intArrCls = (*env)->FindClass(env, "[I"); if (intArrCls == NULL) return NULL; /* exception thrown */ result = (*env)->NewObjectArray(env, size, intArrCls, NULL); if (result == NULL) return NULL; /* out of memory error thrown */ …
Beispiel: Anlegen eines 2D-Feldes Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jint tmp[256]; /* make sure it is large enough! */ int j; jintArray iarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result;
Beispiel: Anlegen eines 2D-Feldes Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jint tmp[256]; /* make sure it is large enough! */ int j; jintArray iarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; Inhalt von tmp nach iarr kopieren
Beispiel: Anlegen eines 2D-Feldes Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jint tmp[256]; /* make sure it is large enough! */ int j; jintArray iarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; Array an übergeordnetes Objekt-Array result übergeben
Beispiel: Anlegen eines 2D-Feldes Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jint tmp[256]; /* make sure it is large enough! */ int j; jintArray iarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; Kontrolle über iarr an JVM geben
Arbeiten mit Strings Java Strings sind Unicode-Codiert, C-Strings sind ASCII-codiert Anbindung über UTF-8 (kann beide Codierungen abbilden) GetStringChars, ReleaseStringChars GetStringUTFChars, ReleaseStringUTFChar GetStringLength, GetStringUTFLength NewString, NewStringUTF GetStringRegion, SetStringRegion GetStringUTFRegion, SetStringUTFRegion Anbindung sehr umständlich, Java im Umgang mit Strings komfortabler, daher wenig Relevanz
Zugriff auf Objekt-Felder und -Methoden Bei komplexen Klassen-Instanzen kann aus C heraus auf die Felder und Methoden der Instanz zugegriffen werden. Mechanismus erscheint relativ umständlich, daher hier nur exemplarisch dargestellt. Für nähere Infos siehe JNI-Referenz
Zugriff auf Static-Feld Java-Klasse mit statischem Feld si si soll in nativer Methode manipuliert werden class StaticFieldAccess { private static int si; private native void accessField(); public static void main(String args[]) { StaticFieldAccess c = new StaticFieldAccess(); StaticFieldAccess.si = 100; c.accessField(); System.out.println("In Java:"); System.out.println(" StaticFieldAccess.si = " + si); } static { System.loadLibrary("StaticFieldAccess"); }
Zugriff auf Static-Feld JNIEXPORT void JNICALL Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj){ jfieldID fid; /* store the field ID */ jint si; /* Get a reference to obj’s class */ jclass cls = (*env)->GetObjectClass(env, obj); /* Look for the static field si in cls */ fid = (*env)->GetStaticFieldID(env, cls, "si", "I"); if (fid == NULL) return; /* field not found */ /* Access the static field si */ si = (*env)->GetStaticIntField(env, cls, fid); printf(" StaticFieldAccess.si = %d\n", si); (*env)->SetStaticIntField(env, cls, fid, 200); }
Zugriff auf Instanz-Methode Methode callback() soll in nativeMethod() gerufen werden class InstanceMethodCall { private native void nativeMethod(); private void callback() { System.out.println("In Java"); } public static void main(String args[]) { InstanceMethodCall c = new InstanceMethodCall(); c.nativeMethod(); static { System.loadLibrary("InstanceMethodCall");
Zugriff auf Instanz-Methode JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj){ jclass cls = (*env)->GetObjectClass(env, obj); jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V"); if (mid == NULL) return; /* method not found */ printf("In C\n"); (*env)->CallVoidMethod(env, obj, mid); }
Diese Themen werden dem geneigten Hörer zum Selbststudium überlassen Das war erst der Anfang... JNI bietet noch mehr: Erzeugen von Objekten, Aufruf von Konstruktoren Caching von Methoden-/Feld-Ids Exception-Handling … Diese Themen werden dem geneigten Hörer zum Selbststudium überlassen
Fazit Enorme Möglichkeiten, positive Aspekte von C, C++ und Java zu integrieren Aufwand allerdings nicht unerheblich, daher sollten immer auch Alternativen in Betracht gezogen werden Für Standardaufgaben ist Einsatz von JNI aufgrund des stetig abnehmenden Performanz-Vorsprungs von C/C++ (wahrscheinlich) nicht lohnend