Cómo cargar un ensamblado a AppDomain con todas las referencias recursivamente?

Quiero cargar a un nuevo AppDomain algún ensamblado que tenga un árbol de referencias complejas (MyDll.dll -> Microsoft.Office.Interop.Excel.dll -> Microsoft.Vbe.Interop.dll -> Office.dll -> stdole.dll )

Por lo que yo entiendo, cuando un ensamblaje se carga en AppDomain , sus referencias no se cargan automáticamente, y tengo que cargarlas manualmente. Entonces cuando lo hago:

 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)); 

y obtuve FileNotFoundException :

No se pudo cargar el archivo o ensamblado ‘MyDll, Version = 1.0.0.0, Culture = neutral, PublicKeyToken = null’ o una de sus dependencias. El sistema no puede encontrar el archivo especificado.

Creo que la parte clave es una de sus dependencias .

Ok, lo hago después antes de domain.Load(AssemblyName.GetAssemblyName(path));

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

Pero obtuve FileNotFoundException nuevamente, en otro ensamblado (referenciado).

¿Cómo cargar todas las referencias recursivamente?

¿Tengo que crear un árbol de referencias antes de cargar el ensamblaje raíz? ¿Cómo obtener las referencias de un ensamblaje sin cargarlo?

Necesita invocar CreateInstanceAndUnwrap antes de que su objeto proxy se ejecute en el dominio de la aplicación extranjera.

  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); } } } 

Además, tenga en cuenta que si utiliza LoadFrom es probable que obtenga una excepción FileNotFound porque la resolución de ensamblado intentará encontrar el ensamblaje que está cargando en el GAC o en la carpeta bin de la aplicación actual. Use LoadFile para cargar un archivo de ensamblaje arbitrario en su lugar, pero tenga en cuenta que si hace esto, tendrá que cargar las dependencias usted mismo.

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

Versión C #:

Crea una clase de moderador y heredad de MarshalByRefObject :

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

llamada desde el sitio del cliente

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

En su nuevo AppDomain, intente configurar un controlador de eventos AssemblyResolve . Se llama a ese evento cuando falta una dependencia.

Una vez que pase la instancia de ensamblaje al dominio llamante, ¡el dominio llamante intentará cargarlo! Es por eso que obtienes la excepción. Esto sucede en tu última línea de código:

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

Por lo tanto, lo que sea que desee hacer con el ensamblado, debe hacerse en una clase proxy, una clase que hereda MarshalByRefObject .

Tenga en cuenta que el dominio de llamante y el nuevo dominio creado deben tener acceso al conjunto de clase de proxy. Si su problema no es demasiado complicado, considere dejar la carpeta ApplicationBase sin cambios, por lo que será igual a la carpeta del dominio llamante (el nuevo dominio solo cargará los ensambles que necesite).

En código simple:

 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); } } } 

Si necesita cargar los ensamblajes desde una carpeta que es diferente a la carpeta actual del dominio de la aplicación, cree el nuevo dominio de la aplicación con la carpeta de búsqueda de dlls específica.

Por ejemplo, la línea de creación del dominio de la aplicación del código anterior debe reemplazarse por:

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

De esta forma, todos los dlls se resolverán automáticamente desde dllsSearchPath.

Debe manejar los eventos AppDomain.AssemblyResolve o AppDomain.ReflectionOnlyAssemblyResolve (dependiendo de la carga que esté realizando) en caso de que el ensamblado al que se hace referencia no se encuentre en el GAC o en la ruta de exploración del CLR.

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

Me tomó un tiempo entender la respuesta de @ user1996230, así que decidí brindar un ejemplo más explícito. En el ejemplo siguiente, hago un proxy para un objeto cargado en otro AppDomain y llamo a un método en ese objeto desde otro dominio.

 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"}); } 

La clave es el evento AssemblyResolve generado por el AppDomain.

 [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"})); } 

He tenido que hacer esto varias veces y he investigado muchas soluciones diferentes.

La solución que encuentro más elegante y fácil de lograr se puede implementar como tal.

1. Crea un proyecto que puedes crear una interfaz simple

la interfaz contendrá firmas de los miembros a los que desee llamar.

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

Es importante mantener este proyecto limpio y ligero. Es un proyecto al que ambos AppDomain pueden hacer referencia y nos permitirá no hacer referencia al Assembly que deseamos cargar en el dominio seprate de nuestro ensamblado de cliente.

2. Ahora crea el proyecto que tiene el código que deseas cargar en el AppDomain separado.

Este proyecto, al igual que con el proyecto de cliente, hará referencia al proy proxy y usted implementará la interfaz.

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

3. Luego, en el proyecto del cliente, cargue el código en otro AppDomain .

Entonces, ahora creamos un nuevo AppDomain . Puede especificar la ubicación base para las referencias de ensamblaje. La prueba comprobará si hay conjuntos dependientes en GAC y en el directorio actual y el AppDomain base de 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 ); 

si lo necesita, hay muchas formas diferentes de cargar un ensamblaje. Puedes usar una forma diferente con esta solución. Si tiene el nombre calificado de ensamblaje, me gusta utilizar CreateInstanceAndUnwrap ya que carga los bytes del ensamblado y luego ejemplifica su tipo para usted y devuelve un object que puede convertir a su tipo de proxy o si no tiene un código fuertemente tipado puede usar el tiempo de ejecución de lenguaje dynamic y asignar el objeto devuelto a una variable de tipo dynamic luego llamar directamente a los miembros.

Ahí tienes.

Esto permite cargar un ensamblado al que el proyecto de su cliente no tiene referencia en un AppDomain independiente y llamar a los miembros desde el cliente.

Para probar, me gusta usar la ventana Módulos en Visual Studio. Le mostrará el dominio de ensamblado de cliente y los módulos que se cargan en ese dominio, así como su nuevo dominio de aplicación y qué ensamblados o módulos se cargan en ese dominio.

La clave es asegurarse de que el código o bien deriva MarshalByRefObject o es serializable.

`MarshalByRefObject te permitirá configurar el tiempo de vida del dominio. Ejemplo, digamos que quieres que el dominio se destruya si no se ha llamado al proxy en 20 minutos.

Espero que esto ayude.