Hallo!
Zur Zeit wurmt mich die AppDomain. Ich versuche eine eigenständige AppDomain zu erstellen um Assemblies zu laden und mittels Reflection Typ-Informationen auszulesen. Allerdings möchte mir das nicht gelingen.
Ich habe schon einiges zu dem Thema von Microsoft gelesen und alle Beispiel laufen entweder darauf hinaus eine Assembly mittels den CreateInstance-Methoden der AppDomain-Klasse zu laden, oder mittels ExecuteAssembly auszuführen. Beide Methoden laden die Assembly nur in der jeweiligen AppDomain ein. Jedoch sind beide Möglichkeiten für mich unakzeptabel.
Für CreateInstance benötige ich bereits vor dem Zugriff auf eine Assembly Informationen über die definierten Typen in der Assembly. Da ich aber genau diese Information zur Laufzeit auslesen möchte, kann ich dies nicht realisieren. Zusätzlich besteht bei CreateInstance* das Problem dass der Typ einen parameterlosen Konstruktor benötig. Auch dies kann ich nicht für jeden Typ garantieren.
Die zweite Methode mittels ExecuteAssembly ist noch schlechter, da ich dafür eine ausführbare Datei brauche und zudem der Einstiegspunkt der Assembly aufgerufen und ausgeführt wird.
Der Hintergrund ist folgender: Ich habe eine Liste verschiedener Assemblies die geladen und auf die mittels Reflection zugegriffen werden soll. Ansich wäre das kein Problem die Assemblies auch in die CurrentDomain einzuladen.
Jedoch soll es die Möglichkeit geben die gleiche Assembly nur in einer anderen Version ebenfalls einzuladen. Dazu brauch ich jedoch zwangsläufig unterschiedliche AppDomains.
Alle möglichen Herangehensweisen sind bisher kläglich gescheitert. Ich schaff es einfach nicht eine Assembly in eine bestimmte AppDomain zu bringen.
Hier ist mein momentaner Testcode zu dem Problem:
public class Program
{
const int BUFFERLEN = 2048;
// Irgendeine Assembly einladen
readonly static string[] ASSEMBLIES = new string[] {
@"C:\WINXP\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Tasks.dll"};
static void Main(string[] args)
{
System.Collections.Generic.List<System.Reflection.Assembly> loadedAssems = new System.Collections.Generic.List<System.Reflection.Assembly>();
System.Reflection.Assembly[] curAppDomAssems;
// Separate AppDomain erstellen
System.AppDomainSetup appDomSetup = new System.AppDomainSetup();
appDomSetup.LoaderOptimization = System.LoaderOptimization.SingleDomain;
System.AppDomain appDom = System.AppDomain.CreateDomain("DomDom", null, appDomSetup);
// Resolve-Handler registrieren
appDom.AssemblyResolve += new System.ResolveEventHandler(appDom_AssemblyResolve);
System.AppDomain.CurrentDomain.AssemblyResolve += new System.ResolveEventHandler(CurrentDomain_AssemblyResolve);
// Assemblies einladen
foreach (string assemFile in ASSEMBLIES)
{
byte[] assem = Program.Read(assemFile);
System.Console.ForegroundColor = System.ConsoleColor.Gray;
System.Console.WriteLine("Lade Assembly: {0}", assemFile);
if (assem != null)
try
{
// AppDomain.Load scheint für für solche Aktionen vorgesehen zu sein.
// Es werden keine Dependencies aufgelöst, auch wird nicht das richtige AssemblyResolve-Event ausgelöst.
loadedAssems.Add(appDom.Load(assem));
}
catch (System.Exception ex)
{
System.Console.ForegroundColor = System.ConsoleColor.Red;
System.Console.WriteLine(ex.Message);
System.Console.ForegroundColor = System.ConsoleColor.Gray;
System.Console.WriteLine("Assembly.Load für Testzwecke aufrufen");
// Testweise Assembly mittels Assembly.Load einladen
try
{
System.Reflection.Assembly loadedAssem = System.Reflection.Assembly.Load(assem);
curAppDomAssems = System.AppDomain.CurrentDomain.GetAssemblies();
// Pech ... mittels Assembly.Load wird immer in die CurrentDomain geladen
if (System.Array.IndexOf(curAppDomAssems, loadedAssem) > -1)
System.Console.WriteLine("Assembly in CurrentDomain geladen");
}
catch (System.Exception ex2)
{
System.Console.ForegroundColor = System.ConsoleColor.Red;
System.Console.WriteLine(ex2.Message);
}
}
}
// Eingeladene Assemblies testen
System.Console.ForegroundColor = System.ConsoleColor.Gray;
System.Console.WriteLine();
System.Console.WriteLine("Geladene Assemblies prüfen");
System.Reflection.Assembly[] appDomAssems;
try
{
appDomAssems = appDom.GetAssemblies();
}
catch (System.Exception)
{
System.Console.WriteLine("Fehler beim holen der geladenen Assemblies von appDom");
appDomAssems = new System.Reflection.Assembly[] { };
}
int iter = 0;
do
{
curAppDomAssems = System.AppDomain.CurrentDomain.GetAssemblies();
iter++;
foreach (System.Reflection.Assembly loadedAssem in loadedAssems)
{
System.Console.WriteLine();
System.Console.WriteLine("Prüfe Assembly {0}", loadedAssem.GetName().Name);
if (System.Array.IndexOf(appDomAssems, loadedAssem) > -1)
System.Console.WriteLine("Treffer appDomAssems");
else
System.Console.WriteLine("Kein Treffer appDomAssems");
if (System.Array.IndexOf(curAppDomAssems, loadedAssem) > -1)
System.Console.WriteLine("Treffer curAppDomAssems");
else
System.Console.WriteLine("Kein Treffer curAppDomAssems");
}
if (iter == 1)
{
System.Console.WriteLine("Entlade appDom");
System.AppDomain.Unload(appDom);
appDomAssems = new System.Reflection.Assembly[] { };
}
} while (iter < 2);
System.Console.ReadLine();
}
static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, System.ResolveEventArgs args)
{
System.Console.WriteLine("Assembly resolve in current domain: {0}", args.Name);
return null;
}
// Wird nie angesprochen!
static System.Reflection.Assembly appDom_AssemblyResolve(object sender, System.ResolveEventArgs args)
{
System.Console.WriteLine("Assembly resolve in appDom: {0}", args.Name);
return null;
}
static byte[] Read(string File)
{
System.IO.FileInfo fileInfo = new System.IO.FileInfo(File);
byte[] buffer;
byte[] data = null;
if (!fileInfo.Exists)
return null;
System.IO.FileStream ioFile = fileInfo.Open(System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.Read);
if (ioFile.CanRead)
{
int read = 0;
data = new byte[fileInfo.Length];
buffer = new byte[BUFFERLEN];
for (int i = 0; i < fileInfo.Length; i += read)
{
if (fileInfo.Length - BUFFERLEN >= i)
read = ioFile.Read(buffer, 0, BUFFERLEN);
else
read = ioFile.Read(buffer, 0, data.Length - BUFFERLEN);
System.Array.Copy(buffer, 0, data, i, read);
}
}
return data;
}
}
Ausgabe:
Lade Assembly: C:\WINXP\Microsoft.NET\Framework\v2.0.50727\Microsoft.Build.Tasks.dll
Geladene Assemblies prüfen
Prüfe Assembly Microsoft.Build.Tasks
Treffer appDomAssems
Treffer curAppDomAssems
Entlade appDom
Prüfe Assembly Microsoft.Build.Tasks
Kein Treffer appDomAssems
Treffer curAppDomAssems
Das tolle ist, obwohl der Aufruf von AppDomain.Load mit dieser Assembly funktioniert (die Assembly und ihre Dependencies können aufgelöst werden), wird die Assembly a) in CurrentDomain geladen und b) beim entladen von appDom aber auch nicht wieder aus CurrentDomain entfernt.
Testweise kann Microsoft.Build.Task.dll jedoch auch gegen eine eigene Assembly ausgetauscht werden die nicht im GAC liegt (z. B. log4net).
Habe ich einen Fehler gemacht, oder gibt es einen anderen Weg der zur Lösung führt?
Hallo JBuechner,
dein Vorhaben ähnelt einem Plugin-System.
Dazu geht man im Allgemeinen in etwa so vor:1.Suche alle potentiellen Assemblies
1.Erstelle für jede Assembly eine eigene AppDomain
1.Lade einen Loader / eine Bridge in die AppDomain
1.Die Bridge lädt die fragliche Assembly und überprüft, ob Sie ein Plugin ist (oder sonstige Anforderungen erfüllt)
1.Wenn nein, wird die AppDomain von außen wieder gelöscht
1.Wenn ja, kann die Bridge mit Reflection (Constructor mit Argumenten manuell aufrufen) oder mit Activator.CreateInstance() eine Instanz erstellen und mit der Arbeiten
Du versuchst momentan die Assembly direkt zu laden / auszuführen. Wenn du aber erst eine Bridge in die Domain lädst, hast du mit dieser auf der anderen Seite erstmal einen Ansprechpartner. Die Bridge kann dann Reflection nach Belieben verwenden und auch parametrisierte Konstruktoren aufrufen.
Noch ein Stolperstein: Wenn du auf Klassen in der AppDomain von einer anderen AppDomain (die Hauptdomain z.B. also aus dem Hauptprogramm) zugreifen möchtest, sollten diese Klassen von ManagedByRefObject abgeleitet sein und evtl. noch serialisierbar (Attribut). Das gilt insbesondere für die Bridge.
Zu Plugin-Systemen gibt es hier schon einige Threads:
Wie AppDomains überwienden für Plug-In Zugriff?
Erweiterungssystem realisieren mit Code-Beispiel
Mfg NeuroCoder
Hallo NeuroCoder,
ich bin heute dazu gekommen deinen Vorschlag in die Tat umzusetzen und was soll ich sagen, es funktioniert problemlos. Danke für deinen Hinweis mit der separaten Domain und der Bridge.
Hier ist einmal der Code, den man je nach Anwendung und Bedarf noch viel weiter aufboren kann.
Mfg Jens
namespace custappdom
{
public class Program
{
public static void Main(string[] Args)
{
// Neue Domain erstellen
CustomAppDomain custAppDom = new CustomAppDomain("ExampleAppDomain", BeliebigerPfadZurAnwendung, typeof(AppDomainBridge));
// Assembly in neue Domain einladen
IWrappedAssembly assem = custAppDom.Bridge.LoadAssembly(PfadZurLadendenAssembly);
System.Console.WriteLine("Geladen in separater Domain: {0}", assem.GetName().FullName);
System.Console.WriteLine();
// Assemblies der aktuellen Domain ausgeben
System.Reflection.Assembly[] thisLoadedAssems = System.AppDomain.CurrentDomain.GetAssemblies();
System.Console.WriteLine("Geladene Assemblies von CurrentDomain");
Program.Print(thisLoadedAssems);
System.Console.WriteLine();
System.Console.ReadLine();
}
static void Print(System.Reflection.Assembly[] Assemblies)
{
int i = 0;
foreach (System.Reflection.Assembly assem in Assemblies)
System.Console.WriteLine("[{0}] {1}", ++i, assem.GetName().Name);
}
}
#region "Custom application domain"
public interface ICustomAppDomain
{
System.AppDomain AppDomain { get;}
IAppDomainBridge Bridge { get;}
}
public class CustomAppDomain : ICustomAppDomain
{
System.AppDomain _appDom;
IAppDomainBridge _bridge;
public System.AppDomain AppDomain
{
get
{
return this._appDom;
}
protected set
{
this._appDom = value;
}
}
public IAppDomainBridge Bridge
{
get
{
return this._bridge;
}
protected set
{
this._bridge = value;
}
}
public CustomAppDomain(string Name, string ApplicationPath, System.Type AppDomainBridgeType)
{
System.Exception codeBaseEx = null;
if (string.IsNullOrEmpty(Name))
throw new System.ArgumentNullException("Name");
if (string.IsNullOrEmpty(ApplicationPath))
throw new System.ArgumentNullException("ApplicationPath");
if (AppDomainBridgeType == null)
throw new System.ArgumentNullException("AppDomainBridgeType");
if (AppDomainBridgeType.GetInterface(typeof(IAppDomainBridge).FullName) == null)
throw new System.ArgumentException(AppDomainBridgeType.Name + " does not implement IAppDomainBridge interface.", "AppDomainBridgeType");
try
{
System.IO.FileInfo file = new System.IO.FileInfo(System.Uri.UnescapeDataString(new System.Uri(AppDomainBridgeType.Assembly.CodeBase).AbsolutePath));
if (!file.Exists)
codeBaseEx = new System.IO.FileNotFoundException("Codebase does not exist.", file.FullName);
}
catch (System.Exception ex)
{
codeBaseEx = ex;
}
if (codeBaseEx != null)
throw new System.ArgumentException("Assembly of " + AppDomainBridgeType.Name + " type doesn't have a valid codebase.", "AppDomainBridgeType", codeBaseEx);
System.AppDomainSetup setup = new System.AppDomainSetup();
setup.ApplicationName = Name;
setup.ApplicationBase = ApplicationPath;
setup.PrivateBinPath = ApplicationPath;
setup.PrivateBinPathProbe = ApplicationPath;
setup.DisallowBindingRedirects = true;
setup.DisallowCodeDownload = true;
setup.ShadowCopyFiles = "true";
this.AppDomain = System.AppDomain.CreateDomain(Name, null, setup);
this.Bridge = this.AppDomain.CreateInstanceFromAndUnwrap(AppDomainBridgeType.Assembly.CodeBase, AppDomainBridgeType.FullName) as IAppDomainBridge;
}
}
#endregion
#region "domain bridge"
public interface IAppDomainBridge
{
IWrappedAssembly LoadAssembly(string AssemblyFile);
}
[System.Serializable()]
public class AppDomainBridge : System.MarshalByRefObject, IAppDomainBridge
{
public AppDomainBridge()
{
}
public IWrappedAssembly LoadAssembly(string AssemblyFile)
{
System.Reflection.Assembly assem = System.Reflection.Assembly.LoadFrom(AssemblyFile);
return new WrappedAssembly(assem);
}
}
#endregion
#region "wrapped objects"
public interface IWrappedAssembly
{
System.Reflection.AssemblyName GetName();
}
[System.Serializable()]
public class WrappedAssembly : System.MarshalByRefObject, IWrappedAssembly
{
System.Reflection.Assembly _assem;
protected System.Reflection.Assembly Assembly
{
get
{
return this._assem;
}
set
{
this._assem = value;
}
}
public WrappedAssembly(System.Reflection.Assembly Assembly)
{
this.Assembly = Assembly;
}
public System.Reflection.AssemblyName GetName()
{
return this.Assembly.GetName();
}
}
#endregion
}
Hallo,
ich habe eine Frage zum Beispiel von JBuechner.
Mit dem Code
IWrappedAssembly assem = custAppDom.Bridge.LoadAssembly(PfadZurLadendenAssembly);
ruft man die LoadAssembly Methode auf. Wird dieser Code im Speicher der neu erstellten AppDomain ausgeführt?
Wenn ja, würde das bedeudeten, dass sämtliche Assemlys die man mit Assembly.LoadFrom ladet in der neu erstellten AppDomain hinein geladen werden? Hab ich das richtig verstanden?
public IWrappedAssembly LoadAssembly(string AssemblyFile)
{
System.Reflection.Assembly assem = System.Reflection.Assembly.LoadFrom(AssemblyFile);
return new WrappedAssembly(assem);
}
Außerdem finde ich in dem Beispiel nirgends ein Activator, der die geladene Assembly instanziert.
Schaut mal im IRC vorbei:
Server: https://libera.chat/ ##chsarp