Club robotique de Sophia-Antipolis

Accueil > POBOTpedia > Programmation > Snippets > Utiliser une DLL dans un programme Java

Utiliser une DLL dans un programme Java

dimanche 11 janvier 2009, par Julien H.

Parfois en robotique et encore plus souvent en électronique, les fabricants imposent d’utiliser une DLL (ou Dynamic Linked Library) pour contrôler leur produit. Par exemple un modem radio (Ant) ou une carte d’interface entrée/sortie pour expérimentations (kits Velleman).

Il s’agit d’un groupe de fonctions réunis dans un fichier binaire (pas de code source disponible) que notre programme doit exécuter. Les DLL sont spécifiques à Windows (il existe aussi des bibliothèques logicielles de principe similaire sous Linux et MaxOS, les .so ou "shared objects", .dylib ou "dynamic library", .sl ou "shared library", etc...) et généralement on développe un programme en Basic ou en C/C++ pour les utiliser.

Dans cet article, nous allons expliquer comment appeler les fonctions d’une DLL, donc accéder aux ressources de notre équipement électronique, depuis un programme Java, en l’occurence depuis l’environnement "Processing" qui est vraiment très simple et très pratique (nous avons réalisé ou utilisé plusieurs applets Processing décrites sur ce site : étude du PID, oscilloscope minimal de Sofian Audry, radar, contrôleur 3D, ...).

La carte électronique que nous utiliserons est une K8055 "VM110" de Velleman, il s’agit d’une "USB Experiment Interface Board" qui est décrite dans un autre article de ce site.

Voici le code source du sketch d’exemple :

Sketch de test K8055
Contient la classe de wrapping pour K8055 et un example de sketch Processing.

Principe d’accès à une DLL depuis Java

Une DLL est considérée comme une ressource "native", car il s’agit d’un binaire appelant des ressources bas-niveau du système d’exploitation, en l’occurence il s’agit ici d’exploiter les ressources HID créées par la liaison USB entre notre ordinateur PC et la carte d’expérimentation Velleman.

La carte K8055 détectée comme HID

Java sait parler depuis longtemps à des ressources natives, via ce qu’on appelle une "Java Native Interface" ou JNI. Il existe de nombreuses utilisations de JNI, généralement pour appeler des programmes en C/C++ recompilés pour JNI puis intégrés au programme Java. Il ne s’agit pas toujours d’accéder à des ressources systèmes, mais parfois d’exporter des algorithmes complexes pour les exécuter plus rapidement ou pour communiquer avec une application non Java qu’il serait trop long, impossible ou inutile de migrer sous Java.

Dans notre cas (DLL fournie par un constructeur), il est impossible de recompiler le code pour indiquer qu’on souhaite utiliser JNI. Il faut trouver une solution d’interface Java / natif qui laisse la DLL inchangée. Un article sur le site web de Processing décrit un hack de Processing basé sur JNative, une extension open-source qui permet ce type d’accès dynamique aux fonctions d’une DLL.

Résumé des opérations à faire pour programmer l’accès

 étudier la DLL cible pour connaître ses fonctions
 installer la DLL dans le répertoire c :/Windows/system32
 créer une classe Java basée sur JNative
 écrire les opérations qui implémentent les accès à chaque fonction de la DLL
 écrire de nouvelles fonctions simplifiées indépendantes de JNative
 compiler le programme Java

Résumé de la chaîne d’exécution

 L’application Processing utilise des opérations simplifiées qui correspondent aux fonctionnalités proposées par la DLL.
 Ces opérations traitent l’encapsulation (configuration, gestion des exceptions, changement des types de données, call-back) en utilisant l’API JNative.
 Les classes de JNative prennent la main pour appeler une DLL JNative en passant les paramètres et récupérant les valeurs en retour.
 La DLL JNative traite chacun des appels Java en gérant l’accès à la DLL cible (directement de natif à natif) permettant ainsi de dépasser la limite de JNI qui nécessiterait une recompilation de la DLL cible.

Cas pratique

On va utiliser Processing et JNative pour écrire un programme d’accès à la carte d’expérimentation permettant de faire des lectures et écritures sur les ports numériques et analogiques via la liaison USB.

Etude de la DLL

Deux DLL sont fournies sur le cédérom d’installation et d’exemples fourni avec le kit K8055 : "K8055D.DLL" et "K8055D_C.DLL". La première est destinée aux programmes Visual Basic et Borland, tandis que la seconde est destinée aux programmes Visual C++, sans autre explication... difficile surtout pour ceux qui ne veulent utiliser aucun de ces logiciels.

