Das Java Native Interface

Werbung
Das Java Native Interface
Wozu JNI?
Man benötigt Plattform-spezifische Features, die nicht durch die
vorhandenen JAVA-Klassen bereitgestellt werden. Z.B.:
Zugriff auf einen Bandroboter über die SCSI-Schnittstelle um eine
grafische Oberfläche zur Bedienung des Roboters zu entwickeln.
Es ist eine Bibliothek mit Routinen vorhanden, die nicht in JAVA
programmiert wurden. Z.B.:
Ausnutzung einer speziellen Hardware für numerische
Berechnungen.
Ein kleiner zeitkritischer Programmabschnitt soll in einer
hardwarenahen Sprache programmiert werden, um das Programm
zu beschleunigen. Z.B.:
Textsuche in einem Editor
Skalarprodukt in einem numerischen Programm
Dreiecksberechnung in einem FEM-Code
Was bietet JNI?
Erzeugen, Analysieren und Verändern von JAVA-Objekten
(einschließlich Felder und Zeichenketten) in C-Programmen.
Aufruf von JAVA-Methoden in C-Programmen.
Erzeugen und Abfangen von JAVA-Ausnahmen in C-Programmen.
Laden von JAVA-Klassen in C-Programmen.
Konsequente Typprüfung.
Aufruf von C-Routinen aus JAVA
Das Java Native Interface ermöglicht die Verbindung zwischen
Programmen, die in JAVA geschrieben wurden mit Programmen, die
in anderen Sprachen geschrieben sind, z.B. C, C++, Assembler,
FORTRAN u.s.w.
Ziele:
Die strenge Schnittstellenprüfung von JAVA soll erhalten bleiben.
Es soll ermöglicht werden aus den anderen Sprachen heraus
• JAVA-Objekte zu lesen und zu erzeugen,
• JAVA-Methoden aufzurufen und
• JAVA-Ausnahmen zu erzeugen.
Probleme:
Unterschiede bei den primitiven Datentypen
Unterschiede bei Objekttypen
Unterschiede bei der Parameterübergabe
Es werden eine Reihe von Tools zu Verfügung gestellt, die die
Kopplung zwischen JAVA und C bzw. C++ vereinfachen.
HelloWorld: aufzurufendes C-Programm
#include <stdio.h>
void helloWorld()
{
printf("Hello, world!\n");
}
Das Programm helloWorld sei vorgegeben und als Header-File
hello.h sowie als Objektlibrary (LIB) hello.lib gespeichert.
Problem:
Wie kann helloWorld in einem JAVA-Programm aufgerufen werden.
Lösung:
Entwickeln einer Schnittstelle, die in einer oder mehreren JAVA-Klassen
realisiert ist und den komfortablen Zugriff auf die Library ermöglicht.
Struktur
HelloWorld.java
HelloWorld.class
Hello.java
Hello.class
C_hello.h
C_hello.c
C_hello.dll
hello.h
hello.c
hello.lib
Aufrufendes JAVA-Programm
public class Hello
{
public static native void helloWorld();
static
{
System.loadLibrary("C_hello");
}
}
Das Tool javah
Das Tool javah dient dazu aus einem JAVA-Programm, das nativeMethoden enthält, das header-File zu erzeugen, das die
Schnittstelle zum Interface-Programm beschreibt.
Aufruf: javah –o h-file.h filename
Es wird ein header-File filename.h erzeugt, das die zu realisierenden
Schnittstellen beschreibt.
Beispiel: javah -o C_hello.h Hello erzeugt C_hello.h
Erzeugtes header-File C_Hello.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Hello */
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:
Hello
* Method:
helloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_helloWorld
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
Ausfüllen der Schnittstelle
Es ist nun das Interface zu programmieren.
Dazu muss das in dem Header-File definierte Programm
Java_Hello_helloWorld programmiert werden.
Die Parameter können zunächst vernachlässigt werden.
#include "C_hello.h"
/*
* Class:
Hello
* Method:
helloWorld
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_helloWorld (JNIEnv
* env, jclass class)
{
helloWorld();
}
Übersetzen des C-Interfaces
Bei der Übersetzung muss die Library hello.dll eingebunden
werden:
hello.lib
Es müssen die JNI-spezifischen Include-Verzeichnisse angegeben
werden:
-Ic:\jdk1.3\include -Ic:\jdk1.3\include\win32
Es muss eine DLL erzeugt werden:
-LD -FeC_hello.dll
Insgesamt für MS-C-Compiler unter Windows:
cl -Ic:\jdk1.3\include -Ic:\jdk1.3\include\win32
C_hello.c hello.lib -LD -FeC_hello.dll
Nutzung des Interfaces
Das so entwickelte Interface kann nun wie eine ganz normale JAVAKlasse verwendet werden.
public class HelloWorld
{
public static void main(String[] args)
{
Hello.helloWorld();
}
}
Übergabe von Parametern
Problem: Datentypen von JAVA und C sind nicht kompatibel.
C-Datentypen sind nicht vollständig standardisiert und damit
abhängig von Compiler, Betriebssystem und Hardware.
JAVA-Datentypen sind vollständig standardisiert und überall
identisch.
Der Zugriff auf Attribute von Parameter-Objekten ist naturgemäß
recht kompliziert.
Beispiel: EchoText
#include <stdio.h>
void helloWorld()
{
printf("Hello, world!\n");
}
void echoText(const char *text)
{
printf("%s\n",text);
}
Header-File:C_hello.h
Zusätzlich wird folgender Text generiert.
Er enthält einen zusätzlichen Parameter vom Typ jstring.
Problem: dieser Parameter muss in eine Zeichenkette vom C-Typ
char* umgewandelt werden.
/*
* Class:
Hello
* Method:
echoText
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_Hello_echoText
(JNIEnv *, jclass, jstring);
Lesen des Parameters
Problem: Umwandlung des JAVA-String-Parameters in ein
entsprechendes C-Format (const char *).
JNI stellt dafür diverse Methoden zur Verfügung.
Die Methoden sind erreichbar
in C:
(*env)->Funktionsname(env,parameter);
In C++: env->Funktionsname(parameter);
Speziell lesen einer Unicode-Zeichenkette:
GetStringUTFChars(env, text, isCopy)
Alle mit (*env)->GetXXXX angelegten C-Objekte müssen mit
(*env)->ReleaseXXXX wieder freigegeben werden, wenn sie
nicht mehr benötigt werde.
Beispiel
JNIEXPORT void JNICALL Java_Hello_echoText (JNIEnv
*env, jclass class, jstring text)
{
const char *ctext;
ctext= (*env)->GetStringUTFChars(env, text, NULL);
echoText(ctext);
(*env)->ReleaseStringUTFChars(env, text, ctext);
}
Erzeugen von JAVA-Strings
Zum Erzeugen eines JAVA-Strings aus einer C-Zeichenkette gibt es
die Methode
jstring NewStringUTF(JNIEnv *env, const char
*bytes);
Beispiel: concatSrings
char *concatStrings(const char *s1, const char *s2)
{
char *s;
s=(char *)malloc(strlen(s1)+strlen(s2)+1,sizeof(s[0]));
strcpy(s,s1);
strcat(s,s2);
return s;
}
Hello.java
public class Hello
{
public static native void helloWorld();
public static native void echoText(String text);
public static native String concatStrings(String s1, String s2);
static
{
System.loadLibrary("C_hello");
}
}
C_hello.h
/*
* Class:
Hello
* Method:
concatStrings
* Signature:
(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Hello_concatStrings
(JNIEnv *, jclass, jstring, jstring);
C_hello.c
/*
* Class:
Hello
* Method:
concatStrings
* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_Hello_concatStrings (JNIEnv *env, jclass class,
jstring s1, jstring s2)
{
const char *cs1;
const char *cs2;
char *cs;
jstring s;
cs1= (*env)->GetStringUTFChars(env, s1, NULL);
cs2= (*env)->GetStringUTFChars(env, s2, NULL);
cs = concatStrings(cs1, cs2);
(*env)->ReleaseStringUTFChars(env, s1, cs1);
(*env)->ReleaseStringUTFChars(env, s2, cs2);
s=(*env)->NewStringUTF(env, cs);
free(cs);
return s;
}
Zugriffsmethoden für Zeichenketten
C-ASCII-Zeichenkette JAVA-Zeichenkette
jstring NewStringUTF(JNIEnv *env, const char
*bytes)
const char* GetStringUTFChars(JNIEnv *env,
jstring string, jboolean *isCopy)
void ReleaseStringUTFChars(JNIEnv *env, jstring
string, const char *utf)
jsize GetStringUTFLength(JNIEnv *env, jstring
string)
C-Unicode-Zeichenkette JAVA-Zeichenkette
jstring NewString (JNIEnv *env, const jchar
*bytes)
const jchar* GetStringChars(JNIEnv *env, jstring
string, jboolean *isCopy)
void ReleaseStringChars(JNIEnv *env, jstring
string, const jchar *utf)
jsize GetStringLength(JNIEnv *env, jstring
string)
Zugriff auf Felder
Wie für Zeichenketten gibt es für alle Arten von eindimensionalen
Feldern entsprechende New-, Get-, Set- und Release-Methoden.
Dabei muss unterschieden werden zwischen den Zugriffsmethoden
für Felder von primitiven Typen und Felder von Objekten.
jsize GetArrayLength(JNIEnv *env, jarray array)
bestimmt die Länge des Feldes (array.length).
<ArrayType> New<PrimitiveType>Array(JNIEnv *env,
jsize length) bzw.
jarray NewObjectArray(JNIEnv *env, jsize length,
jclass elementClass, jobject initialElement)
erzeugt ein neues Feld
Felder umkopieren
<NativeType> *Get<PrimitiveType>ArrayElements
(JNIEnv *env, <ArrayType> array, jboolean *isCopy)
erzeugt ein C-Feld und kopiert ggf. den Inhalt des JAVA-Feldes in
das C-Feld. Änderungen des C-Feldes werden ggf. erst bei dem
entsprechenden Release-Aufruf zurückkopiert. isCopy liefert
JNI_TRUE, wenn das Feld kopiert wurde, JNI_FALSE sonst.
void Release<PrimitiveType>ArrayElements(JNIEnv
*env, <ArrayType> array, <NativeType> *elems, jint
mode)
das C-Feld wird ggf. auf das JAVA-Feld zurückkopiert und die
Ressourcen werden wieder freigegeben.
mode steuert die Funktionen:
0 : Kopieren der Daten und Ressourcen freigeben
JNI_COMMIT !
JNI_ABORT : Ressourcen freigeben und Daten nicht kopieren
Teile von Feldern umkopieren
void Get<PrimitiveType>ArrayRegion(JNIEnv *env,
<ArrayType> array, jsize start, jsize len,
<NativeType> *buf)
kopiert die Elemente start,...,start+len-1 aus dem JAVAFeld array in das C-Feld buf
void Set<PrimitiveType>ArrayRegion(JNIEnv *env,
<ArrayType> array, jsize start, jsize len,
<NativeType> *buf)
kopiert die Elemente start,...,start+len-1 aus dem C-Feld
buf in das JAVA-Feld array
Elemente von Object-Arrays
jobject GetObjectArrayElement(JNIEnv *env,
jobjectArray array, jsize index)
liest ein Element aus eine Object-Array
void SetObjectArrayElement(JNIEnv *env,
jobjectArray array, jsize index, jobject value)
schreibt das Element value in das Feld
JAVA-Typen / C-Typen
JAVA Feldtyp
PrimitiveType
NativeType
ArrayType
boolean[]
Boolean
jboolean
jbooleanArray
byte[]
Byte
jbyte
jbyteArray
char[]
Char
jchar
jcharArray
short[]
Short
jshort
jshortArray
int[]
Int
jint
jintArray
long[]
Long
jlong
jlongArray
float[]
Float
jfloat
jfloatArray
double[]
Double
jdouble
jdoubleArray
Beispiel : matrixXvector
void matrixXvector(int n, int m, double *matrix,
double *vector, double *result)
{
int i,j;
for (i=0;i<m;i++)
{
result[i]=0.0;
for (j=0;j<n;j++)
{
result[i]+=matrix[i*n+j]*vector[j];
}
}
}
C_hello.h
/*
* Class:
Hello
* Method:
matrixXvector
* Signature: ([[D[D)[D
*/
JNIEXPORT jdoubleArray JNICALL Java_Hello_matrixXvector
(JNIEnv *, jclass, jobjectArray, jdoubleArray);
C-hello.c
JNIEXPORT jdoubleArray JNICALL Java_Hello_matrixXvector
(JNIEnv *env, jclass clazz, jobjectArray matrix, jdoubleArray vector) {
jsize n, m, i;
jdouble *cMatrix;
jdouble *cVector;
jdouble *cResult;
jboolean isCopy;
jdoubleArray zeile;
jdoubleArray result;
n=(*env)->GetArrayLength(env, vector);
m=(*env)->GetArrayLength(env, matrix);
cVector=(*env)->GetDoubleArrayElements(env, vector, &isCopy);
cMatrix=(jdouble *)calloc(m*n,sizeof(jdouble));
for (i=0;i<m;i++){
zeile=(jdoubleArray)((*env)->GetObjectArrayElement(env, matrix, i));
(*env)->GetDoubleArrayRegion(env, zeile, 0, n, &(cMatrix[i*n]));
}
cResult=(jdouble *)calloc(m,sizeof(jdouble));
matrixXvector(n, m, cMatrix, cVector, cResult);
result=(*env)->NewDoubleArray(env, m);
(*env)->SetDoubleArrayRegion(env, result, 0, m, cResult);
free(cMatrix);
free(cResult);
(*env)->ReleaseDoubleArrayElements(env, vector, cVector, JNI_ABORT);
return result;
}
Zugriff auf JAVA-Objekte
Objekte können als Parameter an das Interface JAVA-C übergeben
werden und es soll auf die Attribute und Methoden des Objektes
zugegriffen werden.
Es soll auf JAVA-Klassen und deren statische Methoden zugegriffen
werden, z.B. sollen die Methoden von StrictMath im C-Programm
verwendet werden.
Es sollen im C-Programm Objekte erzeugt werden, z.B. um ein
Objekt als return-Wert zurückgeben zu können.
Für die Unterscheidung der überladenenMethode spielen die
Signaturen eine entscheidende Rolle.
Mit Hilfe der Signaturen lassen sich Datentypen eindeutig und relativ
kompakt beschreiben.
Signaturen
Type Signature
Java Type
Z
boolean
B
byte
C
char
S
short
I
int
J
long
F
float
D
double
V
void
Lfully-qualified-class;
fully-qualified-class
[type
type[]
(arg-types)ret-type
method type
Beispiele für Signaturen
Signatur von Feldern: Für jeden Index eine "[" anschließend die
Signatur des Elementtyps.
Z.B.: double[][] [[D
Signatur von Objekten: Lvoller-Klassen-Name;
Z.B.: String Ljava/lang/String;
Signatur von Methoden: (Signaturen der Parameter)Signatur des
Wertes.
Z.B.: double[] methode(int[][], boolean)([[DZ)[D
Zugriff auf Attribute von Objekten
Allgemeiner Typ von Objekten: jobject
Der Zugriff auf Attribute erfolgt über den FieldID. Um den FieldID
zu bestimmen benötigt man:
Die Klasse zu der das Objekt gehört.
Den Namen des Attributes.
Die Signatur des Attributes.
Es muss unterschieden werden zwischen statischen und
nichtstatischen Attributen und Methoden.
jclass GetObjectClass(JNIEnv *env, jobject obj)
bestimmt die Klasse zu der das Objekt obj gehört.
jclass FindClass(JNIEnv *env, const char *name)
bestimmt die Klasse anhand ihres vollständigen Namens
Für interne Klassen gilt der Klassenname:
Klasse$interneKlasse
nichtstatische Attribute
jfieldID GetFieldID(JNIEnv *env, jclass clazz,
const char *name, const char *sig)
bestimmt aus der Klasse, dem Attributnamen und seiner Signatur
den FieldID.
<NativeType> Get<PrimitiveType>Field(JNIEnv *env,
jobject obj, jfieldID fieldID)
liest den Wert des Attributes, funktioniert auch für Objekt-Typen
void Set<type>Field(JNIEnv *env, jobject obj,
jfieldID fieldID, <NativeType> value)
schreibt einen Wert in das Attribut
Beispiel
... method(JNIEnv *env, jclass class, jobject obj)
{
jclass objClass;
jfieldID attrID;
jint intAttr;
objClass=(*env)->GetObjectClass(env, obj);
attrID=(*env)->GetFieldID(env, class, "dimension", "I");
intAttr=(*env)->GetIntField(env, obj, attrID);
intAttr=2*intAttr+77;
(*env)->SetIntField(env, obj, attrID, intAttr);
}
statische Attribute
jfieldID GetStaticFieldID(JNIEnv *env, jclass
clazz, const char *name, const char *sig)
bestimmt aus der Klasse, dem Attributnamen und seiner Signatur
den FieldID.
<NativeType> GetStatic<PrimitiveType>Field(JNIEnv
*env, jclass clazz, jfieldID fieldID)
liest den Wert des Attributes, funktioniert auch für Objekt-Typen
void SetStatic<type>Field(JNIEnv *env, jclass
clazz, jfieldID fieldID, <NativeType> value)
schreibt einen Wert in das Attribut
Beispiel
... method(JNIEnv *env, jclass class, jobject obj)
{
jclass objClass;
jfieldID attrID;
jint intAttr;
objClass=(*env)->GetObjectClass(env, obj);
attrID=(*env)->GetStaticFieldID(env, class, "dimension",
"I");
intAttr=(*env)->GetStaticIntField(env, class, attrID);
intAttr=2*intAttr+77;
(*env)->SetStaticIntField(env, class, attrID, intAttr);
}
nichtstatische Methoden von Objekten
jmethodID GetMethodID(JNIEnv *env, jclass clazz,
const char *name, const char *sig)
bestimmt den MethodID einer Methode. Bei überladenen Methoden
wird die sie durch die Signatur identifiziert.
Diese Varianten des Aufrufs von JAVA-Methoden unterscheiden sich
nur in der Art der Übergabe der Parameter.
<NativeType> Call<PrimitiveType>Method(JNIEnv *env,
jobject obj, jmethodID methodID, ...)
alle Parameter werden nacheinander angegeben
<NativeType> Call<PrimitiveType>MethodA(JNIEnv
*env, jobject obj, jmethodID methodID, jvalue
*args)
die Parameter werden als Feld übergeben
<NativeType> Call<PrimitiveType>MethodV(JNIEnv
*env, jobject obj, jmethodID methodID, va_list
args)
die Parameter werden als Liste übergeben
Beispiel
method(JNIEnv *env, jclass class, jobject obj)
{
jclass objClass;
jmethodID methID;
objClass=(*env)->GetObjectClass(env, obj);
methID=(*env)->GetMethodID(env, objClass,
"setDimension", "(I)V");
(*env)->CallVoidMethod(env, obj, methID, 777);
}
Aufruf statischer Methoden
jmethodID GetStaticMethodID(JNIEnv *env, jclass
clazz, const char *name, const char *sig)
<NativeType> CallStatic<PrimitiveType>Method
(JNIEnv *env, jclass clazz, jmethodID methodID,
...)
alle Parameter werden nacheinander angegeben
<NativeType> CallStatic<PrimitiveType>MethodA
(JNIEnv *env, jclass clazz, jmethodID methodID,
jvalue *args)
die Parameter werden als Feld übergeben
<NativeType> CallStatic<PrimitiveType>MethodV
(JNIEnv *env, jclass clazz, jmethodID methodID,
va_list args)
die Parameter werden als Liste übergeben
Beispiel
... jdouble sin(JNIEnv *env, jclass clazz, jdouble x)
{
jclass cl;
jmethodID sinID;
jdouble sx;
cl=(*env)->FindClass(env, "java/lang/StrictMath");
sinID=(*env)->GetStaticMethodID(env, cl, "sin", "(D)D");
sx=(*env)->CallStaticDoubleMethod(env, cl, sinID, x);
return sx;
}
Neue JAVA-Objekte erzeugen
jobject AllocObject(JNIEnv *env, jclass clazz)
erzeugt eine neues Objekt der Klasse clazz ohne einen Konstruktor
aufzurufen
jobject NewObject(JNIEnv *env, jclass clazz,
jmethodID methodID, ...)
jobject NewObjectA(JNIEnv *env, jclass clazz,
jmethodID methodID, jvalue *args)
jobject NewObjectV(JNIEnv *env, jclass clazz,
jmethodID methodID, va_list args)
erzeugt ein neues Objekt, wobei der Konstruktor mit der ID
methodID und den angegebenen Parametern benutzt wird.
Als Name des Konstruktors wird "<init>" verwendet. Als Signatur
für den return-Wert "V".
Bei internen Klassen ist die Parameterliste am Anfang um einen
Parameter jclass zu erweitern. Diesem ist der Parameter class der
C-Methode zu übergeben.
Beispiel
...jobject method(JNIEnv *env, jclass clazz)
{
jclass tsClass;
jmethodID tsConst;
jobject tsObj;
jmethodID tsAdd;
tsClass=(*env)->FindClass(env, "java/util/TreeSet");
tsConst=(*env)->GetMethodID(env, tsClass, "<init>", "()V");
tsObj=(*env)->NewObject(env, tsClass, tsConst);
tsAdd=(*env)->GetMethodID(env, tsClass, "add",
"(Ljava/lang/Object;)Z");
(*env)->CallBooleanMethod(env, tsObj, tsAdd,
(*env)->NewStringUTF(env, "Mueller"));
(*env)->CallBooleanMethod(env, tsObj, tsAdd,
(*env)->NewStringUTF(env, "Meier"));
return tsObj;
}
Das Tool javap
Mit javap kann man class-Files bearbeiten. Javap liefert:
Ausgabe des Byte-Codes (-c)
Tabelle der lokalen Variablen (-l)
Signaturen alle Methoden (-s)
Herunterladen