Wie Laden einer Assembly in AppDomain mit allen Referenzen rekursiv?

Ich möchte zu einer neuen AppDomain eine Assembly laden, die eine komplexe Verweistabelle (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> Stdole.dll hat )

So weit ich es verstanden habe, wenn eine Assembly in AppDomain geladen AppDomain , werden ihre Referenzen nicht automatisch geladen, und ich muss sie manuell laden. Also wenn ich das tue:

 string dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory string path = System.IO.Path.Combine(dir, "MyDll.dll"); AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation; setup.ApplicationBase = dir; AppDomain domain = AppDomain.CreateDomain("SomeAppDomain", null, setup); domain.Load(AssemblyName.GetAssemblyName(path)); 

und habe FileNotFoundException :

Datei oder Assembly konnte nicht geladen werden ‘MyDll, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null’ oder eine seiner Abhängigkeiten. Die angegebene Datei wurde vom System nicht gefunden.

Ich denke, der Schlüssel ist eine seiner Abhängigkeiten .

Ok, mache ich als nächstes vor domain.Load(AssemblyName.GetAssemblyName(path));

 foreach (AssemblyName refAsmName in Assembly.ReflectionOnlyLoadFrom(path).GetReferencedAssemblies()) { domain.Load(refAsmName); } 

Aber FileNotFoundException erneut in einer anderen (referenzierten) Assembly.

Wie laden Sie alle Referenzen rekursiv?

Muss ich vor dem Laden der Root-Assembly einen Referenzbaum erstellen? Wie bekommt man die Referenzen einer Baugruppe, ohne sie zu laden?

