Java JNI nutzen mit Hilfe von Eclipse CDT und MinGW

Da ich mich jüngst mit diesem Thema beschäftigt habe und ich auch ein paar Stunden Zeit investieren musste, bis alles einwandfrei funktionierte, hier nun eine kleine Anleitung nach dem Schema “Hello World”, wie man Java und C++ mit Hilfe von JNI verbindet und das Ganze in Kombination mit Eclipse.
Einige Dinge, die ich nun versuche kurz und knapp zu beschreiben, kann man sicherlich auch anders machen, eventuell auch besser, aber so funktioniert es, jedenfalls bei mir ;-) . Vorweg sei auch gesagt, dass ich hier nicht groß und breit erklären werde, was JNI bedeutet, was es macht, warum, wieso, weshalb und so weiter. Hier geht es einfach nur darum ein HelloWorld mit Hilfe von Eclipse auf den Bildschirm/TFT zu zaubern und darauf werde ich mich hier beschränken. Anleitungen zu JNI gibt es haufenweise im Netz, deshalb muss ich hier nicht auch noch ein Buch schreiben, wo das Selbe enthalten ist. Detaillierte Beiträge zu JNI, was es macht und wie es funktioniert, gibt es zum Beispiel hier. Die Anleitung basiert auf Windows XP, müsste aber ähnlich auch bei Linuxsystemen funktionieren.

Voraussetzungen für JNI mit Eclipse

Ein paar Erfahrungen sollte man mit Eclipse, CDT, MinGW und so weiter schon gemacht haben, also für wen das Neuland ist, dem sei erst einmal ein Eclipse Tutorial ans Herz gelegt.

Folgendes muss installiert sein und funktionieren:

  • Eclipse 3.2 mit CDT
  • JDK (1.6.0)
  • MinGW
  • MSYS

Der grobe Ablauf

  1. Erstellen einer Javaklasse, die das JNI in Anspruch nimmt
  2. Erstellen einer Headerdatei
  3. Schreiben der C++ Funktionen
  4. DLL erstellen
  5. Das Ergebnis bewundern

Arbeiten wir nun die Punkte nach und nach ab…

Erstellen der Projekte

Zuerst ein neues Standard Java Projekt mit dem Namen “HelloWorld” in Eclipse anlegen und eine einfache Klasse erstellen im Package “helloworld”, die eine native Funktion erhält. Zum Beispiel so:

package helloworld;

public class HelloWorld {

  public native void callNative(); // Funktion die später in der DLL implementiert wird

  static {
    System.loadLibrary("HelloWorld"); // Name der externen DLL Datei (ohne .dll)
  }

  public static void main(String[] args) {

    System.out.println("Servus, ich bin die Javaklasse. Wie geht es dir CallNative.DLL?");
    HelloWorld hello = new HelloWorld();
    hello.callNative();
    System.exit(0);

  }
}

Anschließend kompilieren, wenn nicht schon automatisch von Eclipse erledigt. Im bin-Verzeichnis müsste sich nun ein Unterverzeichnis “helloworld” befinden, welches die Klasse “HelloWorld.class” enthält. Wir haben nun ein fertiges Javaprojekt. Um die spätere DLL erstellen zu können brauchen wir noch ein C++ Projekt. Das erstellen wir nun.

Als neues Projekt wählen wir also dieses mal aus dem Unterordner “C++” ein “Managed Make C++ Project” aus. Ein passender Name wäre, im Gegensatz zu dem Java Projekt “HelloWorld”, hier dieses Mal passend zur DLL “HelloWorldDLL”. Im nächsten Fenster wählen wir dann bei Project Type “Shared Library (Gnu on Windows)” aus und entferne noch das Häkchen bei “Debug”. Das wir hier nicht zwingend gebraucht. Mit “Finish” wird das Projekt erstellt. Gleich danach erstellen wir ein Verzeichnis “/src” direkt im C++ Projekt. So haben wir nun am Ende 2 Projekte: ein Java und ein C++ Projekt.

