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 : 90

AJOUTER UN COMMENTAIRE