# JNI-Problem beim erneuten Laden einer dll



## Apo (23. Mrz 2009)

Hallo,

ich lade mithilfe von JNI eine dll. Es ist für ein Spiel gedacht, wo man seine KI selber schreiben darf (unter anderem auch mit c++ oder c# und nicht nur mit Java).

Beim ersten Mal klappt das auch wunderbar. Das Problem ist nun folgendes. Ich möchte mehrere KI's testen. Beim erneuten Laden einer neuen KI überschreibt er die alte nicht. Er läd die neue KI einfach nicht. (sehe ich an den Debugausgaben, die ich in der dll ausgebe). Der Pfad zum Laden der neuen dll ist aber jeweils der richtige, den lasse ich mir zu Testzwecken mit ausgeben.
Muss ich die geladene DLL erst "entladen"? Wenn ja wie? Oder wie gehe ich sonst vor?

Ich danke für eure Hilfe! 

hier ist der Code den ich verwende ...

```
public class ApoSoccerAIJNI {

	public void loadAI(String ai) {
		System.out.println(ai);
		System.load(ai);
	}
	
	public static native String getTeamName();
	
	//... hier dann die ganzen anderen Interfacemethoden
}
```


----------



## thE_29 (23. Mrz 2009)

Sowas suche ich auch schon seit langem, aber das gibt es nicht in Java!

Es gibt nur eine UnloadAllLibrary (wenn man in den Java Sourcen sucht) aber die wird vom Ende der JVM aufgerufen. Explizit eine dll entladen gibts leider nicht...


----------



## Apo (23. Mrz 2009)

Es muss nicht zwangsläufig ein "Entladen" sein. Geht das Überschreiben der dll nicht? Oder ist die Klasse, die einmal diesen Befehl ausgeführt hat, bis zum Ende es Programms damit "verdammt", die einmal geladene dll zu nutzen?
Das wäre sehr ***** schade und unschön.


----------



## Ebenius (23. Mrz 2009)

Das kann eigentlich nicht funktionieren. Die Klasse mit den native-Methoden ist vollständig geladen, wenn die Bibliothek geladen ist die die Methoden imeplementiert. Eine einmal geladene Klasse wird weder im Java noch mit JNI neu geladen. Das wäre äußerst seltsam, oder?

Zwei Lösungsansätze fallen mir ein: 
Eine Bibliothek selbst anbieten die auf der einen Seite die JNI-Schnittstelle bedient und auf der anderen Seite zu anderen Bibliotheken delegiert. Das hätte den Vorteil, dass man nicht in allen anderen Bibliotheken den JNI-Krempel mitschleppen muss.
Die Klasse die die Bibliothek lädt in einem eigenen ClassLoader-Kontext laden, diesen nach Verwendung wieder zerstören. Das sollte aber getestet werden, keine Ahnung ob das funktioniert.

Ebenius


----------



## Apo (23. Mrz 2009)

Der zweite Vorschlag klingt super! =)
Ich versuche ihn gleich mal umzusetzen.

Falls das nicht klappt, werde ich den 1 Ansatz nehmen. Aber da müsste ich erstmal schauen wie ich das genau handeln kann.

Auf jeden Fall erstmal Danke =)


----------



## Ebenius (23. Mrz 2009)

Ich hatte eigentlich vorgehabt den zweiten Vorschlag heimlich zu vergessen, weil der erste meines Erachtens schöner ist. 

In jedem Fall: Gutes Gelingen.

Ebenius


----------



## thE_29 (23. Mrz 2009)

Nur verstehe ich nicht, wie ihm der erste Vorschlag was bringen soll?!

Du kapselst die JNI Ladung/Ausführung in eine extra Klasse? Trotzdem kann man nicht mehr laden.. Wozu also?


----------



## Ebenius (23. Mrz 2009)

Ich meinte das in etwa so: [Highlight=Java]public class ApoSoccerAIJNI {
  static {
    System.load("aistub");
  }

  /** Let the AI stub use the implementation library with the given name */
  public static native void setAILibName(String libName);

  /** Get the AI implementation library name from the AI stub */
  public static native String getAILibName();

  public static native String getTeamName();

  //... hier dann die ganzen anderen Interfacemethoden
}[/Highlight]
Ebenius


----------



## Apo (23. Mrz 2009)

Sorry, dass ich schon wieder eine Frage habe.

