Java:JNI
Sommaire
But
Java Native Interface (JNI) permet à une application Java d'exécuter du code natif placé dans des bibliothèques chargées dynamiquement (DLL sous Windows et SO sous Linux).
JNI est bien adapté pour accéder à ses propres bibliothèques. Par contre si l'on doit accéder à des bibliothèques tiers, JNA est mieux adapté.
Pré-requis
Pas de pré-requis si ce n'est qu'une plateforme de développement C++ opérationnelle.
Mode opératoire
- Déclarer les méthodes natives dans le code Java:
public native void myMethodA();
- Implémenter le chargement de la bibliothèque dans le code d'initialisation statique de la classe (celle qui contient les déclarations des méthodes natives) - sous MS Windows le fichier se nommera myLibrary.dll et myLibrary.so sous Linux:
static { System.loadLibrary("myLibrary"); }
- Compiler la classe Java:
javac MyClass.java
- Générer les fichier d'en-tête C (extension .h) en exécutant la commande:
javah -jni MyClass
gcc -c -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -o MyClass.o MyClass.c
- Créer le fichier .def correspondant à la bibliothèque:
EXPORTS Java_MyClass_myMethodA
- Créer la bibliothèque:
gcc -shared -o myLibrary.dll MyClass.o MyClass.def
Appel d'objet Java passé en paramètre
Il est possible d'appeler des méthodes d'un objet Java passé en paramètre. Par exemple, si nous avons la fonction native suivante:
public native void loadDeviceTree (final UsbDeviceTreeBuilder builder);
La fonction C aura la signature suivante:
JNIEXPORT void JNICALL Java_org_minetti_test_JniLibrary_loadDeviceTree(JNIEnv *env, jobject obj, jobject builderObject)
Dans le source C de la bibliothèque, notre objet sera du type jobject. Pour appeler une méthode quelconque de cette objet, il faudra d'abord déterminer sa signature en tapant la commande suivante à partir du répertoire où sont compilés les classes Java (ici, le répertoire Maven):
cd target\classes javap -s -p org.minetti.test.UsbDeviceTreeBuilder
Par exemple, ça pourrait donner ceci:
Compiled from "UsbDeviceTreeBuilder.java" public final class org.minetti.test.UsbDeviceTreeBuilder extends java.lang.Object{ private int level; Signature: I public org.minetti.test.UsbDeviceTreeBuilder(); Signature: ()V public void down(); Signature: ()V public void up(); Signature: ()V public void addHostController(java.lang.String); Signature: (Ljava/lang/String;)V public void addRootHub(); Signature: ()V public void addHub(int, int, java.lang.String, java.lang.String, int, java.lang.String, short, short, short); Signature: (IILjava/lang/String;Ljava/lang/String;ILjava/lang/String;SSS)V public void add(int, int, java.lang.String, java.lang.String, int, java.lang.String, short, short, short); Signature: (IILjava/lang/String;Ljava/lang/String;ILjava/lang/String;SSS)V }
Ensuite, nous revenons à notre source C afin d'implémenter, par exemple, l'appel à la méthode addHostController de notre objet UsbDeviceTreeBuilder (signature: (Ljava/lang/String;)V):
jclass builderClass = env->GetObjectClass(builderObject); jmethodID addHostControllerMethod = env->GetMethodID(builderClass, "addHostController", "(Ljava/lang/String;)V"); jstring name = env->NewStringUTF("MyName"); env->CallVoidMethod(builderObject, addHostControllerMethod, name);
Conversions types C en Java
- Pour convertir une chaîne de caractère codé en UTF-8:
char *cStr = "Exemple de chaîne de caractères"; jstring javaStr = env->NewStringUTF(cStr);
- Pour convertir une chaîne de caractère Unicode:
WCHAR *cStr = L"Exemple de chaîne de caractères"; jstring javaStr = env->NewString((const jchar*)buf, wcslen(buf));
Gestion des exceptions
Le code C++ suivant permet d'intercepter les exceptions C pour les renvoyer dans le code Java:
try { ... } catch (const char *message) { env->ThrowNew(env->FindClass("java/lang/Exception"), message); }
Ici, l'exception renvoyée sera de type java.lang.Exception. Ce code doit être obligatoirement placé dans la fonction appelé par le code Java (c'est à dire la fonction native).