Erstellen der Headerdatei mit Eclipse

Nun kommt Schritt 2, erstellen der Headerdatei. Wir wechseln nun in das bin-Verzeichnis und geben dort folgendes Kommando ein:

javah -jni helloworld.HelloWorld

Nun müsste eine Headerdatei erzeugt worden sein, mit dem Namen “helloworld_HelloWorld.h” oder so ähnlich. Diese sollte später unverändert bleiben, als nicht darin rumschreiben! Dies ist jetzt aber der normale Weg, wir wollen das ganze einfach und automatisch in Eclipse machen und die Headerdatei soll auch gleich im richtigen Verzeichnis des “HelloWorldDLL” Projektes landen. Die eben erstellte Headerdatei kann dann erstmal wieder gelöscht werden.

Wir wollen nun den Befehl “javah” in ein “External Tool” von Eclipse transferieren. Dazu klicken wir auf “Run -> External Tools -> External Tools…”. In dem nun sich geöffneten Fenster wählen wir “Program” aus und füllen alle wichtigen Felder aus. Im Reiter “Main” muss zuerst die Location der “javah”-Datei eingegeben werden. Bei mir ist das beispielsweise:

C:/Programme/Java/jdk1.6.0/bin/javah.exe

Im “Working Directory” geben wir ein:

${workspace_loc:/HelloWorld/bin}

Hinter der Variable “workspace_loc:” kommt das Verzeichnis, in dem das Projekt zuhause ist, also “/HelloWorld” und anschließend “/bin”, da hier die fertig kompilierten Klassen liegen. Unter “Arguments kommt” kommt folgender Eintrag:

-jni -d ${workspace_loc:/HelloWorldDLL/src} helloworld.HelloWorld

“/HelloWorldDLL/src” ist unser Zielverzeichnis. Somit wird die Headerdatei gleich in das passende Verzeichnis des C++ Projektes verschoben. Abschließend geben wir dem Ganzen noch einen Namen, ganz oben in “Name:”. Unter dem Reiter “Refresh” machen wir noch ein Häkchen und wählen “The entire workspace” aus. Nun können wir das Ganze laufen lassen. In dem C++ Projekt müsste jetzt im Verzeichnis “/src” eine Headerdatei mit dem Namen “helloworld_HelloWorld.h” zu finden sein. Wenn alles funktioniert hat, haben wir nun für die Zukunft immer die Möglichkeit mit einem Klick eine neue Headerdatei zu erzeugen, sollte sich in der Javaklasse mal was ändern. Nun geht es an die Implementierung der DLL Datei.

Implementierung der C++ Funktionen

Im C++ Projekt erstellen wir nun eine “.cpp” Datei. Der genaue Inhalt der cpp-Datei richtet sich nach dem nun folgenden Inhalt der eben erstellten Headerdatei:

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class helloworld_HelloWorld */
#ifndef _Included_helloworld_HelloWorld
#define _Included_helloworld_HelloWorld
#ifdef __cplusplusextern "C" {
#endif

/*
* Class:     helloworld_HelloWorld
* Method:    callNative
* Signature: ()V
*/

JNIEXPORT void JNICALL Java_helloworld_HelloWorld_callNative (JNIEnv *, jobject);

#ifdef __cplusplus

}

#endif
#endif

Wir sehen hier die Funktion

JNIEXPORT void JNICALL Java_helloworld_HelloWorld_callNative (JNIEnv *, jobject);

Diese müssen wir nun in der C++ Datei implementieren. Der Inhalt der C++ Datei sieht dann so aus:

#include <jni.h>
#include "helloworld_HelloWorld.h"
#include <iostream>

using namespace std;

JNIEXPORT void JNICALL Java_helloworld_HelloWorld_callNative (JNIEnv *env, jobject obj) {

   cout << "Mir geht es gut: Hello World!" << endl;

}