Ich kann zur Laufzeit Java-Klassen laden. Man wählt einfach mithilfe eines FileChoosers seine class-Datei aus und läd sie. Das funktioniert wunderbar. (Zum Glück =) )
Jetzt wollte ich den gleichen eigenen ClassLoader nehmen um die ApoSoccerJNIAI zu laden, aber ich bekomme folgenden Fehler:  java.lang.ClassCastException: apoSoccer.entityForAI.ApoSoccerAIJNI cannot be cast to apoSoccer.entityForAI.ApoSoccerAIJNI 
Das verwirrt mich. Da steht doch eigentlich, dass es wirklich die Klasse ist, in die ich casten möchte. Er findet die Klasse und kann sie Laden, bloss das casten vom Object in die Klasse funktioniert nicht ... Ich bin etwas verwirrt. Woran könnte das liegen? Ich danke euch =)

der wichtigste Code von der Classloaderklasse:

```
public ApoSoccerAIJNI getAIDll() {
		ApoSoccerAIJNI ai = null;
		Class c;
		try {
			c = loadClass( this.name );
			Object o;
			try {
				o = c.newInstance();
				System.out.println(o.getClass());
				ai = (ApoSoccerAIJNI)o;
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return ai;
	}
	
	@SuppressWarnings({ "unchecked", "deprecation" })
	public Class loadClass(String name) throws ClassNotFoundException {
		Class c;
		try {
			c = findLoadedClass(name);

			if (c == null) {
				String filename = name.replace('.', File.separatorChar)	+ ".class";
				try {
					byte data[] = loadClassData(filename);
					c = defineClass(data, 0, data.length);
					System.out.println("class = "+c.getName()+" ");
				} catch (IOException e) {
				} catch (NoClassDefFoundError e) {
				}
			}

			if (c == null) {
				try {
					c = findSystemClass(name);
				} catch (Exception e) {
				}
			}

			if (c == null) {
				throw new ClassNotFoundException(name);
			}

		} catch (Throwable ex) {
			throw new ClassNotFoundException(name);
		}

		return c;
	}

	private byte[] loadClassData(String filename) throws IOException {

		File f = new File(this.root, filename);
		
		int size = (int) f.length();
		
		byte buff[] = new byte[size];

		FileInputStream fis = new FileInputStream(f);
		DataInputStream dis = new DataInputStream(fis);

		dis.readFully(buff);

		dis.close();

		return buff;
	}
```


----------



## Ebenius (23. Mrz 2009)

Es sind zwei verschiedene Klassen, da sie in unterschiedlichen ClassLoader-Kontexten geladen wurden. Die Klassen werden vollkommen unabhängig voneinander initialisiert.

Ebenius


----------



## thE_29 (23. Mrz 2009)

@Ebenius: Und was soll das bringen? Ich verstehe den Sinn dahinter nicht ganz... (also von deiner JNI Klasse da oben).


----------



## Apo (23. Mrz 2009)

Nunja, wenn ich aber über den Parent von der ApoSoccerAIJNI-Klasse gehe, sagt er, er findet die Klasse nicht.
Darf ich (hoffentlich) ein letztes Mal fragen, wie ich vorgehen muss, um die gleiche Klasse zu erhalten bzw. das ich korrekt casten kann?
Ich danke =)


----------



## Ebenius (23. Mrz 2009)

thE_29 hat gesagt.:


> @Ebenius: Und was soll das bringen? Ich verstehe den Sinn dahinter nicht ganz... (also von deiner JNI Klasse da oben).


Der Sinn ist, dass ich 
die austauschbare Implementierung nicht direkt an der Java-Klasse hängen habe
mir den den JNI-Krempel in den einzelnen Implementierungen sparen kann und
nicht das Problem habe, dass ich ständig meine Java-Klasse loswerden muss, um die Bibliothek neu laden zu können (falls das überhaupt funktioniert)



Apo hat gesagt.:


> Nunja, wenn ich aber über den Parent von der ApoSoccerAIJNI-Klasse gehe, sagt er, er findet die Klasse nicht. Darf ich (hoffentlich) ein letztes Mal fragen, wie ich vorgehen muss, um die gleiche Klasse zu erhalten bzw. das ich korrekt casten kann?


Ich kann Dir dabei gar nicht weiter helfen. Mit eigenen ClassLoader-Implementationen habe ich vor über sieben Jahren mal kurz zu tun gehabt. Keine Ahnung wie das alles funktionierte. 

Ebenius


----------



## Apo (23. Mrz 2009)

So ich habe es nun mit ApoSoccerJNIAI.class.newInstance() versucht, aber da lande ich wieder beim alten Problem. Ich werde mich nun wohl oder übel mit dem ersten Vorschlag beschäftigen. Aber dieser sollte wirklich klappen (hoffe ich mal =) ).
Ich melde mich wieder, falls es a.) geklappt hat und/oder b.) ich noch Fragen habe. =)
Danke soweit auf jeden Fall. =)


----------



## thE_29 (24. Mrz 2009)

