2009-09-28 16 views
7

En tiempo de ejecución, me gustaría poder descargar una DLL y volver a cargar una versión modificada de la misma. Mi primer experimento se incendió. puede alguien decirme por que?El dominio de mi aplicación no se descargará

private static void Main() 
{ 
    const string fullPath = "C:\\Projects\\AppDomains\\distrib\\MyLibrary.dll"; 

    // Starting out with a version of MyLibrary.dll which only has 1 method, named Foo() 
    AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath); 
    AppDomain appDomain = AppDomain.CreateDomain("MyTemp"); 
    appDomain.Load(assemblyName); 
    appDomain.DomainUnload += appDomain_DomainUnload; 
    AppDomain.Unload(appDomain); 

    // Breakpoint here; swap out different version of MyLibrary.dll which only has 1 method, named Goo() 
    AssemblyName assemblyName2 = AssemblyName.GetAssemblyName(fullPath); 
    AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2"); 
    Assembly asm2 = appDomain2.Load(assemblyName2); 

    foreach (Type type in asm2.GetExportedTypes()) 
    { 
     foreach (MemberInfo memberInfo in type.GetMembers()) 
     { 
     string name = memberInfo.Name; 
     // Breakpoint here: Found Foo and but no Goo! I was expecting Goo and no Foo. 
     } 
    } 
} 

private static void appDomain_DomainUnload(object sender, EventArgs e) 
{ 
    // This gets called before the first breakpoint 
} 

Editar:

Vale, esto es, obviamente, mi primera publicación tiempo. Gracias a Daniel por formatear mi código (ahora veo el botón de la barra de herramientas para hacer eso, ¡y también el panel de vista previa!). No veo una manera de publicar un "comentario" en respuesta a los que pidieron una aclaración a la publicación original oa la respuesta 1, así que solo publicaré otra "respuesta" para continuar la conversación. (Se agradecerán sugerencias sobre cómo se hacen los comentarios).

Mitch - Cayó en llamas porque mi ciclo foreach debería haber estado iterando sobre los tipos en la DLL modificada, no en la previamente cargada/descargada. Matthew - Eso podría funcionar, pero realmente necesito el caso del mismo nombre de archivo para trabajar. Mike Two - No tiene un nombre muy fuerte.

Comentarios a la respuesta: Azul & Mike Two - Falsificaré en sus sugerencias, pero primero necesito entender un aspecto crítico. Leí que debe tener cuidado de no arrastrar el ensamblaje al dominio principal de la aplicación, y el código tenía previamente una copia del ciclo foreach antes de la descarga. Entonces, al sospechar que el acceso a MethodInfos estaba absorbiendo el ensamblado en el dominio principal de la aplicación, eliminé el ciclo. Fue entonces cuando supe que necesitaba pedir ayuda, ¡porque la primera DLL todavía no se descargaría!

Así que mi pregunta es: ¿Qué en el siguiente segmento de código hace que el dominio principal aplicación para siempre directamente (o indirectamente) el acceso nada en la DLL ... ¿por qué sería hacer que el conjunto para cargar también en la aplicación principal dominio:

appDomain.Load(assemblyName); 
appDomain.DomainUnload += appDomain_DomainUnload; 
AppDomain.Unload(appDomain); 

no me dio mucha confianza que jamás había realmente ser capaz de utilizar la DLL antes de descargarlo.


Edición 2:

Mike Dos - Gracias por su persistencia ... cargar el ensamblado desde dentro DoCallBack era el ingrediente secreto. Para cualquier persona interesada, aquí hay más información que podría ser útil en el camino:

Parece que nadie podría reproducir mis condiciones exactamente. Para demostrar el problema original, generé mi dlls de esta manera: 1. Agregué el proyecto de la biblioteca de clase a la solución 2. Construí la versión 1.0.0 con Foo; el ensamblado resultante renombrado como MyLibrary.dll.f. 3. Renombrado Foo to Goo y construido otra versión 1.0.0; el ensamblaje resultante renombrado como MyLibrary.dll.g. 4. Se eliminó el proyecto de la solución. Antes de comenzar la ejecución, eliminé el .f y corrí al punto de interrupción (primera línea después de la descarga). Luego volví a conectar el .f y quité el .g del otro dll y corrí al siguiente punto de interrupción. Windows no me impidió cambiar el nombre. Nota: Aunque sería una mejor práctica, no cambié el número de versión porque no quería suponer que mis clientes siempre lo harían, ya que la entrada predeterminada en AssemblyInfo no es la versión de comodín. Parecía el caso más feo de manejar.

Además, acabo ahora descubrí algo que me habría puesta al tanto en cuanto antes:

AssemblyName assemblyName = AssemblyName.GetAssemblyName(FullPath); 
AppDomain appDomain = AppDomain.CreateDomain("MyTemp"); 
appDomain.Load(assemblyName); 
Assembly[] tempAssemblies = appDomain.GetAssemblies(); 
// MyLibrary.dll has been loaded into the temp domain...good 
Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); 
// MyLibrary.dll has been loaded into the main domain, too...bad! 

Así que no estoy seguro de lo que el punto de AppDomain.Load es, pero parece tener el efecto secundario de bonos de cargar el ensamblaje en el dominio principal de la aplicación también. El uso de este experimento, pude ver la solución de Mike dos limpiamente lo carga solamente en el dominio temporal:

