Introduction à JNI
Ce tutoriel d’initiation à JNI illustre comment Java Native Interface peut être utilisé, afin de permettre à une application Java de communiquer avec un programme C.
Avant la lecture de cet article, les prérequis suivants sont nécessaires :
- Savoir créer une bibliothèque en C
- Savoir compiler une classe Java
- Savoir lancer un programme fourni avec le JDK en ligne de commandes
Qu’est JNI ?
JNI est une interface de programmation qui permet d’utiliser le langage Java avec d’autres langages de programmation.
Il existe différentes manières d’utiliser cette API. Une première consiste à appeler le code Java à partir du code natif, (ie, à partir du code écrit dans un langage différent du Java) en mettant en place la machine virtuelle, alors qu’une autre appelle le code natif à partir de la machine virtuelle préalablement lancée. C’est cette dernière méthode qui sera le propos de cet article.
Mettre en place JNI
A partir d’un programme écrit en Java, nous allons appeler une fonction écrite en C. Cette dernière devra afficher un message à l’écran. La mise en place d’une telle application se fait en six étapes bien distinctes qui sont : la définition du point d’entrée de l’application en langage Java, la compilation de l’application Java, la génération du fichier header correspondant, la définition de la fonction du fichier header, la création de la bibliothèque dynamique ainsi que l’exécution de l’application Java.
Mise en place du point d’entrée de l’application
Afin qu’une application Java puisse utiliser des fonctions écrites en C, il faut que les fonctions en question soient chargées dynamiquement par une bibliothèque (*.so, *.dll, etc.). Comme dans l’exemple qui suit :
package jnitest; //JNItest.java public class JNItest { public native void getMystring(); static { System.load("c:\JNItest.dll"); } public static void main(String[] args) { JNItest f= new JNItest(); f.getMystring() ; } }
Le fichier ci-dessus est composé d’une simple classe nommée « JNItest » dont la fonction principale (main) appelle une fonction nommée « getMystring » par le biais d’une bibliothèque dynamique « JNItest.dll ».
Toute application servant de point d’entrée doit comporter les éléments suivants : la déclaration de la fonction, le chargement de la bibliothèque dynamique et l’appel de la fonction.
La déclaration de la fonction de la bibliothèque
Il s’agit d’une étape très importante, car elle indique au client la signature de la fonction. Cette dernière doit être précédée du mot clef « native ».
public native void getMystring();
Le chargement de la bibliothèque
Dans l’exemple précédent, la bibliothèque est chargée à l’aide de la commande « System.load » qui prend en paramètre une variable de type « String » contenant l’adresse complète de la libraire à charger.
System.load("c:\JNItest.dll");
L’appel de la fonction C
Il s’agit de l’étape dans laquelle la fonction native est utilisée. Dans le code ci-dessus, la fonction « getMystring » est appelée de la même manière qu’une fonction Java quelconque.
f.getMystring();
Compilation du point d’entrée
Après l’écriture de la classe principale, il convient de la compiler à l’aide de la commande adéquate.
javac JNItest.java
Génération du header
La classe résultante à la compilation est ensuite utilisée comme suit, afin de générer le fichier en-tête.
javah -jni JNItest
En éditant le fichier « JNItest.h », on trouve les instructions suivantes.
//JNItest.h #include <jni.h> #ifndef _Included_JNItest #define _Included_JNItest #ifdef __cplusplus extern "C" { #endif /* * Class: JNItest * Method: getMystring * Signature: ()V */ JNIEXPORT void JNICALL Java_JNItest_getMystring (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
Il s’agit de la déclaration du prototype de la fonction native que l’application principale Java accèdera.
La déclaration d’une fonction native se fait en précisant le type de la valeur de retour et le nom de la fonction.
Le type de la valeur de retour
Le type de la valeur de retour doit être un des types JNI suivants, ie : jdouble, jchar, jbyte, jboolean, jfloat, jint, jlong, jobject ou jshort.
Il convient de noter que le langage Java manipule des types « string » de 16 bits alors qu’en C ils ne font que 8 bits. C’est pourquoi, une chaîne provenant d’un programme Java, doit préalablement être passée à la fonction « GetStringUTFChars ».
const char *str = (*env)->GetStringUTFChars(env, text, NULL);
Cette fonction prend en paramètre une variable de type « JNIEnv », une variable de type « string » contenant le texte à convertir ainsi qu’une variable de type booléen qui peut être initialisé à « NULL ». La variable de type « JNIEnv » sera décrite plus bas. Cette fonction retourne une chaîne de caractère. La manipulation doit obligatoirement se terminer par une libération de la mémoire, exécutée par l’appel à la fonction « ReleaseStringUTFChars » qui prend en paramètre une variable de type « JNIEnv *» ainsi que le texte convertis et la chaîne de caractère retournée par la fonction « GetStringUTFChars ».
(*env)->ReleaseStringUTFChars(env, text, str);
D’autres fonctions existent permettant de retourner des chaînes de type « jstring » manipulable par le langage Java ainsi que des fonctions permettant de déterminer la taille d’une chaîne « jstring ».
Le nom de la fonction
Le nom de la fonction native est composé des éléments suivants :
Java_ + nom de la classe Java appelante + nom de la fonction native
Java_JNItest_getMystring
Toutes les fonctions natives qui ont pour but d’être utilisées par JNI comportent deux paramètres par défaut de types « JNIEnv * » et « jobject ». Le premier est un pointeur sur l’environnement JNI et l’autre une référence sur la méthode appelante.
Ces paramètres doivent êtres de types JNI.
Pour avoir plus d’information concernant les types utilisés dans JNI, il est possible de se référer à la documentation officielle située à l’adresse suivante :
http://java.sun.com/developer/onlineTraining/Programming/JDCBook/jnistring.html
La fonction déclarée dans le fichier entête, ie celle utilisée dans notre exemple « JNItest.h » se nomme « getMystring », elle ne prend aucun paramètre en entrée et ne renvoie rien. Elle se contente d’afficher un simple message à l’écran.
Définition des méthodes du header
//JNItest.c #include <jni.h> #include "JNItest.h" JNIEXPORT void JNICALL Java_JNItest_getMystring(JNIEnv *env, jobject obj) { printf("Hello So@t \n"); }
La fonction déclarée dans le header peut maintenant être définies comme ci-dessus.
Création de la bibliothèque
La commande de compilation du fichier ci-dessus est la suivante si le compilateur utilisé est gcc.
gcc -c -I"C:\jdk1.4\include" -I"C:\jdk1.4\include\win32" -o JNItest.o JNItest.c
La commande permettant de créer le fichier de définition « JNItest.def » est la suivante :
gcc -shared -o JNItest.dll JNItest.o JNItest.def
Ce fichier met en évidence la fonction qui sera partagée. Ce fichier doit être composé des éléments suivants :
EXPORTS <function name 1> <function name 2> … <function name n>
Le fichier « JNItest.def » de notre tutoriel contient le code suivant :
EXPORTS Java_JNItest_getMystring
La commande de création de la bibliothèque est la suivante
gcc -shared -o JNItest.dll JNItest.o JNItest.def
Exécution de l’application
Le lancement de l’application se fait de la manière suivante :
java JNItest
Le résultat est le suivant :
Hello So@t
Ainsi se termine ce tutoriel d’initiation à JNI. Nous avons vu comment utiliser JNI de manière simple, mais en utilisant cette technique, il est possible de faire des choses très puissantes. Telles qu’accéder aux méthodes et propriétés d’une classe Java à partir d’une fonction C, gérer des exceptions ainsi que le multi-processing. En utilisant ces techniques avec habileté, JNI peut très vite devenir un outil très utile pour un développeur Java.
Nombre de vue : 274