23 März 2010

Im aktuellen Projekt verwenden wir JNI zum Aufruf einer C-API.
Dabei habe ich verschiedene Herausforderungen gemeistert.

Die größte Herausforderung betrifft die Bibliothek, die im Java-Code über System.loadLibrary(String) geladen werden soll. Diese muss im java.library.path liegen. Diesen kann man beim Aufruf über die Konsole angeben: java -Djava.library.path=<path_to_lib> JNICall.
Unter Sun Solaris kann man dafür auch die Umgebungsvariable LD_LIBRARY_PATH nutzen; unter Windows ist es PATH. Dann benötigt man den Parameter beim Aufruf nicht mehr.
Im Websphere 6.1 ist das etwas schwieriger. Dort kann man die Variable java.library.path als benutzerdefiniertes Merkmal für die JVM definieren. Für jeden Anwendungsserver kann man unter Java- und Prozessverwaltung in der Prozessdefinition unter Java Virtual Machine ein entsprechendes Merkmal hinzufügen.

Das Kompilieren des C-Codes war leider auch nicht ganz so einfach. Unter Windows haben wir dazu GCC von MinGW genutzt. Unter Sun Solaris hatten wir CC zur Verfügung. Mit Hilfe eines Ant-Scripts konnten wir relativ einfach für die entsprechenden Umgebungen mit kleinen Anpassungen den Code compilieren.

Die größte Herausforderung war allerdings das Laden der Bibliothek unter Solaris. Während es unter Windows eine *.dll ist, ist es unter Solaris eine *.so! Unter Solaris kommt allerdings noch ein Präfix dazu: lib. Das bedeutet, dass unter Windows die Bibliothek z.B. Demo.dll heißen muss, unter Solaris jedoch libDemo.so!

Ich hoffe, dass diese Informationen dem einen oder anderen dabei helfen können, selbst über JNI C-Code aufzurufen.

Nachtrag:

Wenn eine Bibliothek bereits von einem Classloader geladen wurde, kann diese nicht erneut geladen werden. Wird es doch versucht, erhält man einen UnsatisfiedLinkError mit der Meldung „Native Library Demo already loaded in another classloader“.

Das Laden einer Bibliothek in in einem Application Server wie dem Websphere AS sollte im Allgemeinen vermieden werden. Es gibt aber eben auch Fälle, in denen es notwendig ist. Dann habe ich folgenden Tipp: Das Laden der Bibliothek und der Aufruf der nativen Methoden sollte in ein JAR ausgelagert werden und nicht in einem EAR enthalten sein. Dieses JAR sollte in das lib-Verzeichnis des Websphere AS kopiert werden. Der Pfad zur Bibliothek im java.library.path muss, wie oben beschrieben, im Anwendungsserver definiert werden.
So sollte die Bibliothek nicht von dem Classloader des Anwendungsservers geladen werden, sondern von einem höher angesiedelten.

Übrigens: Wenn es einen UnsatisfiedLinkError beim Aufruf einer nativen Methode gibt (im Stacktrace taucht „Native Method“ ganz oben auf), dann stimmt in den meisten Fällen der Klassenname oder der Packagename nicht.