Am Besten man kopiert die Funktion einfach per copy-and-pase von der Headerdatei in die C++ Datei und fügt bei den Parametern nur noch “*env” und “obj” hinzu. So kann man Tippfehler vermeiden.

Damit das Kompilieren später funktioniert und die Funktionen der DLL auch von Java genutzt werden können muss nun noch eine 3. Datei erstellt werden, am besten mit dem Namen “HelloWorld.def”. Die Endung “def” ist dabei zwingend erforderlich. Diese hat den Inhalt:

EXPORTS

Java_helloworld_HelloWorld_callNative

Wie wohl jedem auffällt, ist das der Name der Funktion. Jetzt ist es an der Zeit den C++ Compiler zu konfigurieren.

Kompilieren der DLL

Wie klicken dazu auf “Project -> Properties”. In dem jetzigen Fenster nehmen wir nun alle nötigen Einstellungen vor:

Zuerst wählen wir links “C/C++ Build” aus. Dann geht es in den Reiter “Tool Settings”. Dort wählen wir unter “GCC C++ Compiler” -> “Directories” aus. Rechts können wir nun die “Include Paths” angeben. Hier fügen wir nun 2 (!) Verzeichnisse hinzu:

C:/Programme/Java/jdk1.6.0/include/win32
C:/Programme/Java/jdk1.6.0/include

Je nachdem wo das JDK installiert wurde muss hier der Pfad zu dem “include”-Verzeichnis von Java angegeben werden. Jetzt wählt man “GCC C++ Linker” aus. Wieder auf der rechten Seite müsste nun ein Feld mit dem Namen “Command:” erscheinen mit dem Inhalt “g++”. Hier fügen wir nach einem Leerzeichen den Ausdruck “../src/HelloWorld.def” hinzu, so dass am Ende in dem Feld folgendes da stehen muss: “g++ ../src/HelloWorld.def”. Sicherlich geht das auch irgend wie eleganter, aber ich habe noch keine bessere Möglichkeit gefunden ;-) . Zuletzt müssen wir noch unter dem Reiter “Build Settings” den Namen der DLL-Datei ändern von “HelloWorldDLL” nach “HelloWorld”.

Normalerweise müsste das Kompilieren jetzt ohne Fehler funktionieren und in dem Verzeichnis “Release” müsste sich die Datei “HelloWorld.dll” befinden.

Der letzte Feinschliff

Wir wollen aber nun, dass die DLL-Datei gleich automatisch in das Java-Projekt kopiert wird. Dazu gehen wir noch mal zurück in die “Project -> Properties”. Dieses Mal wählen wir links “Builders” aus. Auf der rechten Seite werden jetzt die Builder aufgelistet: “Generated Makefile Builder”. Nun klicken wir ganz rechts auf “New” und dann auf “Program”. Es öffnet sich die gleiche Maske wie in den “External Tools”. Als Namen geben wir irgend was ein, zum Beispiel: “DLL kopieren” . Da wir ja MSYS installiert haben, machen wir uns das einfach und benutzen den “cp” Befehl, den wir aus der Linuxwelt kennen. Dies kann natürlich auch mit dem normalen “copy”-Befehl von Windows gemacht werden, aber das ist nicht so wichtig. Als Location wird hier also nur noch die “cp” Datei angegeben, die sich im “/MSYS/bin”-Verzeichnis befinden müsste. Als “Working Directory” geben wir an:

${workspace_loc:/HelloWorldDLL/Release}

und als Arguments:

*.dll ${workspace_loc:/HelloWorld/bin}

Unter “Build Options” machen wir ein Haken bei “Run the Builder” -> “During auto builds”.

Das Ergebnis bewundern

Die kompilierte DLL sollte sich nun immer automatisch in dem “/bin” Verzeichnis des Java Projektes befinden. Um das Ganze zum Laufen zu bringen, muss nun einmal auf “Run -> Run…” geklickt werden. Indem nun sich öffnenden Fenster muss man zu guter letzt noch auf den Reiter “Arguments” wechseln und in dem Feld “Working Directory -> Other:” ganz unten folgende Zeile einfügen:

