Dans ce chapitre, nous allons voir comment on peut mélanger du code écrit dans un autre langage à l'intérieur du code Java .
Précisons tout de suite que cette possibilité ne doit être utilisé que dans des cas exceptionnels. En effet, le fait d'introduire du code natif dans Java enlève un certain nombre d'attraits de ce langage: la portabilité, l'indépenance de la plate forme utilisé, possibilité de téléchargement etc...
Pourquoi donc vouloir mélanger Java avec un autre langage ?
Tout d'abord, Java ne permet d'accéder aux fonctionnalités spécifique d'un système. Par exemple, il n'est pas possible avec Java de manipuler des cartes d'acquisition. Toutes ces manipulations proches du système se fera donc dans un autre langage et sera interfacer avec Java pour le reste de l'application.
Ensuite, il peut être intéressant d'interfacer un code récent Java avec une application existante écrite dans un autre langage. La récriture complète serait bien trop fastidieuse.
Enfin, il existe des application dans lesquelles des bouts critiques doivent s'exécuter de manière extrèmement efficace. On pourra alors récrire ces parties cruciales dans un langage plus efficace (plus proche de la machine).
L'interface Java permettant de lier du code natif au code Java est désigné par le terme JNT (Java Native Interface) Dans ce qui va suivre nous allons plus particulièrement nous intéresser à la programmation des méthodes natives en C et la fin du chapitre présentera les particularité de ce type de lien avec C++.
Dans la communication entre Java et un autre langage comme C qui n'est pas un langage orienté objet, il y a plusieurs problèmes à résoudre:
Appletet natif
Une méthode native est une méthode dont l'implantation est réalisée dans un autre langage que Java généralement du C ou C++. Une méthode est qualifiée de native, lorsque il comporte le qualifier (ou modifier ) natif.
Il n'est évidemment pas question de donner une quelconque implantation de cette méthode puisqu'elle censé être faite dans un autre langage que Java .
public native void afficherHello();
Après implantation du code natif C, on devra la copiler et produire une bibliothèque dynamique. Cette bibliothèque devra être chargée au chargement de la classe Hello. Le chargement d'une bibliothèque se fait avec la méthode System.load(...) ou System.loadLibrary(...). En supposons que cette bibliothèque se nomme libhello.so (pour UNIX ) ou libhello.dll (pour Windows ), voici le code complet de la classe Hello.
public class Hello public native void afficherHello(); static System.load("Hello");
Avant d'implanter la fonction C qui sera mis en correspondance avec la méthode afficherHello, il faut
La glue est constituée d'un fichier .h qui sera automatiquement produit par la commande javah.
Cette commande produit le fichier Hello.h
% javac Hello % javah -jni Hello
vous aurez noté que ce fichier convient également pour interfacer Java avec C++ .
#include/* Header for class Hello */ #ifndef _Included_Hello #define _Included_Hello #pragma pack(4)
typedef struct ClassHello char PAD; /* ANSI C requires structures to have a least one member */ ClassHello; HandleTo(Hello);
#pragma pack()
#ifdef __cplusplus extern "C" #endif extern void Hello_afficherHello(struct HHello *); #ifdef __cplusplus #endif #endif
Ce fichier va servir de glue en Java et C . A présent, on va pouvoir implanter la méthode native; il faudra respecter un certain nombre de conventions dans cette implantation:
JNIEXPORT type JNICALL Java_NomDePackage _NomDeClasse _NomDeLaMethode (JNIEnv *env, jobject obj, ...)
Lorsque la classe appartient au package sans, comme dans notre exemple, le nom du package et le caractère _ le précédant sont omis. Pour cet exemple, le prototype de fonction C sera donc de la forme
On remarquera que toutes ces méthodes admenttent au moins deux arguments: JNIEnv *env et jobject obj.
JNIEXPORT void JNICALL Java_Hello _afficherHello (JNIEnv *env, jobject obj)
La variable env est un pointeur vers l'environnment Java et c'est cette dernière qui permet de récupérer les arguments de la méthode et tout autre information provenant de l'environnement Java .
La variable obj est une référence vers l'objet sur lequel la méthode s'applique. Lorsqu'il s'agit d'une méthode static, la valeur de cette référence est nulle.
#include <jni.h> #include "Hello.h" #include <stdio.h> JNIEXPORT void JNICALL Java_Hello_afficherHello(JNIEnv *env, jclass obj) { printf("Coucou !!!\n"); }
Cette commande est évidemment dépendante du système et de la configuration du système utilisée.
gcc -shared -I/usr/include/jdk-1.1.5/ -I/usr/include/jdk-1.1.5/genunix/ HelloImpl.c -o libHello.so
Une fois compilé cette classe, il ne reste plus qu' exécuter:
public class Test public static void main(String args[]) { Hello h = new Hello(); h.afficherHello(); } }
Hello !!!
\u0000 et
\u007f (caractères ASCII ) ne sont pas modifiés. Quant
aux caractères Unicode qui ne sont pas dans cette plage, le
caractère \udddd est transformer en la suite de caractères
_0dddd.
Pour implanter correctement les méthodes, il faut établir un moyen de convertir les données Java en données et inversement. A chaque classe Java correspond une structure (struct) C et chaque membre de la classe garde le même qu'en Java , à la conversion des caractères Unicode près.
A chaque type primitif, il existe un type C :
| Type Java | Type C | Taille (en octet) boolean |
| boolean | jboolean | 1 | byte | jbyte | 1 | char | jchar | 2 | short | jshort | 2 | int | jint | 4 | long | jlong | 8 | float | jfloat | 4 | double | jdouble | 8 |
long ?
Lorsq'un type primitif est passé en argument d'une méthode native, ce passage sa fait par valeur.
class Param { public static native int somme(int x, int y); static { System.loadLibrary("Param"); } }
class ParamEx { public static void main(String[] args) { System.out.println(Param.somme(3, 5)); } }
#include <jni.h> #include "Param.h" #include <stdio.h> JNIEXPORT jint JNICALL Java_Param_somme(JNIEnv *env, jobject obj, jint X, jint Y) { return X + Y; }
Les objets Java ou les tableaux sont désigné en C par un jobject. Lorsq'une instance d'une classe est passée en argument d'une méthode native, ce passage sa fait par référence.
Java met également à notre disposition les types plus fin pour minimiser les erreurs de programmation.
Les objets de type String ont besoin d'une conversion avant leur utilisation en C; et pour ce faire, on dipose de la méthode GetStringUTFChars. Inversement, la méthode NewStringUTF transforme une chaîne C en jstring. La méthode ReleaseStringUTFChars informe la machine Java qu'il peut récupérer la place consommer par la transformation de la chaîne.
class Str { public static native Str conc(String s1, String s2); static { System.loadLibrary("Str"); } }
class StrEx { public static void main(String[] args) { System.out.println(Str.conc("Coucou, ", "Tout le monde")); } }
#include <jni.h> #include "Str.h" #include <string.h> JNIEXPORT jobject JNICALL Java_Str_conc(JNIEnv * env, jclass obj, jstring js1, jstring js2) { jstring js ; const char *s1 = (*env)->GetStringUTFChars(env, js1, 0); const char *s2 = (*env)->GetStringUTFChars(env, js2, 0); char *s = (char *)malloc(strlen(s1)+strlen(s2)); s = strcat(strcpy(s, s1), s2); (*env)->ReleaseStringUTFChars(env, js1, s1); (*env)->ReleaseStringUTFChars(env, js2, s2); js = (*env)->NewStringUTF(env, s); free(s); return js; }
Comme les jstring, les tableaux Java ne peuvent se manipuler directement: il faut passer par une phase de conversion.
A TERMINER
class Tab { public static native int max(int [] t); static { System.loadLibrary("Tab"); } }
class TabEx { public static void main(String[] args) { int [] tab = {4, 7, 5, 9, 2, 0, 1}; System.out.println(Tab.max(tab)); } }
#include <jni.h> #include "Tab.h" #include <string.h> JNIEXPORT jint JNICALL Java_Tab_max(JNIEnv * env, jclass obj, jintArray t) { int i; jsize len = (*env)->GetArrayLength(env, t), max = -1; jint *body = (*env)->GetIntArrayElements(env, t, 0); for (max = body[0], i=1; i<len; i++) if (max < body[i]) max = body[i]; (*env)->ReleaseIntArrayElements(env, arr, body, 0); return max; }
A TERMINER
| Signature | Type Java Z |
Par exemple, la méthode
a pour signature
long f (int n, String s, int[] arr);
(ILjava/lang/String;[I)J
Un programme C peut avoir accès aux champs (static ou pas) d'une classe Java et ce à travers les deux méthodes GetStaticFieldID et GetFieldID. Ces fonctions retournent un jfieldID.
Dans cet exemple, fid1, fid2 et fid2 sont respectivement des pointeurs vers les champs ii de type int, ss de type String et tab de type tableau d'int.
jfieldID fid1 = (*env)->GetStaticFieldID(env, cls, "ii", "I"); jfieldID fid2 = (*env)->GetFieldID(env, cls, "ss", "Ljava/lang/String;"); jfieldID fid3 = (*env)->GetFieldID(env, cls, "tab", "[I");
Avec ces jFieldID, il est alors poosible d'accéder aux valeurs de ces champs et/ou de les modifier. Les fonctions d'accès aux champs sont
Quant à la modification des valeures de champs, on devra utiliser le sméthodes suivantes:
jTypePrimitif GetTypePrimitif Field(jobject obj, jfieldID fid) jobject GetObjectField(jobject obj, jfieldID fieldID) jTypePrimitif GetStaticTypePrimitif Field(jobject obj, fid fid) jobject GetStaticObjectField(jclass clazz, jfieldID fieldID)
void SetTypePrimitif Field(jobject obj, jfieldID fieldID, jTypePrimitif val) void SetObjectArrayElement(jobjectArray array, jsize index, jobject val) void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) void SetStaticTypePrimitif Field(jobject obj, jfieldID fieldID, jTypePrimitif val)
class Obj { static int ii = 2222; int iii = 2222; float ff = 234.34F; String ss = "Coucou !"; int [] tab; static { System.loadLibrary("Obj"); } public Obj(int s) { tab = new int[s]; for (int i = 0; i < s ; i++) tab[i] = i; } public String toString() { String s = null; s += "int : " + ii + "\n"; s += "float : " + ff + "\n"; s += "String : " + ss + "\n"; s += "int [] : [" ; for (int i = 0; i < tab.length ; i++) s += tab[i]+" "; s += "]\n"; return s; } public native void afficher() ; }
class ObjEx { public static void main(String[] args) { Obj t = new Obj(10); t.afficher(); System.out.println(t); } }
#include <jni.h> #include "Obj.h" #include <string.h> JNIEXPORT void JNICALL Java_Obj_afficher(JNIEnv * env, jobject obj) { int i; jint *tab, ii; jsize len; jfloat ff; jstring jstr; const char *ss; jobject jtab; jfieldID fid; jclass cls = (*env)->GetObjectClass(env, obj); fid = (*env)->GetStaticFieldID(env, cls, "ii", "I"); if (fid == 0) return; ii = (*env)->GetStaticIntField(env, cls, fid); printf("int : %d \n", ii); (*env)->SetStaticIntField(env, cls, fid, 444); fid = (*env)->GetFieldID(env, cls, "ff", "F"); if (fid == 0) return; ff = (*env)->GetFloatField(env, obj, fid); printf("float : %f \n", ff); (*env)->SetFloatField(env, obj, fid, 0.5f); fid = (*env)->GetFieldID(env, cls, "ss", "Ljava/lang/String;"); if (fid == 0) return; jstr = (*env)->GetObjectField(env, obj, fid); ss = (*env)->GetStringUTFChars(env, jstr, 0); printf("String : %s \n", ss); (*env)->ReleaseStringUTFChars(env, jstr, ss); jstr = (*env)->NewStringUTF(env, "Bonjour !"); (*env)->SetObjectField(env, obj, fid, jstr); fid = (*env)->GetFieldID(env, cls, "tab", "[I"); if (fid == 0) return; jtab = (*env)->GetObjectField(env, obj, fid); tab = (*env)->GetIntArrayElements(env, jtab, 0); len = (*env)->GetArrayLength(env, jtab); printf("int [] : ["); for (i = 0; i < len ; i++) printf("%d ", tab[i]); printf("]\n"); }
Cette section présente la manière d'invoquer une méthode Java à partir d'une fonction C .
Pour l'invocation d'une méthode d'instance (non statique), il faut
jclass GetObjectClass(JNIEnv *env, jobject obj);
clazz désigne la classe, name le nom de la méthode, sig la signature de la méthode sous la forme d'une chaîne de caractères selon les conventions exposées en 43.5.
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
class CtoJava { static int ii = 2222; int iii = 2222; float ff = 234.34F; String ss = "Coucou !"; int [] tab; static { System.loadLibrary("CtoJava"); } public CtoJava(int s) { tab = new int[s]; for (int i = 0; i < s ; i++) tab[i] = i; } public String toString() { String s = new String(); s += "int : " + ii + "\n"; s += "float : " + ff + "\n"; s += "String : " + ss + "\n"; s += "int [] : [" ; for (int i = 0; i < tab.length ; i++) s += tab[i]+" "; s += "]\n"; return s; } public native void jafficher() ; }
class CtoJavaEx { public static void main(String[] args) { CtoJava t = new CtoJava(10); t.jafficher(); System.out.println(t); } }
#include <jni.h> #include "CtoJava.h" #include <string.h> JNIEXPORT void JNICALL Java_CtoJava_jafficher(JNIEnv * env, jobject obj) { const char *str; jclass cls = (*env)->GetObjectClass(env, obj); jstring s; jmethodID mid = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;"); if (mid == 0) return; printf("-------------------------------------\n"); s = (*env)->CallObjectMethod(env, obj, mid); str = (*env)->GetStringUTFChars(env, s, 0); printf("%s", str); (*env)->ReleaseStringUTFChars(env, s, str); printf("-------------------------------------\n"); }
Pour l'invocation d'une méthode de classe (statique), il faut
clazz désigne la classe, name le nom de la méthode, sig la signature de la méthode sous la forme d'une chaîne de caractères selon les conventions exposées en 43.5.
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
NativeType CallStatic<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
Des méthodes Java sont suceptibles de générer des exceptions; il en est de même de certaines fonctions de l'interface JNI. Par exemple, la méthode getFieldID peut lever l'exception NoSuchFieldError. La pluspart des fonction JNI à la fois retourne un code d'erreur et lancent un exception.
Asynchronous
La structure de donnée jthrowable code une exception en C . La méthodeExceptionClear annule l'exception et laméthode ExceptionOccurred retourne
jthrowable exception; ... // méthode ou fonction JNI engendrant une exception exception = (*env)->ExceptionOccurred(env); if (exception) { (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env);
Une fonction native peut lever une exception avec les fonctions ThrowNew ou Throw dont l'un des arguments est une structure de type jclass.
jint Throw(JNIEnv *env, jthrowable obj); jint ThrowNew(JNIEnv *env, jclass clazz, const char *message);
jclass newExceptionCls = (*env)->FindClass(env, "java/lang/IllegalArgumentException"); if (newExceptionCls == 0) return; (*env)->ThrowNew(env, newExceptionCls, "thrown from C code"); }
A TERMINER
A TERMINER
public class Prog { public static void main(String[] args) { System.out.println("Hello World" + args[0]); } }
#include <jni.h> main() { JNIEnv *env; JavaVM *jvm; JDK1_1InitArgs vm_args; jint res; jclass cls; jmethodID mid; jstring jstr; jobjectArray args; /* IMPORTANT: specify vm_args version # if you use JDK1.1.2 and beyond */ vm_args.version = 0x00010001; JNI_GetDefaultJavaVMInitArgs(&vm_args); vm_args.classpath = "/disk2/linux/tourai/Java/Perso/JavaPoly/Programmes/jni:\ /usr/lib/jdk-1.1.5/classes.zip"; res = JNI_CreateJavaVM(&jvm,&env,&vm_args); if (res < 0) { fprintf(stderr, "Can't create Java VM\n"); exit(1); } cls = (*env)->FindClass(env, "Prog"); if (cls == 0) { fprintf(stderr, "Can't find Prog class\n"); exit(1); } mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V"); if (mid == 0) { fprintf(stderr, "Can't find Prog.main\n"); exit(1); } jstr = (*env)->NewStringUTF(env, " from C!"); if (jstr == 0) { fprintf(stderr, "Out of memory\n"); exit(1); } args = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), jstr); if (args == 0) { fprintf(stderr, "Out of memory\n"); exit(1); } (*env)->CallStaticVoidMethod(env, cls, mid, args); (*jvm)->DestroyJavaVM(jvm); }
unix: cc -I-L -ljava invoke.c windows: cl -I -MT invoke.c -link +lb
A TERMINER
jint GetVersion(JNIEnv *env);
jclass DefineClass(JNIEnv *env, jobject loader, const jbyte *buf, jsize bufLen); jclass FindClass(JNIEnv *env, const char *name); jclass GetSuperclass(JNIEnv *env, jclass clazz); jboolean IsAssignableFrom(JNIEnv *env, jclass clazz1, jclass clazz2);
jint Throw(JNIEnv *env, jthrowable obj); jint ThrowNew(JNIEnv *env, jclass clazz, const char *message); jthrowable ExceptionOccurred(JNIEnv *env); void ExceptionDescribe(JNIEnv *env); void ExceptionClear(JNIEnv *env); void FatalError(JNIEnv *env, const char *msg);
jobject NewGlobalRef(JNIEnv *env, jobject obj); void DeleteGlobalRef(JNIEnv *env, jobject globalRef); void DeleteLocalRef(JNIEnv *env, jobject localRef);
jobject AllocObject(JNIEnv *env, jclass clazz); 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); jclass GetObjectClass(JNIEnv *env, jobject obj); jboolean IsInstanceOf(JNIEnv *env, jobject obj, jclass clazz); jboolean IsSameObject(JNIEnv *env, jobject ref1, jobject ref2);
jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID); void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...); NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args); NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args); NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...); NativeType CallNonvirtual<type>MethodA(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, jvalue *args); NativeType CallNonvirtual<type>MethodV(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, va_list args);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig); NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID); void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig); NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...); NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args); NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
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); 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);
jsize GetArrayLength(JNIEnv *env, jarray array); jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value); ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length); NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy); void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode); void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,jsize start, jsize len, NativeType *buf); void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
jint RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods); jint UnregisterNatives(JNIEnv *env, jclass clazz);
jint MonitorEnter(JNIEnv *env, jobject obj); jint MonitorExit(JNIEnv *env, jobject obj);
jint GetJavaVM(JNIEnv *env, JavaVM **vm);