@Ebenius: Jo, aber ich kapsel die JNI Ladung + Methoden sowieso immer in einer eigenen Klasse?! Was hat das also für Vorteile? Ich werde da nicht ganz schlau draus...
Und wie willst du da nochmals eine DLL Laden?! Die DLLs sind ja package spezifisch. Da kannste nicht in Klasse A.B.C ein Dll laden die für C.D.E gemacht wurde.


----------



## Ebenius (24. Mrz 2009)

thE_29 hat gesagt.:


> @Ebenius: Jo, aber ich kapsel die JNI Ladung + Methoden sowieso immer in einer eigenen Klasse?! Was hat das also für Vorteile? Ich werde da nicht ganz schlau draus...
> Und wie willst du da nochmals eine DLL Laden?! Die DLLs sind ja package spezifisch. Da kannste nicht in Klasse A.B.C ein Dll laden die für C.D.E gemacht wurde.


Häh? Was? Wie? Ich kann doch wohl in aistublib per C/C++ eine andere Bibliothek verwenden. Warum denn nicht?

Es gibt dann nur noch *eine* package-spezifische DLL. Die heißt aistublib (in meinem Beispiel). Diese macht nix anderes als andere Bibliotheken zu benutzen, die zu keinem Package gehören, weil sie mit Java gar nix mehr am Hut haben.

Ebenius


----------



## tuxedo (24. Mrz 2009)

Scheint so als redet ihr gerade aneinander vorbei.

Wenn ich Ebenius richtig verstanden habe, dann besteht die (möglich) Lösung darin, von Java-Seite aus nur eine DLL zu laden, die sich dann um das laden und entladen der KI-DLLs kümmert. Somit wäre es nicht mehr aufgabe der JVM die KI-DLLs zu laden und entladen.

Oder hab ich nun auch etwas missverstanden?

- Alex


----------



## Spacerat (24. Mrz 2009)

