STS IRIS Jean-Claude CABIANCA Mise en œuvre de JNI (Java Native Interface) à partir de Visual Studio C++ I – INTRODUCTION I.1 – Versions des logiciels Microsoft Visual Studio 2008 Netbeans 6.9 I.2 – Généralités Le JNI (Java Native Interface) est un framework qui permet au code Java s'exécutant à l'intérieur de la JVM d'appeler et d'être appelé par des applications natives (c'est-à-dire des programmes spécifiques au matériel et au système d'exploitation de la plate-forme concernée), ou avec des bibliothèques logicielles basées sur d'autres langages (C, C++, assembleur, etc.). II – MISE EN OEUVRE DE JNI II.1 – Cahier des charges On se propose de concevoir une dll nommée dllstat permettant de faire un calcul de valeur moyenne sur un lot de 2 valeurs entières. Cette dll correspond à celle qui est fournie par le constructeur. Ensuite nous allons créer une nouvelle dll nommée dllcalc permettant de calculer l'erreur relative et utilisant la fonction fournit par dllstat, ceci afin de pouvoir l'utiliser via JNI avec du code Java. Le fonctionnement peut être représenté par le diagramme suivant : Visual Studio C++ et JNI page 1/8 STS IRIS Jean-Claude CABIANCA II.2 – Génération et test d'une DLL avec Visual C++ (dllstat) 2.2.1. Génération de la dll Les première étapes de la génération d'une DLL (ici dllstat.dll )sont similaires à celles d'une application classique (Application Console). La solution (et donc le projet) s'appelle dllstat. Il faut par contre choisir « DLL » dans la dernière boîte de dialogue de l'assistant à la création d'application. Deux fichiers .cpp apparaissent alors dans l'explorateur de solution : - Le fichier dllmain.cpp est inséré automatiquement par Visual Studio afin de créer un point d'entrée pour la DLL. - Le fichier dllstat.cpp (même nom que le projet et que la DLL qui sera créée) permettra de définir les fonctions de la DLL. Il faut ensuite créer le fichier d'en-tête de la DLL (dllstat.h) : Le contenu du fichier .h va par contre varier. En effet, il faut prototyper les fonctions en employant les mots clés __declspec(dllimport) afin de signifier au programme source qui voudra appeler la fonction qu'elle provient d'une DLL : Visual Studio C++ et JNI page 2/8 STS IRIS Jean-Claude CABIANCA La définition de la fonction CalculerMoyenne() dans dllstat.cpp nécessite aussi une adaptation. Il faudra utiliser les mots clés __declspec(dllexport) pour signifier que les fonctions seront «exportées» pour des programmes (modules logiciels) situés « hors » de la DLL (cette directive permet ne pas avoir à créer de fichier .def utilisant la directive EXPORTS) : Afin de faciliter l'utilisation de la DLL, la technique employée sera liée à l'utilisation d'une bibliothèque d'importation. Une bibliothèque d'importation (fichier .lib) n'est pas une librairie statique mais un fichier qui permet à un programme d'appeler les fonctions d'une DLL de manière transparente, comme s'il utilisait une bibliothèque statique. Pour que cette bibliothèque d'importation soit créée lors de la génération du projet et de la DLL, il faut s'assurer que dans les propriétés du projet (Propriétés de configuration > Editeur de liens > Avancé) , le champ Bibliothèque d'importation soit renseigné : Visual Studio C++ et JNI page 3/8 STS IRIS Jean-Claude CABIANCA Le projet peut être maintenant généré comme vu précédemment. On trouve alors dans le répertoire Release de la solution les fichiers dllstat.dll (DLL) et dllstat.lib (bibliothèque d'importation) : On dispose maintenant d'une solution comportant les fichiers dllstat.dll (DLL) et dllstat.lib (bibliothèque d'importation) au même titre que celle qui est livrée par le fabricant d'une carte d'entrées-sorties. 2.2.2. Test de la dll Nous allons créer un nouveau projet nommer testdllstat afin de tester la dll crée précédemment. Afin de pouvoir appeler les fonctions de la DLL tout comme s'il s'agissait d'une librairie statique, il faut inclure au projet la librairie d'importation, en se positionnant sur l'onglet Fichiers de ressources, de l'explorateur de ressource, il faut cliquer droit et sélectionner Ajouter > Élément existant. Il faut ensuite sélectionner la librairie d'importation (dllstat.lib) afin de l'associer aux Fichiers de ressources. Visual Studio C++ et JNI page 4/8 STS IRIS Jean-Claude CABIANCA Nous avons le résultat suivant : II.3 – JNI 2.3.1. Démarche Pour mettre en œuvre JNI, il faut suivre les étapes suivantes : – – – – Déclarer des méthodes natives (prototype Java) et charger les librairies natives; Générer le header C/C++; Implémenter le code natif; Compiler et générer la librairie native. 2.3.2. Déclaration des méthodes natives La première étape consiste donc à écrire le prototype Java des méthodes natives. Dans le code de la classe TestJNI1.java qui suit, on retrouve une méthode correspondant à la méthode native à appeler CalculerErreur(). Ce code peut être compilé normalement sans problème puisque le compilateur ne vérifie pas les liens vers les méthodes natives (ne charge par la librairie dynamique), par contre l'exécution génèrera une belle exception puisque les méthodes natives correspondantes n'existent pas encore. Visual Studio C++ et JNI page 5/8 STS IRIS Jean-Claude CABIANCA Remarque : Il est impératif de charger les librairies (présentes dans le dossier dll du projet Java) pendant le chargement de la classe afin que les méthodes natives puissent être utilisées sans problème. Pour cela il suffit d'ajouter un bloc static dans le corps de la classe avec l'instruction System.loadLibrary. 2.3.3. Génération du header C++ Une fois cette classe compilée, il faut utiliser l'outil javah sur la classe générée et non sur le code source. L'outil javah, fourni avec le JDK, permet de générer un fichier d'en-tête C/C++. Ce dernier s'utilise comme la commande java et nécessite donc un nom de classe complet (c'est-àdire avec le package) : javah -o D:\entete.h -classpath D:\sources\java\testjni\build\classes testjni.TestJNI1 entete.h : nom du fichier .h généré par javah -classpath : option précisant le chemin vers la classe contenant les méthodes natives ../build/classes : chemin vers la classe Visual Studio C++ et JNI page 6/8 STS IRIS Jean-Claude CABIANCA testjni.TestJNI1 : nom du paquet.nom de la classe Ce qui nous génèrera dans le cas présent un fichier nommé entete.h contenant le code suivant : /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class testjni_TestJNI1 */ #ifndef _Included_testjni_TestJNI1 #define _Included_testjni_TestJNI1 #ifdef __cplusplus extern "C" { #endif /* * Class: testjni_TestJNI1 * Method: CalculerErreur * Signature: (II)I */ JNIEXPORT jint JNICALL Java_testjni_TestJNI1_CalculerErreur (JNIEnv *, jobject, jint, jint); #ifdef __cplusplus } #endif #endif 2.3.4. Implémentation du code natif Il est maintenant nécessaire de coder les fonctions natives correspondant au prototype généré, ce qui donne pour notre étude de cas le résultat suivant : // dllcalc.cpp : définit les fonctions exportées pour l'application DLL.// #include "stdafx.h" #include "dllstat.h" #include "entete.h" JNIEXPORT jint JNICALL Java_testjni_TestJNI1_CalculerErreur (JNIEnv *, jobject, jint val1, jint val2) { int erreur = (50 * ( val2 - val1 )) / CalculerMoyenne(val1, val2); return (erreur); } Visual Studio C++ et JNI page 7/8 STS IRIS Jean-Claude CABIANCA 2.3.5. Compilation et génération de la librairie native Il nous faut désormais compiler ce bout de code à l'aide de Visual C++. Pour cela il faut spécifier au compilateur l'emplacement des headers natif de JNI qui se trouvent dans le répertoire include du JDK. Il faut donc configurer Visual C++, dans Propriétés de configuration > C/C++ > Général, entrer les chemins des répertoires des include à ajouter dans le champ Autres répertoires Include dans notre cas, C:\Program Files\Java\jdk1.6.0_20\include; C:\Program Files\Java\jdk1.6.0_20\include\win32 : 2.3.6. Utilisation de la librairie native Il suffit maintenant de copier les librairies (dll et obj) dans le dossier dll du projet puis exécuter le projet. On obtient le résultat suivant : Vérification : erreur = (50*(20-10)) / ((20+10)/2) = 500/15 = 33,33 Visual Studio C++ et JNI page 8/8