Il faut tout d’abord vérifier les fonctions mises à dispostion dans chacune de ces DLL fournies par Velleman : cette étape pourrait être superflue car des spécifications (document PDF en français) sont fournies, mais c’est l’occasion surtout de noter le nom exact de ces fonctions tel que le compilateur utilisé par Velleman a pu les modifier. En effet, le compilateur "décore" ("mangle" en anglais) le nom des fonctions, en ajoutant des caractères devant et derrière. On va utiliser un petit utilitaire "dllexp" (disponible sur le site de Nirsoft) pour vérifier cela.

Voici ce que ça donne pour la DLL "K8055D" :

Liste des fonctions K8055
on vérifie et on note dans la première colonne le nom des fonctions que nous utiliserons.

Et voici ce que ça donne pour la DLL "K8055D_C" :

Liste des fonctions K8055 (2)
par curiosité, ce que contient la seconde DLL, K8055D_C

La première version est plus sympathique, non ? On va commencer avec elle, et éviter de se coltiner des décorations parasites (même si elles ont certainement une utilité qui n’est pas le sujet du présent article... enfin j’espère qu’on aura pas à chercher leur utilité pour faire fonctionner JNative).

Installation de la DLL

Il faut copier K8055D.dll dans un répertoire du système d’exploitation connu de tous les programmes. Sans faire trop d’essais, j’ai choisi "c :/windows/system32" comme indiqué dans l’article d’origine (hack Processing en anglais).

Installation de Processing

C’est une application simple qui ne pose aucun problème. Consultez le site web : www.processing.org.

Installation de JNative

Il faut récupérer la dernière version de JNative : http://jnative.free.fr. On découvre au passage que c’est un projet (au moins en partie) français. Les livrables sont disponibles sous Sourceforge. On télécharge le jar et on le copie sous /libraries/JNative/library. On peut également extraire (à l’intérieur du jar dans la version que j’ai récupérée) le fichier JNative.DLL et le placer à côté du fichier JNative.jar.

Installation de JNative
On ajoute JNative comme une "library" de Processing.

Ecriture de la classe "wrapper" des fonctions de la DLL

Dans un nouveau "sketch" Processing (manipulez un peu l’interface pour vous familiariser avec ses concepts et voir quelques exemples d’applets), créez un fichier avec une extension .java et portant le nom (en respectant la casse, majuscule/minuscule) de votre DLL, ici "K8055D.java". Ce n’est pas habituel car Processing utilise son propre format ".pde" car il simplifie (pas de notion de "classe" dans les sketchs ordinaires). Dans notre cas, on gardera un sketch classique et le fichier .java est une classe secondaire donc on ne perd pas la simplicité de Processing.

Création du sketch et du wrapper java
Deux fichiers seulement : le sketch processing habituel et la classe Java qui utilisera JNative.

Ce fichier "K8055D.java" une fois ajouté à l’interface d’édition de code de Processing, on va écrire son contenu en commençant par l’enveloppe, la "classe" à la sauce Processing :

import processing.core.*;

public class K8055D extends PApplet
{


}

Comme cette classe implémente l’encapsulation d’appels à la DLL par JNative, il faut ajouter toutes les références aux packages de JNative, et s’identifier comme faisant partie du package JNative :

package org.xvolks.jnative.util;

import processing.core.*;

...

Pour importer les références, Processing propose un menu pour chacune des "libraries" stockées dans son répertoire d’installation.

Ajout des imports nécessaires à JNative.

Tout le gros pavé d’import doit être copié en-dessous de la ligne "package" :

package org.xvolks.jnative.util;

import processing.core.*;

import org.xvolks.test.bug.*;
import org.xvolks.jnative.util.ole.*;
import org.xvolks.jnative.util.windows.hooks.*;
[... et une vingtaine de lignes comme ça ...]

public class K8055D extends PApplet
{


}

Maintenant que c’est fait, on va remplir la classe. D’abord en stockant en constante le nom exact de la DLL qui devra être indiqué à chaque fois dans les appels aux fonctions JNative.

public class K8055D extends PApplet
{

  public static final String DLL_NAME = "K8055D.DLL";

}