Da hilft wohl nur ein JNI-Basierender ClassLoader. Entspricht der Lösung 1 von Ebenius. In Java erstellt man eigentlich nur ein Interface (oder auch eine Abstrakte Klasse) (KI bzw. AI) mit allen Methoden die man dafür braucht. Auf der JNI-Seite bildet man dieses Interface nach und hat so schonmal alle Funktions- bzw. Methoden-Prototypen. Nun können beliebige KIs auf JNI-Seite gebastelt werden. Die DLL muss im Prinzip nur noch feststellen, mit was für Mitteln die KI-Klasse kompiliert wurde (z.B. C#, C++, Java, PHP) und die Methodenaufrufe dorthin delegieren. Laden und Entladen findet dann ebenfalls auf JNI-Seite statt. Wie die DLL an die Dateinamen der KI-Klasse kommt hat Ebenius ebenfalls schon oben erklärt. Hoffe das war einigermassen Verständlich... Ich und Erklärungen...


----------



## Ebenius (24. Mrz 2009)

tuxedo hat es so verstanden wie ich es gemeint habe und kann es besser beschreiben. Danke dafür.

Spacerat, das wird viel komplizierter als mein Vorschlag, deswegen würde ich davon abraten.

Ebenius


----------



## Spacerat (24. Mrz 2009)

Apo hat gesagt.:
			
		

> Es ist für ein Spiel gedacht, wo man seine KI selber schreiben darf (unter anderem auch mit c++ oder c# und nicht nur mit Java)


@Ebenius: Wenn er sowas vorhat, wird er wohl kaum drum herum kommen. Irgendwie muss er doch die Java-Seite auf die "unbestimmte" Seite abbilden. Natürlich müsste er dann auch für jede Sprache, die er unterstützen will, ebenfalls so ein Interface bauen. Wenn du einen einfacheren Weg kennst, würd' ich den gerne auch kennen.
@Edit: Ähm... Ich bemerke gerade, das "abstrakte Klasse" auf Javaseite gar nicht sein muss (bzw. darf). Es muss ein Native-Interface sein, also eine Klasse (möglicherweise sogar "final") deren Methoden durchweg "native" sind.


----------



## thE_29 (24. Mrz 2009)

Sag doch gleich, dass die erste DLL sich um die 2te DLL kümmern soll 

Jo, das wäre eigentlich der beste Ansatz! Die erste DLL stellt alle Interface Methoden zur Verfügung und in dieser DLL kann/muss man dann einen Lademechanismus haben, welcher jenachdem eine DLL lädt oder eine andere (am besten sagt man einer Methode laded jetzt DLL 1, DLL 2, etc..).

Pluspunkt Ebenius  Aber das nächste mal einfach klar ausdrücken damits auch die Österreicher verstehen


----------



## Apo (24. Mrz 2009)

Ebenius Vorschlag wird grad umgesetzt.
Es hat wirklich auch den Vorteil, dass die Leute sich nicht mit den JNI-spezifischen Sachen herumärgern müssen, sondern die Mittelsmann-dll kümmert sich nicht nur um das Ansprechen der jeweiligen KI, sondern kann mithilfe eines eigenen Interface bzw header Datei dafür sorgen, dass die KI wirklich nur in C++ und C# Code geschrieben wird.

Ich melde mich wieder, wenn es geklappt hat. (noch bin ich positiver Dinge)


----------



## thE_29 (24. Mrz 2009)

@Apo: Hast du das was Ebenius wollte auch gleich verstanden oder bin ich der einzige mit der langen Leitung


----------



## Ebenius (24. Mrz 2009)

Spacerat hat gesagt.:


> @Ebenius: Wenn er sowas vorhat, wird er wohl kaum drum herum kommen. Irgendwie muss er doch die Java-Seite auf die "unbestimmte" Seite abbilden. Natürlich müsste er dann auch für jede Sprache, die er unterstützen will, ebenfalls so ein Interface bauen. Wenn du einen einfacheren Weg kennst, würd' ich den gerne auch kennen.


Wie jetzt? Wenn er DLLs laden will, dann muss er natürlich nicht pro Sprache was neues bauen. Library ist Library; mit welcher Sprache diese erzeugt wurde ist dabei vollkommen wurscht. Was auch immer die "unbestimmte Seite" ist; die Bindung an die Implementierung macht dann der Stub (native Library per JNI eingebunden). Und natürlich muss nicht jede Methode native sein. Und die Klasse muss auch nicht final sein. Siehe bspw. java.io.FileSystem.



thE_29 hat gesagt.:


> @Apo: Hast du das was Ebenius wollte auch gleich verstanden oder bin ich der einzige mit der langen Leitung


Spacerat scheint auch mit verlängertem Kabel zu arbeiten, aber ich weiß nicht, ob das an mir liegt.

Ebenius


----------



## Spacerat (24. Mrz 2009)

Ähm... Die "unbestimmte Seite" sind X-Beliebige Programmiersprachen: Java erzeugt schon mal keine DLL, PHP und JavaScript da hat man Scripte, C++ da hat man eine DLL, C# keine Ahnung wohl auch 'ne DLL. Die DLL die nun von der Java-Anwendung gestartet wird muss das letztendlich unterscheiden können. Und um die Methoden dann an die geladene KI-Implementation weiter zu leiten wird halt noch dieses Interface benötigt. Ich kanns gerne aufzeichnen. Dauert 'ne Weile.
@Edit: Soooo. Hab's mal rasch aufgekritzelt, wie ich das meine.


----------



## thE_29 (25. Mrz 2009)

Wir gehen aber davon aus, dass er verschiedene DLLs laden will und nicht verschiedene Sprachen 

Da ich ja keinem User zwingend vorraussetzen kann, dass er zB php.exe installiert hat. Macht man ne DLL raus (was in Windows mit den meisten nativen Sprachen geht) dann sieht das ganze anders aus (und funktioniert auch überall wenn es static gelinkt ist).


----------



## Spacerat (25. Mrz 2009)

Ja... gut... dan lässt er halt die Sprachen, wofür möglicherweise externe Interpreter oder ähnliches benötigt werden weg. Dann entfällt lediglich die Dateitypeerkennung. Trotzdem muss er die DLLs gegen ein Interface linken oder nicht? Das sollte nur ein paar weiter Möglichkeiten aufzeigen.


----------



## Apo (25. Mrz 2009)

Naja Java-Klassen kann er schon gut lesen. =)
Und da bei uns an der Uni viele andere Studienrichtungen wie Maschinenbau sich eher mit C++ beschäftigen als mit Java, wollte ich diese gerne noch mit ins Boot holen für den diesjährigen Programmierwettbewerb. Deshalb wollte ich "nur" erstmal die C-Sachen abdecken.
Ein weiterer Grund ist, da wir dieses Jahr auch Preise für externe Teilnehmer haben, wollte ich die Leute, die mit einer dll kommunizieren können, nicht ausschließen. Es können alle mitmachen. Auch ihr =)
Am Wochenende habe ich endlich wieder Zeit und kann mich darum kümmern und bekannt geben, ob das Projekt "Mittelsmann-dll" geklappt hat. =)


----------



## Apo (30. Mrz 2009)

Wollte nur bescheid sagen, dass es mit der "Mittelsmann"-dll klappt. Außerdem ist noch ein Interface gebastelt worden, damit man sich nicht mit den JNI Sachen rumärgern muss. 
Ich danke nochmal für die Idee mit der Mittelsmann-dll

Alles in allem kann nun endlich der Wettbewerb am 09.04.2009 losgehen. Ihr dürft gerne mitmachen und die feinen Preise mit abräumen. Ich darf als Leiter leider nicht mitmachen.


----------

