Native Methode in Java Agent

  • Hallo zusammen,


    ich habe unter Verwendung der Lotus C++ API eine Dynamic Link Library erstellt, deren (externe) Funktionen ich unter Java verwenden möchte. Hierzu habe ich mich der JNI (Java Native Interface) bedient. In einer eigenständigen Java-Anwendung (welche die Klasse NotesThread erweitert) funktioniert der Zugriff auf die nativen Funktionen einwandfrei. Wenn ich jedoch selbiges in einem Agenten tun möchte, so meldet mir Java, dass es die nativen Funktionen nicht finden könne.


    Um das Verhalten zu demonstrieren, habe ich mal ein simples Beispiel angefertigt. Hier zunächst einmal der Code für die Dynamic Link Library und die dazugehörige Java Klasse, welche die externe Funktion der DLL implementiert und so unter Java verfügbar macht:


    MyNativeMethod.h

    Code
    /* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class de_dominoforum_MyNativeMethod */#ifndef _Included_de_dominoforum_MyNativeMethod#define _Included_de_dominoforum_MyNativeMethod#ifdef __cplusplusextern "C" {#endif/* * Class:     de_dominoforum_MyNativeMethod * Method:    Print * Signature: (Ljava/lang/String;)V */JNIEXPORT void JNICALL Java_de_dominoforum_MyNativeMethod_Print  (JNIEnv *, jobject, jstring);#ifdef __cplusplus}#endif#endif


    MyNativeMethod.cpp

    Code
    #include "MyNativeMethod.h"#include <iostream>using namespace std;JNIEXPORT void JNICALL Java_de_dominoforum_MyNativeMethod_Print(JNIEnv *env, jobject obj, jstring jStrDbTitle){	string strDbTitle( env->GetStringUTFChars(jStrDbTitle,NULL) );	cout << strDbTitle.c_str() << endl;}


    MyNativeMethod.java

    Code
    package de.dominoforum;/** * Die Klasse MyNativeMethod implementiert die native Methode Print, * welche in einer externen Dynamic Link Libary definiert ist. * Die Methode wurde unter Verwendung der Lotus C++ API realisiert * und tut nichts weiter, als den im ersten (und einzigen) Argument * mitgegebenen String auf der Konsole (Bildschirm) auszugeben. *   * @author Leon */public class MyNativeMethod{	/**	 * Die Methode Print ist in der Dynamic Link Library MyNativeMethod.dll	 * definiert und tut nichts weiter, als den im ersten (und einzigen)	 * Argument mitgegebenen String auf der Konsole (Bildschirm) auszugeben.	 * 	 * @param str Der String, welcher auf der Konsole ausgegeben	 * werden soll.	 */	public native void Print(String str);	/**	 * Hier wird die Dynamic Link Library MyNativeMethod.dll geladen und	 * zwar noch vor der Instanziierung der Klasse. Die Dymanic Link	 * Library muss sich im CLASSPATH befinden.	 */	static { System.loadLibrary("MyNativeMethod"); }}


    So weit so gut. Im Folgenden der Code, welcher eine eigenständige Java-Anwendung realisiert und die native Funktion einbindet. Dies funktioniert einwandfrei, d.h. der Titel des lokalen Adressbuchs (names.nsf) wird auf der Konsole (dem Bildschirm) ausgegeben.


    ExtendedNotesThread.java

    Code
    package de.dominoforum;import lotus.domino.*;/** * Die Klasse Main erweitert die Klasse NotesThread und bezieht eine * Notes-Session außerhalb einer Notes-Datenbank. Somit lassen sich * eigenständige Anwendungen realisieren, welche im Vergleich zu * einem klassischen Agenten nicht Teil einer Notes-Datenbank sind. * Ein weiterer Wichtiger Unterschied zum klassischen Agenten besteht * darin, dass die eigenständige Anwenung nicht zwingend unter der * Notes / Domino eigenen Java Runtime Engine ausgeührt werden muss, * sondern unter einer (gewöhnlichen) JRE von Sun Microsystems * ausgeführt werden kann. *  * @author Leon */public class ExtendedNotesThread extends NotesThread{	/**	 * Die statische main-Methode erzeugt eine Instanz der Klasse	 * Main, welche die Klasse NotesThread erweitert. Dann wird	 * mittels der Methoden start() und join() die Methode runNotes	 * aufgerufen.	 * 	 * @param args Kommandozeilenparameter - nicht in Verwendung	 */	public static void main(String[] args)	{		ExtendedNotesThread main = new ExtendedNotesThread();		main.start();		try { main.join(); }		catch(InterruptedException e) { e.printStackTrace(); }	}	/**	 * In der Methode runNotes() wird eine Notes-Session erzeugt und	 * das lokale Adressbuch (names.nsf) geöffnet. Dann wird der Titel	 * des Adressbuchs an die native Methode Print der Klasse	 * MyNativeMethod übergeben, welche den Titel auf der Konsole	 * ausgeben soll.	 */	public void runNotes()	{		Session session;		Database db;		MyNativeMethod m = new MyNativeMethod();		try		{			session = NotesFactory.createSession();			db      = session.getDatabase("", "names.nsf");			m.Print( db.getTitle() );			db.recycle();			session.recycle();		}		catch(NotesException e)		{			e.printStackTrace();		}	}}


    Führt man die Anwendung aus, wird wie erwartet die native Funktion Print aus der Dymanic Link Library MyNativeMethod.dll aufgerufen und der Titel des lokalen Adressbuchs erscheint auf der Konsole.


    Im Folgenden versuchen wir nun das selbe in einem Agenten:


    JavaAgent.java

    Code
    package de.dominoforum;import lotus.domino.*;/** * Die Klasse Main erweitert die Klasse AgentBase und bezieht von * ihr eine Notes-Session. Somit wird ein klassischer Agent * realisiert, welcher Teil einer Notes-Datenbank ist. Daher läuft * der Agent unter der Java Runtime Engine von Notes / Domino, wo * sich die Datenbank befindet. *  * @author Leon */public class JavaAgent extends AgentBase{	/**	 * Die Methode NotesMain() bildet den Einstiegspunkt für den Agenten.	 * Es wird eine Session über die AgentBase bezogen. Im Folgenden wird	 * das lokale Notes-Adressbuch (names.nsf) geöffnet und der Titel des	 * Adressbuchs an die native Methode Print der Klasse MyNativeMethod	 * übergeben, welche des Titel auf der Konsole ausgeben soll.	 */	public void NotesMain()	{		Session session;		Database db;		MyNativeMethod m = new MyNativeMethod();		try		{			session = getSession();			db      = session.getDatabase("", "names.nsf");			m.Print( db.getTitle() );			db.recycle();			session.recycle();		}		catch(NotesException e)		{			e.printStackTrace();		}	}}


    Die dll muss sich dabei im Notes-Verzeichnis befinden, damit sie gefunden wird. Allerings bekomme ich trotzdem folgende Fehlermeldung:


    Code
    java.lang.UnsatisfiedLinkError: Print
    
    
    	at de.dominoforum.JavaAgent.NotesMain(JavaAgent.java:33)
    
    
    	at lotus.domino.AgentBase.runNotes(AgentBase.java:161)
    
    
    	at lotus.domino.NotesThread.run(NotesThread.java:203)


    Wieso wird auf einmal die externe Funktion Print in der Dynamic Link Library nicht mehr gefunden ?


    Bitte um Hilfe ! Den kompletten Quellcode inkl. compilierter DLL habe ich dem Post einmal als zip-Archiv beigefügt.


    MfG


    Leon


    PS: Gibt es in diesem Forum eigentlich auch Spoiler, mit denen sich bestimmt Bereiche im Post auf- und zuklappen lassen ? Dann würde ich den ganzen Code in Spoiler packen, um den Post nicht so auf zu blasen.

  • Hallo taurec,


    ich habe das JDK 1.1.8 verwendet. Dabei handelt es sich um die gleiche Version wie die unter Notes / Domino R5. Führt man folgenden Java-Agenten unter R5 aus...


    Java
    import java.util.Properties;import lotus.domino.*;public class JavaAgent extends AgentBase{	public void NotesMain()	{		Properties props = System.getProperties();		System.out.println( "Java Version: " + props.getProperty( "java.version" ) );	}}


    ...erhält man folgende Ausgabe:


    Code
    Java Version: 1.1.8


    Mir ist bekannt, dass die JRE unter Domino nicht exakt die selbe ist wie die von Sun Microsystems, da IBM sie etwas modifiziert hat, aber die JNI Funktionen sollten unterstützt werden. Die JNI-Funktionen welche ich verwende wurden in Version 1.1 eingeführt und laut Aussage meiner "Bibel" (Java unter Lotus Domino von Thomas Ekert, erschienen im Springer Verlag) basiert die Java-API von Notes / Domino ohnehin vollständig auf JNI:


    Zitat

    Dies hat seine Ursache in der Architektur der Java-Core-Klassen von Domino. Diese Java-Klassen sind lediglich Wrapper-Klassen, die über „native“ Referenzen auf die maschinenspezifischen C++ Klassen von Notes und Domino zugreifen. Diese Bibliotheken sind übrigens dieselben, auf die LotusScript zugreift, d.h. letztendlich ist die Verwendung der Domino-Klassen nicht nur ähnlich zu der Verwendung in LotusScript – so haben fast alle LotusScript Klassen einen Repräsentanten in Java und beide haben in der Regel eine äquivalente Signatur –, sondern faktisch rufen beide denselben C++ Code auf.


    Quelle: www.domino-java.com


    MfG


    Leon

  • Ist dein Notes-Verzeichnis wo die DLL liegt im Pfad enthalten ?


    Ich hatte das Problem da auch schon öfters, z.b. bei einem SAP Connector, und das liess sich nur lösen indem ich die DLL ins JVM-BIN-Verzeichnis des Domino-Servers gelegt habe.
    Da hab ich sonst auch immer die Meldung bekommen, daß er die DLL nicht findet

  • Das Notes-Verzeichnis habe ich in meiner PATH-Variable eingetragen. Daran lag's nicht. Das Problem ist auch nicht, dass die Library nicht gefunden wird, sondern dass er die Funktion in der Library nicht findet. Wenn Du in der Datei MyNativeMethod.java einfach mal den Namen der Library verfälschst, z.B. so:


    (einfach ein X vor den Namen geschrieben)

    Code
    static { System.loadLibrary("XMyNativeMethod"); }


    bekommst Du die Meldung, dass eine Library mit diesem Namen nicht gefunden werden konnte.


    Code
    java.lang.UnsatisfiedLinkError: no XMyNativeMethod in shared library path	at java.lang.Runtime.loadLibrary(Runtime.java:434)	at java.lang.System.loadLibrary(System.java:561)	at 	at 	at lotus.domino.AgentBase.runNotes(AgentBase.java:161)	at lotus.domino.NotesThread.run(NotesThread.java:203)


    Vieleicht habe ich beim Kompilieren etwas falsch gemacht ?


    Ich verwende Microsoft Visual Studio .NET 2003. Hier mal die Optionen, welche ich für den Linker und den Compiler verwendet habe:


    Compiler-Optionen:

    Code
    /O2 /I "C:\Programme\Notes\notescpp\include" /I "C:\Programme\Java\jdk1.1.8_010\include\win32" /I "C:\Programme\Java\jdk1.1.8_010\include" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_USRDLL" /D "MYNATIVEMETHOD_EXPORTS" /D "W32" /D "_WINDLL" /D "_MBCS" /FD /EHsc /MT /Zp1 /GS /Fo"Release/" /Fd"Release/vc70.pdb" /W3 /nologo /c /Wp64 /Zi /TP


    Linker-Optionen:

    Code
    /OUT:"Release/MyNativeMethod.dll" /INCREMENTAL:NO /NOLOGO /DLL /DEBUG /PDB:"Release/MyNativeMethod.pdb" /SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF 
    /IMPLIB:"Release/MyNativeMethod.lib" /MACHINE:X86 C:\Programme\Notes\notescpp\lib\mswin32\notescpp.lib  kernel32.lib user32.lib gdi32.lib winspool.lib 
    comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib


    Wer über Visual Studio .NET 2003 verfügt, kann sich auch das Projekt herunterladen und sich die Projekteigenschaften ansehen. Das ist wesentlich angenehmer zu lesen und extrem einfacher zu verstehen.


    MfG


    Leon

  • Problem gelöst! Das developerworks-Forum bei IBM enthielt die Erklärung für das hier beschriebene Verhalten und zugleich auch die Lösung.


    Kurz gesagt liegt es daran, dass ein Agent im Gegensatz zu einer externen Java-Anwendung über den classloader geladen wird. In diesem Fall verbietet uns jedoch leider die JVM den Aufruf von nativen Methoden.


    Um dennoch auch unter einem Agenten mit nativen Methoden arbeiten zu können, müssen wir die Klassen, in denen native Methoden deklariert sind, aus dem Agenten entfernen. (In meinem Beispiel also die Klasse de.dominoforum.MyNativeMethod) Diese Klassen packen wir statt dessen in ein JAR-Archiv und speichern dieses, zusammen mit der DLL, welche die nativen Methoden definiert, in unser Notes-Verzeichnis.


    Wie gesagt dürfen wir die Klassen, in denen native Methoden deklariert sind, nicht direkt mit in den Agenten aufnehmen. Wie aber gewähren wir nun den verbleidenden Klassen den Zugriff auf diese Klassen, welche wir in ein JAR-Archiv gepackt und im Notes-Verzeichnis gespeichert haben ?
    Hierzu bedienen wir uns dem Parameter JavaUserClasses in der notes.ini. Dieser Parameter macht die JAR-Files in der lokalen Notes / Domino Umgebung bekannt, so dass alle Agenten, welche sich in Datenbanken auf diesem Rechner befinden Zugriff darauf haben. Bezogen auf mein Beispiel sieht das dann so aus:


    Code
    JavaUserClasses=C:\Programme\Notes\MyNativeMethod.jar


    (Nicht vergessen, Notes samt Designer einmal zu schließen, damit die neue Konfiguration auch wirksam wird.)


    Wenn wir nun den Agenten aufrufen, wird die native Funktion (endlich) wie erwartet aufgerufen.


    MfG


    Leon