Puis on va s’intéresser aux appels JNative en eux-même. Le principe est d’avoir une variable pour stocker chaque "pont" entre Java et une fonction de la DLL. Cette variable sera initialisée la première fois que vous ferez appel à une fonction, puis restera prête à recevoir vos invocations suivantes :

  // Native-side function names
  private static JNative nNomDeLaFonction;
  
  // JNative implementation of DLL functions
  public static void NomDeLaFonction() throws NativeException, IllegalAccessException 
  {
    if(nNomDeLaFonction == null) 
    {
      nNomDeLaFonction = new JNative(DLL_NAME, "NomDeLaFonction");  // The actual "decorated" name in the DLL
    }
    nNomDeLaFonction.invoke();  // Finally, execute the native function 
  }

Et c’est tout (dans le cas où il n’y a aucun paramètre en entrée ni en sortie). Il suffit de faire ça pour chacune des fonctions, en remplaçant "NomDeLaFonction" par chacune des fonctions. Devant un film, ça passe vite :)

Notez bien qu’on a créé une instance d’objet JNative en passant le nom de la DLL et le "NomDeLaFonction" de la fonction. Si on utilise la DLL avec décorations, cela donne par exemple :

      nNomDeLaFonction = new JNative(DLL_NAME, "_NomDeLaFonction@4");  // The actual "decorated" name in the DLL

La gestion des paramètres

Prenons un exemple concret : ouvrir la connexion avec la carte. Il s’agit de la fonction OpenDevice qui prend en argument l’adresse de la carte selon la configuration des cavaliers SK5 et SK6 et qui retourne 1 si c’est réussi et 0 si ça ne fonctionne pas.

On va ajouter une ligne dans l’initialisation de l’instance JNative pour indiquer le type de la donnée de retour. Pas besoin de spécifier le type d’argument, il suffit d’ajouter sa valeur lors de l’invocation. Enfin la troisième ligne supplémentaire consiste à récupérer la valeur de retour selon le type concerné pour la retourner à notre tour.

  /**
   *
   */
  public static int OpenDevice(int address) throws NativeException, IllegalAccessException 
  {
    if(nOpenDevice == null) 
    {
      nOpenDevice = new JNative(DLL_NAME, "OpenDevice");  // The actual "decorated" name in the DLL
      nOpenDevice.setRetVal(Type.INT);      
    }
    nOpenDevice.setParameter(0, address);
    nOpenDevice.invoke();  // Finally, execute the native function 
    return nOpenDevice.getRetValAsInt(); // get native call's return value
  }

Il suffit maintenant de compléter chacune des opérations Java en fonction des paramètres et valeurs de retour indiquées dans la documentation des fonctions de la DLL et le tour est joué.

Simplification des appels depuis le sketch Processing

Comme on le voit dans les exemples de code ci-dessus, les opérations ne gèrent pas les exceptions de JNative et les renvoient pour traitement ultérieur. Si on appelait ces fonctions depuis le sketch Processing, il faudrait tout un tas de code "try... catch..." et plein d’imports des classes de la bibliothèque JNative... pas très "Processing" tout ça.

On va donc simplifier grandement en écrivant des fonctions intermédiaires. En plus, on va choisir des noms d’opérations et des types de données indépendant de ce que nous impose la carte K8055. J’ai choisi d’implémenter les opérations d’accès aux ports d’entrées/sorties numériques et analogiques "à la sauce Arduino" :
 digitalRead(pin)
 digitalWrite(pin, value)

  /**
   * Write a high or low value to a digital pin (or channel as K8055 refers to)
   * @arg pin : channel from 1 to 5
   * @arg value : true or false for HIGH or LOW
   */
  public static void digitalWrite(int pin, boolean value)
  {
    try {
      if (value) {
        SetDigitalChannel(pin);      
      } 
      else {
        ClearDigitalChannel(pin); 
      } 
    } 
    catch (NativeException ne)  {    
      println("Error ! "+ne.toString());   
    }
    catch (IllegalAccessException e)  {    
      println("Error ! "+e.toString());   
    }
  }

  /**
   * Reads the value from a specified digital pin (or channel as K8055 refers to)
   * @arg pin : channel from 1 to 5
   * @return boolean value (true for HIGH and false for LOW)
   */
  public static boolean digitalRead(int pin)
  {
    try {
      return (ReadDigitalChannel(pin) == 1);
    } 
    catch (NativeException ne)  {    
      println("Error ! "+ne.toString());   
    }
    catch (IllegalAccessException e)  {    
      println("Error ! "+e.toString());   
    }
    return false;
  }

