Assemblies zur Laufzeit ersetzen
Seit meinem TechTalk über "Kompilieren statt Interpretieren" treibt mich die Frage um, wie Assemblies zur Laufzeit ausgetauscht werden können.
Ausgangspunkt der Überlegung sind zwei Probleme:
- Assemblies können nicht einzeln aus einem Prozess entladen werden.
- Assemblies sind auf der Festplatte gesperrt, sobald sie in einem Prozess geladen wurden.
Für das erste Problem ist der Lösungsansatz einfach: Assemblies werden in AppDomains geladen und AppDomains können auch wieder entladen werden. Damit verschwinden auch die darin geladenen Assemblies aus dem Speicher. Um Assemblies nach Gebrauch entsorgen zu können, müssen sie also nur in eine eigene AppDomain geladen werden.
dim info as New AppDomainSetup()
with appdomain.CurrentDomain.SetupInformation
info.ApplicationBase = .ApplicationBase
info.PrivateBinPath = "."
end with
dim app as AppDomain
app = AppDomain.CreateDomain("MyDomain", _
AppDomain.CurrentDomain.Evidence, info)
dim c as Class1
c = app.CreateInstanceAndUnwrap("MyLib", "MyLib.Class1")
...
appdomain.Unload(app)
Class1 in diesem Beispiel muss allerdings von MarshalByRefObject abgeleitet sein, da sonst kein Cross-AppDomain-Aufruf möglich ist.
Allerdings tritt auch dann nicht der gewünschte Effekt ein, da die aufrufende AppDomain ebenfalls die Assembly MyLib.dll laden muss, um den Proxy für die Class1-Instanz in der neuen AppDomain zu erzeugen.
Das bedeutet: Um eine Assembly mit einer AppDomain auch wieder zu entladen, darf außerhalb der AppDomain keinerlei Bezug zu der Assembly existieren! Um mit Typen aus der Assembly zu arbeiten, müssen alle Interaktionen durch Objekte laufen, deren Assemblies nicht entladen werden müssen. Für das obige Beispiel würde das z.B. bedeuten:
dim c as Class1Proxy
c = app.CreateInstanceAndUnwrap("MyApp", "MyApp.Class1Proxy")
Die Klasse Class1Proxy könnte zum Beispiel in der aufrufenden Assembly definiert sein, die schon in der aufrufenden AppDomain geladen ist. Diese Assembly würde dann zwar auch noch einmal in die neue AppDomain geladen, aber das macht nichts. Sie ist ja ohnehin schon im Speicher und soll daraus nicht verschwinden.
Zu beachten ist dann allerdings, dass innerhalb von Class1Proxy kein Typ aus der "wahren" zu ladenden Assembly an ihrer Schnittstelle auftaucht. Also keine Member-Variable und kein Parameter eine Methode darf von einem Typ der zu ladenden Assembly sein, denn sonst wird sie auch in der aufrufenden AppDomain benötigt, um den Proxy zu bauen.
Problem 1 ist damit gelöst: Zur Laufzeit kann ein Programm nicht mehr benötigte Assemblies los werden. Und Problem 2? Dazu morgen mehr.