${workspace_loc:HelloWorld/bin}

Sonst findet die Javaklasse die DLL nicht. Der Rest solle von Eclipse automatisch ausgefüllt worden sein. Wenn nun auf Run geklickt wird, sollte unten in der Konsole der Dialog zwischen Java und der DLL erscheinen.

Done

Wir haben nun die Möglichkeit parallel in 2 Projekten zu arbeiten. Die DLL wird immer automatisch kompiliert und an den richtigen Platz gebracht. Die obige Beschreibung habe ich selber noch einmal praktisch durchgespielt und es hat alles einwandfrei funktioniert. Wie es so häufig der Fall ist, kann es natürlich sein, dass auf einem anderen Rechner, mit einer anderer Konfiguration, die Sache irgend wie nicht funktioniert. Dann findet man aber schnell Hilfe in den vielen Javaforen. Zudem ist diese HowTo nur eine Möglichkeit die Sache in Eclipse umzusetzen. Hier und da kann man einige Dinge vielleicht geschickter lösen. So gibt es zum Beispiel auch die Möglichkeit beide Projekte zu vereinen. Der Übersicht halber finde ich diese Lösung aber sinnvoller. Zudem könnte man die Erzeugung der Headerdatei auch in die Buildkonfiguration des Javaprojektes aufnehmen und die Sache so noch weiter automatisieren. Ich hoffe aber, dass ich mit dieser HowTo einen guten Leitfaden zur Verfügung gestellt habe, womit man als Neuling erst einmal anfangen kann. Sollten irgend welche Fehler in dem Text enthalten sein, wäre ich dankbar, wenn diese in den anschließenden Commtsbereich gepostet werden. Für Fragen, Anregungen und Kritiken bin ich immer offen, sofern sie konstruktiv sind.