Sie müssen CreateInstanceAndUnwrap aufrufen, bevor das CreateInstanceAndUnwrap in der fremden Anwendungsdomäne ausgeführt wird.

  class Program { static void Main(string[] args) { AppDomainSetup domaininfo = new AppDomainSetup(); domaininfo.ApplicationBase = System.Environment.CurrentDirectory; Evidence adevidence = AppDomain.CurrentDomain.Evidence; AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo); Type type = typeof(Proxy); var value = (Proxy)domain.CreateInstanceAndUnwrap( type.Assembly.FullName, type.FullName); var assembly = value.GetAssembly(args[0]); // AppDomain.Unload(domain); } } public class Proxy : MarshalByRefObject { public Assembly GetAssembly(string assemblyPath) { try { return Assembly.LoadFile(assemblyPath); } catch (Exception) { return null; // throw new InvalidOperationException(ex); } } } 

Beachten Sie außerdem, dass Sie bei Verwendung von LoadFrom wahrscheinlich eine FileNotFound Ausnahme erhalten, da der Assembly-Resolver versucht, die Assembly zu finden, die Sie in den GAC oder den bin-Ordner der aktuellen Anwendung laden. Verwenden LoadFile stattdessen LoadFile , um eine beliebige Assemblydatei zu laden. Beachten Sie jedoch, dass Sie alle Abhängigkeiten selbst laden müssen.

http://support.microsoft.com/kb/837908/en-us

C # -Version:

Erstellen Sie eine Moderatorklasse und erben Sie sie von MarshalByRefObject :

 class ProxyDomain : MarshalByRefObject { public Assembly GetAssembly(string assemblyPath) { try { return Assembly.LoadFrom(assemblyPath); } catch (Exception ex) { throw new InvalidOperationException(ex.Message); } } } 

Anruf von der Kundenseite

 ProxyDomain pd = new ProxyDomain(); Assembly assembly = pd.GetAssembly(assemblyFilePath); 

Versuchen Sie in Ihrer neuen Anwendungsdomäne, einen AssemblyResolve- Ereignishandler festzulegen. Dieses Ereignis wird aufgerufen, wenn eine Abhängigkeit fehlt.

Sobald Sie die Assembly-Instanz zurück an die Anruferdomäne übergeben, versucht die aufrufende Domäne, sie zu laden! Aus diesem Grund erhalten Sie die Ausnahme. Dies geschieht in Ihrer letzten Codezeile:

 domain.Load(AssemblyName.GetAssemblyName(path)); 

Was immer Sie mit der Assembly machen wollen, sollte also in einer Proxy-class geschehen – einer class, die MarshalByRefObject erbt.

Nehmen Sie an, dass die aufrufende Domäne und die neu erstellte Domäne beide Zugriff auf die Proxy-classnassembly haben sollten. Wenn das Problem nicht zu kompliziert ist, sollten Sie den ApplicationBase-Ordner unverändert lassen, damit er dem Domänenordner der aufrufenden Seite entspricht (die neue Domäne lädt nur Assemblies, die sie benötigt).

Im einfachen Code:

 public void DoStuffInOtherDomain() { const string assemblyPath = @"[AsmPath]"; var newDomain = AppDomain.CreateDomain("newDomain"); var asmLoaderProxy = (ProxyDomain)newDomain.CreateInstanceAndUnwrap(Assembly.GetExecutingAssembly().FullName, typeof(ProxyDomain).FullName); asmLoaderProxy.GetAssembly(assemblyPath); } class ProxyDomain : MarshalByRefObject { public void GetAssembly(string AssemblyPath) { try { Assembly.LoadFrom(AssemblyPath); //If you want to do anything further to that assembly, you need to do it here. } catch (Exception ex) { throw new InvalidOperationException(ex.Message, ex); } } } 

Wenn Sie die Assemblys aus einem Ordner laden müssen, der sich von Ihrem aktuellen App-Domänenordner unterscheidet, erstellen Sie die neue App-Domäne mit dem Suchpfadordner für bestimmte DLLs.

Die Erstellungszeile der App-Domain aus dem obigen Code sollte beispielsweise wie folgt ersetzt werden:

 var dllsSearchPath = @"[dlls search path for new app domain]"; AppDomain newDomain = AppDomain.CreateDomain("newDomain", new Evidence(), dllsSearchPath, "", true); 

Auf diese Weise werden alle dlls automatisch von dllsSearchPath aufgetriggers.

Sie müssen die Ereignisse AppDomain.AssemblyResolve oder AppDomain.ReflectionOnlyAssemblyResolve (abhängig davon, welche Auslastung Sie durchführen) verarbeiten, falls sich die referenzierte Assembly nicht im GAC oder auf dem Sondierungspfad der CLR befindet.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

Ich brauchte eine Weile, um die Antwort von @ user1996230 zu verstehen, also entschied ich mich, ein expliziteres Beispiel zu geben. Im folgenden Beispiel mache ich einen Proxy für ein Objekt, das in einer anderen AppDomain geladen ist, und rufe eine Methode für dieses Objekt aus einer anderen Domäne auf.

 class ProxyObject : MarshalByRefObject { private Type _type; private Object _object; public void InstantiateObject(string AssemblyPath, string typeName, object[] args) { assembly = Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + AssemblyPath); //LoadFrom loads dependent DLLs (assuming they are in the app domain's base directory _type = assembly.GetType(typeName); _object = Activator.CreateInstance(_type, args); ; } public void InvokeMethod(string methodName, object[] args) { var methodinfo = _type.GetMethod(methodName); methodinfo.Invoke(_object, args); } } static void Main(string[] args) { AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = @"SomePathWithDLLs"; AppDomain domain = AppDomain.CreateDomain("MyDomain", null, setup); ProxyObject proxyObject = (ProxyObject)domain.CreateInstanceFromAndUnwrap(typeof(ProxyObject).Assembly.Location,"ProxyObject"); proxyObject.InstantiateObject("SomeDLL","SomeType", new object[] { "someArgs}); proxyObject.InvokeMethod("foo",new object[] { "bar"}); } 

Der Schlüssel ist das AssemblyResolve-Ereignis, das von der AppDomain ausgetriggers wird.

 [STAThread] static void Main(string[] args) { fileDialog.ShowDialog(); string fileName = fileDialog.FileName; if (string.IsNullOrEmpty(fileName) == false) { AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; if (Directory.Exists(@"c:\Provisioning\") == false) Directory.CreateDirectory(@"c:\Provisioning\"); assemblyDirectory = Path.GetDirectoryName(fileName); Assembly loadedAssembly = Assembly.LoadFile(fileName); List assemblyTypes = loadedAssembly.GetTypes().ToList(); foreach (var type in assemblyTypes) { if (type.IsInterface == false) { StreamWriter jsonFile = File.CreateText(string.Format(@"c:\Provisioning\{0}.json", type.Name)); JavaScriptSerializer serializer = new JavaScriptSerializer(); jsonFile.WriteLine(serializer.Serialize(Activator.CreateInstance(type))); jsonFile.Close(); } } } } static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { string[] tokens = args.Name.Split(",".ToCharArray()); System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name); return Assembly.LoadFile(Path.Combine(new string[]{assemblyDirectory,tokens[0]+ ".dll"})); } 

Ich musste das mehrmals machen und habe viele verschiedene Lösungen recherchiert.

Die Lösung, die ich am elegantesten und einfachsten finde, kann als solche implementiert werden.

1. Erstellen Sie ein Projekt, mit dem Sie eine einfache Schnittstelle erstellen können

Die Schnittstelle enthält Signaturen von Mitgliedern, die Sie anrufen möchten.

 public interface IExampleProxy { string HelloWorld( string name ); } 

Es ist wichtig, dieses Projekt sauber und light zu halten. Es ist ein Projekt, auf das beide AppDomain können und das es uns ermöglicht, nicht auf die Assembly zu verweisen, die wir in einer separaten Domäne von unserer Client-Assembly laden möchten.

2. Erstellen Sie jetzt das Projekt mit dem Code, den Sie in einer AppDomain laden AppDomain .

Dieses Projekt wird wie das Client-Projekt auf das Proxy-Projekt verweisen und Sie werden die Schnittstelle implementieren.

 public interface Example : MarshalByRefObject, IExampleProxy { public string HelloWorld( string name ) { return $"Hello '{ name }'"; } } 

3. Laden Sie anschließend im Client-Projekt Code in eine andere AppDomain .

So, jetzt erstellen wir eine neue AppDomain . Kann den Basisstandort für Assemblyreferenzen angeben. Bei der Überprüfung werden abhängige Assemblys in GAC und im aktuellen Verzeichnis sowie in der AppDomain Basis- AppDomain .

 // set up domain and create AppDomainSetup domaininfo = new AppDomainSetup { ApplicationBase = System.Environment.CurrentDirectory }; Evidence adevidence = AppDomain.CurrentDomain.Evidence; AppDomain exampleDomain = AppDomain.CreateDomain("Example", adevidence, domaininfo); // assembly ant data names var assemblyName = ", Version=1.0.0.0, Culture=neutral, PublicKeyToken=null|"; var exampleTypeName = "Example"; // Optional - get a reflection only assembly type reference var @type = Assembly.ReflectionOnlyLoad( assemblyName ).GetType( exampleTypeName ); // create a instance of the `Example` and assign to proxy type variable IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( assemblyName, exampleTypeName ); // Optional - if you got a type ref IExampleProxy proxy= ( IExampleProxy )exampleDomain.CreateInstanceAndUnwrap( @type.Assembly.Name, @type.Name ); // call any members you wish var stringFromOtherAd = proxy.HelloWorld( "Tommy" ); // unload the `AppDomain` AppDomain.Unload( exampleDomain ); 

Wenn Sie müssen, gibt es eine Menge verschiedener Möglichkeiten, eine Baugruppe zu laden. Sie können mit dieser Lösung einen anderen Weg gehen. Wenn Sie den qualifizierten Namen der Assembly haben, verwende ich gerne den CreateInstanceAndUnwrap da er die Assembly-Bytes lädt und dann Ihren Typ für Sie instanziiert und ein object zurückgibt, das Sie einfach in Ihren Proxy-Typ oder wenn Sie nicht in stark typisierten Code umwandeln können Sie könnten die Laufzeitumgebung für dynamische Sprachen verwenden und das zurückgegebene Objekt einer dynamic typisierten Variablen zuweisen. Dann rufen Sie nur direkt Mitglieder auf.

Hier hast du es.

Dies ermöglicht das Laden einer Assembly, auf die Ihr Client proj nicht verweist, in einer AppDomain und das Aufrufen von AppDomain vom Client aus.

Zum Testen verwende ich gerne das Modulfenster in Visual Studio. Es zeigt Ihnen Ihre Client-Assembly-Domäne und welche Module in dieser Domäne geladen sind sowie Ihre neue App-Domäne und welche Assemblys oder Module in dieser Domäne geladen sind.

Der Schlüssel besteht darin, entweder sicherzustellen, dass Sie entweder MarshalByRefObject oder serialisierbar sind.

Mit MarshalByRefObject können Sie die Lebensdauer der Domäne konfigurieren. Beispiel: Sie möchten, dass die Domäne zerstört wird, wenn der Proxy nicht innerhalb von 20 Minuten aufgerufen wurde.

Ich hoffe das hilft.