2009-03-18 8 views
96

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)Cómo cargar un ensamblado en AppDomain con todas las referencias recursivamente?

Según tengo entendido, cuando se carga un ensamblaje en AppDomain, sus referencias no se cargan automáticamente, y tengo que cargarlas manualmente. Así que cuando lo haga:

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 me FileNotFoundException:

No se pudo cargar el archivo o ensamblado 'MyDll, versión = 1.0.0.0, Culture = neutral, PublicKeyToken = null' o uno de sus dependencias. El sistema no puede encontrar el archivo especificado.

Creo que la parte clave es una de sus dependencias.

Ok, es el siguiente paso antes de domain.Load(AssemblyName.GetAssemblyName(path));

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

Pero tiene FileNotFoundException de nuevo, en otro montaje (referencia).

¿Cómo cargar todas las referencias recursivamente?

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

+1

He cargado montajes como este varias veces, nunca he tenido que cargar manualmente todas sus referencias. No estoy seguro de que la premisa de esta pregunta sea correcta. – Mick

Respuesta

55

Es necesario invocar CreateInstanceAndUnwrap antes su obj proxy ect se ejecutará 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 probablemente obtendrá una excepción FileNotFound debido a que la resolución de la Asamblea intentará encontrar el conjunto va a cargar en la GAC ​​o 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.

+14

Consulte el código que escribí para resolver este problema: https://github.com/jduv/AppDomainToolkit. Específicamente, mire el método LoadAssemblyWithReferences en esta clase: https://github.com/jduv/AppDomainToolkit/blob/master/AppDomainToolkit/AppDomainContext.cs – Jduv

+1

Encontré que aunque esto funciona * la mayoría * de las veces, en * algunos * casos, de todos modos, es necesario adjuntar un controlador al evento 'AppDomain.CurrentDomain.AssemblyResolve' como se describe en [esta respuesta de MSDN] (http://social.msdn.microsoft.com/Forums/en-US/0a18ed66- 6995-4e7c-baab-61c1e528fb82/why-does-appdomaincreateinstanceandunwraprap-work-and-appdomaincreateinstancefununwrap). En mi caso, estaba tratando de engancharme a la implementación de SpecRun que se ejecuta bajo MSTest, pero creo que se aplica a muchas situaciones en las que su código podría no ejecutarse desde el AppDomain "principal": extensiones de VS, MSTest, etc. – Aaronaught

+0

Ah, interesante. Investigaré eso y veré si puedo hacer que sea un poco más fácil trabajar con ADT. Lo siento, el código ha estado un poco muerto por un tiempo, todos tenemos trabajos diurnos :). – Jduv

5

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

AppDomain.AssemblyResolve

AppDomain.ReflectionOnlyAssemblyResolve

+0

¿Debo indicar el montaje solicitado manualmente? ¿Incluso está en AppBase de AppDomain nuevo? ¿Hay alguna manera de no hacer eso? – abatishchev

10

En su nuevo dominio de aplicación, intente configurar un controlador AssemblyResolve evento. Se llama a ese evento cuando falta una dependencia.

+0

No es así. En realidad, obtienes una excepción en la línea en la que estás registrando este evento en el nuevo dominio de la aplicación. Debe registrar este evento en el dominio de la aplicación actual. – user1004959

+0

Lo hace si la clase se hereda de MarshalByRefObject. No lo hace si la clase está marcada solo con el atributo [Serializable]. – user2126375

14

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

C# versión:

crear una clase moderador y heredan de MarshalByRefObject:

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

llamada del sitio del cliente

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

¿Cómo se pone esta solución en el contexto de la creación de un nuevo dominio de aplicación? ¿Alguien puede explicarlo? –

+20

Resuelve el problema pero ¿CÓMO? –

+2

Un 'MarshalByRefObject' se puede pasar alrededor de appdomains. Así que supongo que 'Assembly.LoadFrom' intenta cargar el ensamblado en un nuevo dominio de aplicación, lo que solo es posible, si el objeto llamante se puede pasar entre esos appdomains. Esto también se denomina interacción remota como se describe aquí: http://msdn.microsoft.com/en-us/library/system.marshalbyrefobject%28v=vs.80%29.aspx –

3

La clave es el evento AssemblyResolve generado por el dominio de la aplicación.

[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<Type> assemblyTypes = loadedAssembly.GetTypes().ToList<Type>(); 

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

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

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

Por lo tanto, lo que usted quiere hacer con el montaje, se debe hacer en una clase de proxy - una clase que hereda MarshalByRefObject.

Tenga en cuenta que tanto el dominio llamante como el nuevo dominio creado deben tener acceso al conjunto de la clase 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 ensamblados de una carpeta que es diferente de lo que carpeta de dominio aplicación actual, crear el nuevo dominio de aplicación con la carpeta de archivos DLL específicos ruta de búsqueda.

Por ejemplo, la línea de creación de dominios aplicación desde el código anterior debe ser reemplazado con:

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

De esta manera, todas las DLL automaically se resolverán de dllsSearchPath.

+0

¿Por qué? ¿Tengo que cargar el ensamblaje utilizando una clase de proxy? ¿Cuál es la diferencia en comparación con cargarlo utilizando Assembly.LoadFrom (cadena). Estoy interesado en los detalles técnicos, desde la perspectiva de CLR. Estaría muy agradecido si pudiera proporcione una respuesta –

+0

Utiliza la clase de proxy para evitar que el nuevo ensamblado se cargue en su dominio de llamante. Si usa Assembly.LoadFrom (cadena), el dominio de llamante intentará cargar las nuevas referencias de ensamblado y no los encontrará porque no lo hace buscar ensamblajes en "[AsmPath]". (https://msdn.microsoft.com/en-us/library/yx7xezcf%28v=vs.110%29.aspx) – Nir

3

Me tomó un tiempo entender la respuesta de @ user1996230, así que decidí dar 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"}); 
} 
Cuestiones relacionadas