AppDomain appDomain = AppDomain.CreateDomain("MyTemp"); 
appDomain.DoCallBack(CallBackDelegate); // Executes Assembly.LoadFrom 
Assembly[] tempAssemblies = appDomain.GetAssemblies(); 
// MyLibrary.dll has been loaded into the temp domain...good 
Assembly[] mainAssemblies = AppDomain.CurrentDomain.GetAssemblies(); 
// MyLibrary.dll has NOT been loaded into the main domain...great! 

Así, Mike Dos, exactamente, ¿cómo marcar este novato Stackoverflow su respuesta como "aceptada"? ¿O no puedo hacer eso ya que solo soy un invitado aquí?

Ahora me voy a aprender cómo realmente usar MyLibrary sin aspirar el ensamblado al dominio principal de la aplicación. Gracias a todos por participar.


Datos 3:

BlueMonkMN - Me siguió adelante y se llevó a cabo la suscripción de eventos y obtener el mismo resultado. El programa completo está ahora en la lista a continuación:

const string fullPath = "C:\\Projects\\AppDomains\\distrib\\MyLibrary.dll"; 

// Starting out with a version of MyLibrary.dll which only has 1 method, named Foo() 
AssemblyName assemblyName = AssemblyName.GetAssemblyName(fullPath); 
AppDomain appDomain = AppDomain.CreateDomain("MyTemp"); 
appDomain.Load(assemblyName); 
AppDomain.Unload(appDomain); 

// Breakpoint here; swap out different version of MyLibrary.dll which only has 1 method, named Goo() 
AssemblyName assemblyName2 = AssemblyName.GetAssemblyName(fullPath); 
AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2"); 
Assembly asm2 = appDomain2.Load(assemblyName2); 

foreach (Type type in asm2.GetExportedTypes()) 
{ 
    foreach (MemberInfo memberInfo in type.GetMembers()) 
    { 
     string name = memberInfo.Name; 
     // Breakpoint here: Found Foo and but no Goo! I was expecting Goo and no Foo. 
    } 
} 

Parece que debe haber ninguna manera el conjunto está siendo tirado en el dominio de aplicación principal entre estas 2 líneas:

appDomain.Load(assemblyName); 
AppDomain.Unload(appDomain); 
+0

¿podría explicar "se cayó en llamas"? –

+0

Verificaría que realmente está mirando el segundo conjunto. pruébelos como dos nombres de archivo diferentes uno al lado del otro en lugar de hacerlos del mismo nombre. –

+0

¿Los ensambles tienen un nombre fuerte? El GAC tiene un caché de descarga de ensamblaje que se aferra a ensamblajes con nombres fuertes. Puede verificar si está allí ejecutando gacutil/ldl y borrarlo ejecutando gacutil/cdl. –

Respuesta

5

Intenté replicar esto. En el dll para cargar (MyLibrary.dll) construí dos versiones. El primero tenía una clase con un método llamado Foo y tenía un número de versión de 1.0.0.0. El segundo tenía la misma clase pero el método había sido renombrado como Bar (soy un tradicionalista) y un número de versión de 2.0.0.0.

Puse un punto de interrupción después de la llamada de descarga. Luego traté de copiar la segunda versión sobre la primera versión. Supongo que eso es lo que estás haciendo porque la ruta nunca cambia. Windows no me permitió copiar la versión 2 sobre la versión 1. La dll estaba bloqueada.

Cambié el código para cargar el dll usando el código ejecutado dentro del Dominio de la aplicación usando DoCallback. Eso funciono. Podría cambiar el dll y encontrar el nuevo método. Aquí está el código.

class Program 
{ 
    static void Main(string[] args) 
    { 
     AppDomain appDomain = AppDomain.CreateDomain("MyTemp"); 
     appDomain.DoCallBack(loadAssembly); 
     appDomain.DomainUnload += appDomain_DomainUnload; 

     AppDomain.Unload(appDomain); 

     AppDomain appDomain2 = AppDomain.CreateDomain("MyTemp2"); 
     appDomain2.DoCallBack(loadAssembly); 
    } 

    private static void loadAssembly() 
    { 
     string fullPath = "LoadMe1.dll"; 
     var assembly = Assembly.LoadFrom(fullPath); 
     foreach (Type type in assembly.GetExportedTypes()) 
     { 
      foreach (MemberInfo memberInfo in type.GetMembers()) 
      { 
       string name = memberInfo.Name; 
       Console.Out.WriteLine("name = {0}", name); 
      } 
     } 
    } 

    private static void appDomain_DomainUnload(object sender, EventArgs e) 
    { 
     Console.Out.WriteLine("unloaded"); 
    } 
} 

No llamé fuerte a los ensamblajes. Si lo hace, es probable que encuentre el primero en la memoria caché. Se puede decir ejecutando gacutil/ldl (Caché de descarga de lista) desde la línea de comando. Si lo encuentra almacenado en caché, ejecute gacutil/cdl para borrar la caché de descarga.

1

He encontrado que si Siempre acceda directamente a los tipos en un ensamblaje que se carga en su propio dominio. Entonces, lo que he tenido que hacer es crear un tercer ensamble que implemente interfaces comunes a ambos ensambles. Ese ensamblaje se carga en ambos dominios. Luego, solo tenga cuidado de usar interfaces de ese tercer ensamblado cuando interactúa con el ensamblaje externo. Eso debería permitirle descargar el segundo ensamblaje descargando su dominio.

+3

Otra forma de evitar tirar los tipos al dominio principal es usar AppDomain.DoCallBack . Esto le permite ejecutar código en el dominio cargado. En el ejemplo de OP, foreach ... podría pasarse al AppDomain cargado. –

Cuestiones relacionadas