9 Responses to “Java JNI nutzen mit Hilfe von Eclipse CDT und MinGW”


  • Vielen Dank für die ausführliche Beschreibung. Sie hat mir sehr geholfen!

  • Hi! Das Tutorial ist wirklich gut. Bei mir kommt allerdings beim Generieren der .dll Datei folgender Fehler:

    Building target: HelloWorld.dll
    Invoking: GCC C++ Linker
    g++ ../src/HelloWorld.def -shared -o”HelloWorld.dll” ./src/HelloWorld.o
    Cannot export Java_helloworld_HelloWorld_callNative: symbol not defined
    collect2: ld returned 1 exit status
    make: *** [HelloWorld.dll] Error 1
    make: Target `all’ not remade because of errors.
    make: warning: Clock skew detected. Your build may be incomplete.
    Build complete for project HelloWorldDLL

    –>Ich verstehe nicht, wie ich vor allem das hier verstehen soll “Cannot export Java_helloworld_HelloWorld_callNative: symbol not defined”. Habe dazu auch nichts im Internet gefunden. Ansonsten ist meine Konfiguration nahezu gleich bis auf das ich Cygwin verwende. Das sollte aber doch eingentlich nicht das Problem sein. Kann mir vielleicht jemand weiterhelfen? Wäre sehr nett.

  • Hm, keine Ahnung ob das an Cygwin liegt, ich glaube eher nicht. Wenn alle deine anderen C++ Programme auch ohne Probleme laufen.

    Als erstes würde ich darauf tippen, dass vielleicht im Methodenname ein Tippfehler drinne ist, so dass er die Methode nicht findet. Am besten noch mal kurz drüber schauen. Auch die Parameter müssen passen. Er versucht halt die Methode “Java_helloworld_HelloWorld_callNative” zu exportieren, findet sie aber nicht.

    g++ ../src/HelloWorld.def -shared -o”HelloWorld.dll” ./src/HelloWorld.o

    Ist das richtig, dass bei ./src/HelloWorld.o nur ein Punkt am Anfang steht und bei ../src/HelloWorld.def zwei Punkte? Beide Dateien liegen im gleichen Verzeichnis? Vielleicht stimmt was mit den Pfadangaben nicht. Aber dann sollte eigentlich eine andere Fehlermeldung kommen.

    Sry, aber mehr fällt mir gerade nicht auf. Leider habe ich das Projekt nicht mehr, dass ich mal schnell nachschauen könnte. Ist schon ne Weile her ;-) Hoffe du findest die Lösung des Problems trotzdem.

    Grüße, Andre

  • Danke für die Antwort. Nach langem Testen hat es nun funktioniert, wenn ich statt einer .cpp Datei eine einfache .c Datei implementiere. Auch das automatische Kopieren der HelloWorld.dll in den bin-Ordner des JAVA-Projekts klappt einwandfrei.
    Komischerweise hängt er sich auf, wenn ich die Java Applikation ausführen möchte. Er scheint aber die library zu finden und zu laden, macht aber dann nichts :(

  • Hallo,

    ich muss leider nochmal nerven :( Da JNi bei mir einfach nicht funzen will, habe ich Eclipse auf die gleichen Einstellungen konfiguriert, wie von dir angegeben (JDK 1.6.0, CDT 3.1.2, Eclipse 3.2.1, MinGW 3.1.0.1, MSYS 1.0.10)
    Das Projekt habe ich 1 zu 1 nachgebildet, damit ich wirklich mal sehen kann, dass JNI auch funktioniert. Die .dll wird im Release Ordner erstellt und ist diesmal auch größer als sonst (475 KB, vorher immer 11KB). Über die Kopierfunktion kopiere ich die .dll dann auch immer in den Library Pfad.
    Wenn ich über Java jetzt System.loadLibrary(“HelloWorld”) mache, findet er die DLL auch, gibt aber folgende Fehlermeldung aus:

    java.lang.UnsatisfiedLinkError: E:\eclipse\C++Projekte\workspace\HelloWorld\bin\HelloWorld.dll: Can’t find dependent libraries
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(Unknown Source)
    at java.lang.ClassLoader.loadLibrary(Unknown Source)
    at java.lang.Runtime.loadLibrary0(Unknown Source)
    at java.lang.System.loadLibrary(Unknown Source)
    at helloworld.HelloWorld.(HelloWorld.java:8)
    Exception in thread “main”

    Kannst du oder kann mir sonst irgendjemand weiterhelfen? Ist die .DLL unvollständig? Muss ich weitere Bibliotheken laden, die in Abhängigkeit stehen? Wäre für jede Hilfe dankbar.

    Grüße

  • Hm, per Ferndiagnose ist das immer so eine Sache. Die HelloWorld.def Datei hast du nicht vergessen, oder? Vielleicht ist da beim Funktionsname ein Tippfehler aufgetreten. Allgemein vielleicht mal schauen, ob sich irgend wo ein Fehler eingeschlichen hat. -> Manchmal kann es vorkommen, dass beim “Copy and Paste” die Formatierungen von der Website, also irgend welche HTML Zeichen, mit übernommen werden, die im Quellcode normalerweise nichts zu suchen haben.

    Auf alle Fälle werde ich beim nächsten Mal, wenn ich eine Anleitung schreibe, immer die Projekt-Dateien zum Download anbieten, um solche Fehler in Zukunft auszuschließen. Wenn ich die nächsten Wochen Zeit habe, werde ich diese HowTo noch mal überarbeiten. Momentan bin ich jedoch beruflich zu stark eingespannt, daher kann ich es nicht versprechen.

    Grüße und bis dann, Andre

  • Cooles Tutorial :)

    Hab es durchgespielt und bin mit Cygwin prompt in die Falle getreten.
    Nachdem ich MinGW installiert habe lief es wie beschrieben…

  • Super Tutorial!
    Hat alles ohne Probleme geklappt und mir viel Arbeit erspart.

Leave a Reply