Comment charger un assembly dans AppDomain avec toutes les références récursivement?

Je veux charger dans un nouvel AppDomain un assembly contenant une arborescence de références complexes (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll )

Si j’ai bien compris, lorsqu’un assembly est en cours de chargement dans AppDomain , ses références ne sont pas chargées automatiquement et je dois les charger manuellement. Alors quand je fais:

 ssortingng dir = @"SomePath"; // different from AppDomain.CurrentDomain.BaseDirectory ssortingng 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)); 

et a obtenu FileNotFoundException :

Impossible de charger le fichier ou l’assembly ‘MyDll, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null’ ou l’une de ses dépendances. Le système ne peut pas trouver le fichier spécifié.

Je pense que la partie clé est l’ une de ses dépendances .

Ok, je fais avant avant domain.Load(AssemblyName.GetAssemblyName(path));

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

Mais FileNotFoundException nouveau FileNotFoundException , sur un autre assembly (référencé).

Comment charger toutes les références de manière récursive?

Dois-je créer un arbre de références avant de charger l’assemblage racine? Comment obtenir les références d’un assemblage sans le charger?

Vous devez appeler CreateInstanceAndUnwrap avant que votre object proxy ne s’exécute dans le domaine de l’application étrangère.

  class Program { static void Main(ssortingng[] 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(ssortingng assemblyPath) { try { return Assembly.LoadFile(assemblyPath); } catch (Exception) { return null; // throw new InvalidOperationException(ex); } } } 

Notez également que si vous utilisez LoadFrom vous obtiendrez probablement une exception FileNotFound car le résolveur Assembly tentera de trouver l’assembly que vous chargez dans le GAC ou dans le dossier bin de l’application actuelle. Utilisez LoadFile pour charger un fichier d’assemblage arbitraire à la place, mais notez que si vous faites cela, vous devrez charger les dépendances vous-même.

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

Version C #:

Créez une classe de modérateur et MarshalByRefObject de MarshalByRefObject :

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

appel du site client

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

Sur votre nouvel AppDomain, essayez de définir un gestionnaire d’événements AssemblyResolve . Cet événement est appelé lorsqu’une dépendance est manquante.

Une fois que vous avez passé l’instance d’assembly au domaine de l’appelant, le domaine de l’appelant essaiera de le charger! C’est pourquoi vous obtenez l’exception. Cela se produit dans votre dernière ligne de code:

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

Ainsi, tout ce que vous voulez faire avec l’assemblage doit être fait dans une classe proxy – une classe qui hérite de MarshalByRefObject .

Prenez en compte que le domaine de l’appelant et le nouveau domaine créé doivent tous deux avoir access à l’assembly de classe proxy. Si votre problème n’est pas trop compliqué, envisagez de laisser le dossier ApplicationBase inchangé, il sera donc identique au dossier du domaine appelant (le nouveau domaine ne charge que les assemblages dont il a besoin).

En code simple:

 public void DoStuffInOtherDomain() { const ssortingng 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(ssortingng 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); } } } 

Si vous avez besoin de charger les assemblages à partir d’un dossier différent de celui du dossier du domaine d’application actuel, créez le nouveau domaine d’application avec un dossier spécifique de chemin de recherche dlls.

Par exemple, la ligne de création du domaine d’application du code ci-dessus doit être remplacée par:

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

De cette façon, toutes les DLL seront automatiquement résolues à partir de dllsSearchPath.

Vous devez gérer les événements AppDomain.AssemblyResolve ou AppDomain.ReflectionOnlyAssemblyResolve (en fonction de la charge que vous effectuez) au cas où l’assembly référencé ne se trouve pas dans le GAC ou sur le chemin de sondage du CLR.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

Il m’a fallu du temps pour comprendre la réponse de @ user1996230, alors j’ai décidé de donner un exemple plus explicite. Dans l’exemple ci-dessous, je crée un proxy pour un object chargé dans un autre AppDomain et appelle une méthode sur cet object à partir d’un autre domaine.

 class ProxyObject : MarshalByRefObject { private Type _type; private Object _object; public void InstantiateObject(ssortingng AssemblyPath, ssortingng 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(ssortingng methodName, object[] args) { var methodinfo = _type.GetMethod(methodName); methodinfo.Invoke(_object, args); } } static void Main(ssortingng[] 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"}); } 

La clé est l’événement AssemblyResolve déclenché par AppDomain.

 [STAThread] static void Main(ssortingng[] args) { fileDialog.ShowDialog(); ssortingng fileName = fileDialog.FileName; if (ssortingng.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(ssortingng.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) { ssortingng[] tokens = args.Name.Split(",".ToCharArray()); System.Diagnostics.Debug.WriteLine("Resolving : " + args.Name); return Assembly.LoadFile(Path.Combine(new ssortingng[]{assemblyDirectory,tokens[0]+ ".dll"})); } 

J’ai dû le faire plusieurs fois et j’ai recherché de nombreuses solutions différentes.

La solution que je trouve la plus élégante et la plus facile à réaliser peut être mise en œuvre en tant que telle.

1. Créez un projet que vous pouvez créer une interface simple

l’interface contiendra les signatures de tous les membres que vous souhaitez appeler.

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

Il est important de garder ce projet propre et léger. C’est un projet auquel AppDomain peut se référer et qui nous permettra de ne pas référencer l’ Assembly nous souhaitons charger dans le domaine seprate à partir de notre assembly client.

2. Maintenant, créez le projet qui a le code que vous voulez charger dans AppDomain AppDomain .

Ce projet comme avec le client proj référencera le proxy proj et vous implémenterez l’interface.

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

3. Ensuite, dans le projet client, chargez le code dans un autre AppDomain .

Donc, maintenant, nous créons un nouvel AppDomain . Peut spécifier l’emplacement de base pour les références d’assemblage. Le test vérifiera les assemblys dépendants dans GAC et dans le répertoire en cours et la firebase database 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 ssortingngFromOtherAd = proxy.HelloWorld( "Tommy" ); // unload the `AppDomain` AppDomain.Unload( exampleDomain ); 

si vous en avez besoin, il existe une multitude de façons de charger un assemblage. Vous pouvez utiliser une solution différente avec cette solution. Si vous avez le nom qualifié d’assembly, alors j’aime utiliser CreateInstanceAndUnwrap car il charge les octets d’assemblage, puis instancie votre type pour vous et retourne un object que vous pouvez simplement transtyper à votre type de proxy ou si vous ne le faites pas dans un code fortement typé vous pouvez utiliser le runtime de langage dynamic et affecter l’object renvoyé à une variable typée dynamic , puis appeler directement les membres sur cette variable.

Voilà.

Cela permet de charger un assembly auquel votre client proj n’a pas de référence dans un AppDomain AppDomain et d’appeler les membres depuis le client.

Pour tester, j’aime utiliser la fenêtre Modules dans Visual Studio. Il vous montrera votre domaine d’assemblage client et tous les modules chargés dans ce domaine, ainsi que votre nouveau domaine d’application et les assemblys ou modules chargés dans ce domaine.

La clé est soit de vous assurer que le code dérive MarshalByRefObject ou est sérialisable.

`MarshalByRefObject vous permettra de configurer la durée de vie du domaine. Par exemple, supposons que le domaine soit détruit si le proxy n’a pas été appelé dans 20 minutes.

J’espère que ça aide.