Lost in the matrix

C/C++/C#/Java, Multithreading

Dans cet article, nous allons étudier le framework JNI.
Ce framework Java permet d'exécuter du code non-managé
C/C++ dans du code Java.

Pour cette démonstration nous allons utiliser GCC 3.4.2(Win32)
et le JDK 6. Le code en C sera disponible dans une DLL.
Dev-CPP ou CodeBlocks conviennent parfaitement pour ce tutoriel.
____________________________________________________________________

1) Pour commencer nous allons créer une classe Java "Hello" avec
une methode native "Test" dans un fichier "Hello.java".

public class Hello
{
// Methode native qui correspond au prototype de la fonction
// qui sera appelé dans la DLL
public native void Test();

// Nous chargerons une DLL qui s'appelera "Hello.dll"
static
{
System.loadLibrary("Hello");
}
}

____________________________________________________________________

2) On compile la classe.

$>C:\Sun\SDK\jdk\bin\javac.exe Hello.java

____________________________________________________________________

3) On génère le fichier entête "Hello.h" pour la DLL.

$>C:\Sun\SDK\jdk\bin\javah.exe -jni Hello

#include <jni.h>
#ifndef _Included_Hello
#define _Included_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: Hello
* Method: Test
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_Hello_Test(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

____________________________________________________________________

4) On implémente dans un fichier "Hello.c" notre fonction suivant
Le prototype définit dans "Hello.h"

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "Hello.h"

JNIEXPORT void JNICALL
Java_Hello_Test(JNIEnv *env, jobject obj)
{
printf("42\n");
}

____________________________________________________________________

5) On compile notre fichier source.

$>gcc -c -I"C:\Sun\SDK\jdk\include" \
-I"C:\Sun\SDK\jdk\include\win32" -o Hello.o Hello.c
____________________________________________________________________

6) On génère le fichier de définition de la DLL.

$>dlltool Hello.o -z Hello.def

Cela donne:

EXPORTS
Java_Hello_Test@8 @ 1

____________________________________________________________________

7) On Compile notre DLL.

$>gcc.exe -shared -o Hello.dll -I"C:\Sun\SDK\jdk\include" \
-I"C:\Sun\SDK\jdk\include\win32" Hello.c Hello.def


____________________________________________________________________

8) Nous allons maintenant créer un petit programme en Java pour
tester notre "wrapper" C vers Java dans un fichier "Main.java".


public class Main
{
public static void main(String[] args)
{
// Création d'une instance de notre classe "Hello"
Hello h = new Hello();
// On appel la fonction dans notre DLL (Hello.dll)
h.Test();
}

public Main()
{
super();
}
}

____________________________________________________________________

9) On Compile notre programme de test.

$>C:\Sun\SDK\jdk\bin\javac.exe Main.java

____________________________________________________________________

10) Il nous reste plus qu'à lancer le programme qui doit afficher
"42" sur la sortie standard suivi d'un retour à la ligne.

$>C:\Sun\SDK\jdk\bin\java.exe Main
$>42
$>

____________________________________________________________________

--- Le bug du UnsatisfiedLinkError ---

Il est possible qu'au moment où vous avez exécuter le programme
de l'étape 10, une erreur est apparue:


$>C:\Sun\SDK\jdk\bin\java.exe Main
$>Exception in thread "main" java.lang.UnsatisfiedLinkError: Init
$> at Hello.Test(Native Method)
$> at Main.main(Main.java:8)

Ce problème vient du fait que Java ne trouve pas le symbole de la
fonction "Test" dans notre DLL. Pour résoudre ce problème,
il nous faut revenir à l'étape 6. Dans le fichier "Hello.def"
où l'on peut lire les lignes ci-dessous:


EXPORTS
Java_Hello_Test@8 @ 1


Le problème vient des caractères que j'ai souligné en rouge.
Les noms des fonctions générées par les compilateurs ont toujours ces symboles.
La solution consiste à exporter les fonctions de la DLL sous un autre nom.
Pour cela il existe l'option -k de dlltool qui supprime les décorations
autour des fonctions. Par conséquent nous allons remplacer l'étape 6
par l'étape ci-dessous:

$>dlltool -k Hello.o -z Hello.def

Cela donne:

EXPORTS
Java_Hello_Test @ 1

Nous pouvons maintenant recompiler le projet qui fonctionne parfaitement.
---
Sous Microsoft Visual C, il faut utiliser le programme "dumpbin" afin
de récupérer les symbole de la DLL:

$>dumpbin Hello.dll /EXPORTS

Puis rajouter des directives pré-processeur #pragma pour chaque symbole
utilisé dans le fichier source ("Hello.c").

Exemple:

#pragma comment(linker, "/EXPORT:Java_Hello_Test=_Java_Hello_Test@8")

0 commentaires:

Enregistrer un commentaire