Et c’est fini. Dans le code du sketch, à part l’import de la classe que vous venez d’écrire, rien de plus. Voici ce que ça donne pour faire clignoter une led toutes les secondes pendant une demi-seconde :

import org.xvolks.jnative.util.K8055D;

void setup() 
{
  K8055D.initCard(0);
}

void draw()
{ 
  K8055D.digitalWrite(1,true); 
  delay(500); 
  K8055D.digitalWrite(1,false);
  delay(500);  
}

Simple, n’est-il pas ? Voici les sources pour que vous puissiez continuer à implémenter toutes les fonctions de la DLL (je me suis peut être arrêté en route...) et faire tout ce que vous voulez avec !

Sketch de test K8055
Contient la classe de wrapping pour K8055 et un example de sketch Processing.

Vos commentaires

  • Le 12 mai 2016 à 20:28, par Stéphane En réponse à : Utiliser une DLL dans un programme Java

    Bonsoir Julien,
    Etes-vous encore disponible pour m’assister dans la reproduction de votre tuto ?
    Je suis bien partant, mais pas très au fait de toutes ces manipulations qui me font un peu peur .....
    Merci pour votre réponse

    Répondre à ce message

  • Le 8 mars 2012 à 01:09, par ? En réponse à : Utiliser une DLL dans un programme Java

    Salut à tous,

    Justement je m’intéresse à ce problème, celui d’utiliser une dll sous java alors merci Julien pour cet article.

    Julien tu as écrit :


    Dans notre cas (DLL fournie par un constructeur), il est impossible de recompiler le code pour indiquer qu’on souhaite utiliser JNI. Il faut trouver une solution d’interface Java / natif qui laisse la DLL inchangée.

    Alors justement à ce propos n’y aurait-il pas moyen d’utiliser JNI en recompilant la dll ?

    C’est-à-dire on fait une deuxième dll qui fait appelle aux fonctions de la première mais en respectant les conditions pour JNI.

    Qu’en pensez-vous ?

    PS : J’ai entendu parler de JNA aussi...

    Répondre à ce message

  • Le 10 juin 2011 à 00:24, par ? En réponse à : Utiliser une DLL dans un programme Java

    le problème, c’est que le programme que tu donnes à faire fonctionner sous processing ne marche pas. D’une part tu as oublié de dire qu’il faut inclure K8055D.class dans le paquet org.xvolks.jnative.util dans JNative.jar (d’ailleurs je ne comprends toujours pas comment fonctionne processing car quand on veut exporter sous forme d’applet, aucun fichier class K8055D, et VellmanTest.class est de taille nettement inférieure à celle de K8055D.class. Donc sur quoi se base l’applet qui apparaît à l’écran, mystère...)

    Mais même en intégrant K8055D.class, Processing retourne une erreur, la fonction OpenDevice pose problème, tout comme sur Eclipse d’ailleurs (j’ai choisi Eclipse pour le confort, rien de plus, pour cerner d’avantages ce qui ne va pas, mais ca ne suffit apparemment pas..)

    Donc, si tu as un projet qui fonctionne, même sous processing, je suis preneur...

    Répondre à ce message

  • Le 12 juin 2011 à 04:22, par ? En réponse à : Utiliser une DLL dans un programme Java

    bon, ben ca marche sur un ordi 32bits, pas l’impression que ca tourne sur un ordi 64

    Répondre à ce message

  • Le 8 juin 2011 à 13:44, par moijdikssekool En réponse à : Utiliser une DLL dans un programme Java

    j’ai oublié de précisé que le test de la carte a été effectué via k8055_test.exe du cd et que celui-ci va chercher K8055D.dll stocké dans SYSWOW64,celui que j’utilise pour mon prog java (c’est d’ailleurs le seul K8055D.dll sur le disque dur). Si je supprime ce K8055D.dll, les 2 progs détecte son absence

    la carte marche aussi avec le prog K8055_Demo.exe du cd

    je joins le prog (à ouvrir dans un nouveau projet), la dll et les librairies.jar (à inclure via Projet->propriétés->java Build path->external jars)

    • Le 8 juin 2011 à 22:09, par Julien H. En réponse à : Utiliser une DLL dans un programme Java

      Merci bien mais je ne vais pas déboguer ton programme. Tu as compris l’utilisation de JNative et les DLL, je pense que maintenant c’est une question de configuration machine et de debug pas à pas pour comprendre ce qui ne va pas.

    • Le 9 juin 2011 à 11:38, par ? En réponse à : Utiliser une DLL dans un programme Java

      nop

      tu n’aurais pas un projet sous Eclipse (ou autre) qui fonctionne, ne serait-ce que pour allumer une diode ? parceque je n’avance plus...

      tu ne m’as pas dit pour la procédure init(), elle est présente dans K8055D.h mais semble inexistante si on veut la récupérer via la JNI. D’après http://www.delphifaq.com/faq/f84.shtml, le runtime error 217 concerne un problème d’initialisation...

    • Le 9 juin 2011 à 23:04, par Julien H. En réponse à : Utiliser une DLL dans un programme Java

      Non, je n’ai pas de projet sous Eclipse, j’ai utilisé Processing. Si tu es dans la région de Nice, vient assister à un atelier pour qu’on puisse t’aider.

      Mais tu es sûr la bonne voie : j’ai un bout de code où on j’ai appelé la fonction init de la dll.

      private static void init() throws NativeException, IllegalAccessException

      if(nInit == null)

      nInit = new JNative(DLL_NAME, "init") ; // The actual "decorated" name in the DLL

      nInit.invoke() ; // Finally, execute the native function

    Répondre à ce message

  • Le 8 juin 2011 à 12:56, par moijdikssekool En réponse à : Utiliser une DLL dans un programme Java

    hello
    j’ai la carte, je l’ai testée, elle est ok, adressée en 3

    j’essaye votre prog dans eclipse (sous win7 64bits, avec un jdk 32 ou 64bits, j’ai essayé les 2)

    K8055D.dll est stocké dans SYSWOW64 (j’ai essayé dans system32 mais le fichier n’est pas trouvé)
    j’ai importé les librairies core.jar de processing-1.5.1 ainsi que JNative.rar

    alors, voici le prog :

    je prend l’intégralité de la source K8055D.java du paquet VellemanTest trouvé sur ce site, j’y enlève la ligne

    package org.xvolks.jnative.util ;

    et j’y rajoute un main :

    public static void main(final String[] args)

    System.out.println("c’est tipar") ;

    try

    try

    OpenDevice(3) ;

    CloseDevice() ;


    catch(NativeException e) {}

    catch(IllegalAccessException e) {}

    La compilation est ok et le lancement ok ("C’est Tipar" est bien affiché dans la console) mais ne dépasse pas la ligne

    OpenDevice(3)

     : une fenêtre s’affiche avec un runtime error 217 at 00032...
    Une idée ?

    J’ai essayé de faire fonctionner processing-1.5.1 mais j’ai pas l’impression qu’il indique les erreurs et ne semble pas avoir de console (’C’est Tipar’ ne s’affiche nulle part)

    je vois que dans k8055D, il y a init() mais si je cherche à l’inclure via la JNI, cette procédure n’est pas trouvée...

    • Le 8 juin 2011 à 21:58, par Julien H. En réponse à : Utiliser une DLL dans un programme Java

      Bonjour,

      Pas d’idée sur le runtime erreur.

      Je ne peux pas trop vous aider sur ce problème là : en 64 bits sous Eclipse, en modifiant le programme...

    Répondre à ce message

  • Le 20 mai 2011 à 12:20, par eclipse java En réponse à : Utiliser une DLL dans un programme Java

    Je ne comprends pas ce que veut dire compiler le programme java ?Comment on le fait ?

    • Le 20 mai 2011 à 21:48, par Julien H. En réponse à : Utiliser une DLL dans un programme Java

      Bonjour,

      C’est tout le problème d’utiliser des outils tout intégrés comme Eclipse : on en oublie les fondamentaux.

      Java est un langage compilé : le source du programme, écrit dans un langage compréhensible par un être humain, doit être transformé en "byte code", du code machine haut niveau (indépendant du matériel) qui sera interprété par une machine virtuelle pour donner du code machine bas niveau (spécifique à la plateforme).

      Pour compiler du java, il faut utiliser le compilateur fourni avec le kit de développement (ou JDK), ce que fait votre éditeur Eclipse en permanence sans que vous le voyiez.

    Répondre à ce message

Un message, un commentaire ?

modération a priori

Attention, votre message n’apparaîtra qu’après avoir été relu et approuvé.

Qui êtes-vous ?

Pour afficher votre trombine avec votre message, enregistrez-la d’abord sur gravatar.com (gratuit et indolore) et n’oubliez pas d’indiquer votre adresse e-mail ici.

Ajoutez votre commentaire ici

Ce champ accepte les raccourcis SPIP {{gras}} {italique} -*liste [texte->url] <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.