vendredi 28 mai 2010

Mettre des librairies natives dans un jar, et les utiliser

Certaines applications Java peuvent utiliser du code natif C ou C++ grâce au framework JNI. On parlera pas ici de la manière d'utiliser l'interface JNI, mais de la manière de l'emballer dans l'application finale pour que l'utilisateur n'ait pas à s'en soucier.

En général, une fois le développement terminé, on place les classes dans un fichier jar et on délivre l'application sous cette forme, avec ou sans enrobage supplémentaire tel qu'un installeur.
Avec JNI, outre les classes, on se retrouve avec des librairies natives: une par plateforme supportée.


La question est de savoir que faire de ces librairies. On peut bien sûr les mettre à côté, mais cela oblige à spécifier leur emplacement au moyen de paramètres sur la ligne de commande Java. exemple: java -Dava.library.path=lib_dir .....
Au delà de cette lourdeur, il reste la question de l'identification de la librairie associée à la plateforme. On va voir comment affranchir l'utilisateur de tous ces problèmes.

Dans un premier temps, comme la présence de librairies natives à coté des JAR fait désordre, on va profiter du fait que le jar n'est rien d'autre que tar compressé. On peut y mettre ce que l'on veut, on va donc y mettre nos librairies.

1) Bien nommer les librairies:
On va d'abord renommer nos librairies afin d'identifier clairement l'architecture à laquelle elles se rattachent.
Il existe 2 propriétés java indiquant l'architecture sur laquelle tourne la machine java:
  • os.name: retourne le nom de l'OS (Linux, Win ou Mac)
  • os.arch: retourne l'architecture matérielle (i386, universal...)
On va renommer nos librairies sous la forme suivante: os.name_os.arch.lib, le tout en minuscules afin d'éviter les problèmes de casse. Ainsi, sous Linux 32bits, ma librairie s'appellera linux_i686.lib.

2) Placer les libraires à la racine du fichier JAR.
Cela n'est pas obligatoire, mais ca facilite. Il faut comprendre que le fichier JAR est vu par la JVM comme un répertoire accessible pour rechercher des ressources. On peut donc y organiser nos fichiers à notre guise (sauf pour les .class qui doivent respecter la hiérarchie des paquetages).

exemple: jar cvf mon_application.jar *.lib .....

3) Intégrer dans le code java le chargement des librairies natives.
Avant de faire appel à des méthodes natives, il faut charger le code natif. Le loader de Java sais bien le faire à condition de lui indiquer où chercher.
Cela se fait par étapes:
  1. Construire le nom de la librairie à partir des ressources os.*
  2. Localiser la ressource.
  3. Extraire la bonne librairie
  4. Charger la bonne librairie.
En bon java cela donne (on suppose que la classe native est sqlite.tools.ASCIIDataFileLoader):

public static void loadNativeLibrary() throws Exception {
if( LIB_LOADED == false ) {
String libname = System.getProperty("os.name")
+ "_"
+ System.getProperty("os.arch")
+ ".lib";
ClassLoader cl = Class.forName("sqlite.tools.ASCIIDataFileLoader")
.getClassLoader();
InputStream in = cl.getResourceAsStream(libname);
if (in == null) {
throw new Exception("libname: "
+ libname
+" not found (supposed to be in sqliteimporter.jar)");
}
/*
* Extract the lib file and link it with the app
*/
File tmplib = File.createTempFile("libsqlitejdbc-", ".lib");
tmplib.deleteOnExit();
OutputStream out = new FileOutputStream(tmplib);
byte[] buf = new byte[1024];
for (int len; (len = in.read(buf)) != -1;) {
out.write(buf, 0, len);
}
in.close();
out.close();
System.load(tmplib.getAbsolutePath());

LIB_LOADED = Boolean.TRUE;
}
}


Il faut ensuite lier l'appel de cette méthode à l'appel de chaque méthode native:


class .......
.....
static int importTSV(String table, String file, String db_file)
throws Exception {
ASCIIDataFileLoader.loadNativeLibrary();
return importASCIIFile(table, file, "\t", db_file);
}

native static int importASCIIFile(String table, String file
, String separ, String db_file) throws Exception;
}

Cela ne marche bien que si notre application n'utilise qu'une seule librairie native. Dans le cas contraire, il faut un peu compliquer le nommage afin d'y inclure le nom de la classe ou du paquetage ou de n'importe quoi d'autre évitant toute ambiguïté.

Aucun commentaire: