Uso de ensamblados uno al lado del otro para cargar la versión x64 o x32 de una DLL

Tenemos dos versiones de un ensamblaje C ++ administrado, uno para x86 y otro para x64. Este ensamblado es llamado por una aplicación .net cumplida para AnyCPU. Estamos implementando nuestro código a través de una instalación de copia de archivo, y nos gustaría continuar haciéndolo.

¿Es posible utilizar un manifiesto de ensamblaje Side-by-Side para cargar un ensamblaje x86 o x64 respectivamente cuando una aplicación está seleccionando dinámicamente su architecture de procesador? ¿O hay otra forma de hacer esto en una implementación de copia de archivo (por ejemplo, no usar el GAC)?

Creé una solución simple que puede cargar el ensamblaje específico de la plataforma desde un ejecutable comstackdo como AnyCPU. La técnica utilizada se puede resumir de la siguiente manera:

  1. Asegúrese de que el mecanismo de carga de ensamblaje .NET predeterminado (motor “Fusion”) no pueda encontrar la versión x86 o x64 del ensamblaje específico de la plataforma.
  2. Antes de que la aplicación principal intente cargar el ensamblaje específico de la plataforma, instale un progtwig de resolución de ensamblaje personalizado en el dominio de la aplicación actual.
  3. Ahora, cuando la aplicación principal necesita el ensamblaje específico de la plataforma, el motor de Fusion se rendirá (debido al paso 1) y llamará a nuestro solucionador personalizado (debido al paso 2); en la resolución personalizada determinamos la plataforma actual y usamos la búsqueda basada en directorio para cargar la DLL apropiada.

Para demostrar esta técnica, adjunto un breve tutorial basado en línea de comandos. Probé los binarios resultantes en Windows XP x86 y luego Vista SP1 x64 (copiando los binarios, al igual que su implementación).

Nota 1 : “csc.exe” es un comstackdor C-sharp. Este tutorial asume que está en su camino (mis pruebas usaban “C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe”)

Nota 2 : le recomiendo que cree una carpeta temporal para las pruebas y ejecute la línea de comandos (o powershell) cuyo directorio de trabajo actual se establece en esta ubicación, por ejemplo

(cmd.exe) C: mkdir \TEMP\CrossPlatformTest cd \TEMP\CrossPlatformTest 

Paso 1 : el ensamblaje específico de la plataforma está representado por una biblioteca de clase C # simple:

 // file 'library.cs' in C:\TEMP\CrossPlatformTest namespace Cross.Platform.Library { public static class Worker { public static void Run() { System.Console.WriteLine("Worker is running"); System.Console.WriteLine("(Enter to continue)"); System.Console.ReadLine(); } } } 

Paso 2 : comstackmos ensamblajes específicos de plataforma usando comandos simples de línea de comandos:

 (cmd.exe from Note 2) mkdir platform\x86 csc /out:platform\x86\library.dll /target:library /platform:x86 library.cs mkdir platform\amd64 csc /out:platform\amd64\library.dll /target:library /platform:x64 library.cs 

Paso 3 : El progtwig principal se divide en dos partes. “Bootstrapper” contiene el punto de entrada principal para el ejecutable y registra una resolución de ensamblado personalizada en el dominio de aplicación actual:

 // file 'bootstrapper.cs' in C:\TEMP\CrossPlatformTest namespace Cross.Platform.Program { public static class Bootstrapper { public static void Main() { System.AppDomain.CurrentDomain.AssemblyResolve += CustomResolve; App.Run(); } private static System.Reflection.Assembly CustomResolve( object sender, System.ResolveEventArgs args) { if (args.Name.StartsWith("library")) { string fileName = System.IO.Path.GetFullPath( "platform\\" + System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") + "\\library.dll"); System.Console.WriteLine(fileName); if (System.IO.File.Exists(fileName)) { return System.Reflection.Assembly.LoadFile(fileName); } } return null; } } } 

“Progtwig” es la implementación “real” de la aplicación (tenga en cuenta que App.Run fue invocado al final de Bootstrapper.Main):

 // file 'program.cs' in C:\TEMP\CrossPlatformTest namespace Cross.Platform.Program { public static class App { public static void Run() { Cross.Platform.Library.Worker.Run(); } } } 

Paso 4 : comstack la aplicación principal en la línea de comando:

 (cmd.exe from Note 2) csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs 

Paso 5 : ahora hemos terminado. La estructura del directorio que creamos debe ser la siguiente:

 (C:\TEMP\CrossPlatformTest, root dir) platform (dir) amd64 (dir) library.dll x86 (dir) library.dll program.exe *.cs (source files) 

Si ahora ejecuta program.exe en una plataforma de 32 bits, se cargará platform \ x86 \ library.dll; si ejecuta program.exe en una plataforma de 64 bits, se cargará la plataforma \ amd64 \ library.dll. Tenga en cuenta que agregué Console.ReadLine () al final del método Worker.Run para que pueda usar el administrador de tareas / explorador de procesos para investigar archivos DLL cargados, o puede usar Visual Studio / Windows Debugger para adjuntarlo al proceso y ver el stack de llamadas, etc.

Cuando se ejecuta program.exe, nuestro resolvedor de ensamblado personalizado se adjunta al dominio de aplicación actual. Tan pronto como .NET comienza a cargar la clase Program, ve una dependencia en el ensamblado ‘library’, por lo que intenta cargarlo. Sin embargo, no se encuentra dicho ensamblaje (porque lo hemos ocultado en la plataforma / * subdirectorios). Afortunadamente, nuestro solucionador personalizado conoce nuestro truco y, en base a la plataforma actual, intenta cargar el ensamblado desde el subdirectorio platform / * apropiado.

Mi versión, similar a @Milan, pero con varios cambios importantes:

  • Funciona para TODOS los archivos DLL que no se encontraron
  • Se puede encender y apagar
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase se utiliza en lugar de Path.GetFullPath() porque el directorio actual puede ser diferente, por ejemplo, en escenarios de alojamiento, Excel puede cargar su complemento pero el directorio actual no se establecerá en su archivo DLL.

  • Environment.Is64BitProcess se utiliza en lugar de PROCESSOR_ARCHITECTURE , ya que no debemos depender de qué es el sistema operativo, sino de cómo se inició este proceso: podría haber sido un proceso x86 en un sistema operativo x64. Antes de .NET 4, use IntPtr.Size == 8 lugar.

Llame a este código en un constructor estático de alguna clase principal que se carga antes que todo.

 public static class MultiplatformDllLoader { private static bool _isEnabled; public static bool Enable { get { return _isEnabled; } set { lock (typeof (MultiplatformDllLoader)) { if (_isEnabled != value) { if (value) AppDomain.CurrentDomain.AssemblyResolve += Resolver; else AppDomain.CurrentDomain.AssemblyResolve -= Resolver; _isEnabled = value; } } } } /// Will attempt to load missing assembly from either x86 or x64 subdir private static Assembly Resolver(object sender, ResolveEventArgs args) { string assemblyName = args.Name.Split(new[] {','}, 2)[0] + ".dll"; string archSpecificPath = Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, Environment.Is64BitProcess ? "x64" : "x86", assemblyName); return File.Exists(archSpecificPath) ? Assembly.LoadFile(archSpecificPath) : null; } } 

Eche un vistazo a SetDllDirectory. Lo usé para cargar dinámicamente un ensamblaje spss de IBM para x64 y x86. También resolvió rutas para soporte no ensamblado dll’s cargadas por ensamblajes en mi caso era el caso con spss dll’s.

http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx

Se puede utilizar el corflags utilidad para forzar un exe Cualquier CPU para cargar como un ejecutable x86 o x64, pero que no cumple totalmente con el requisito de despliegue de copia de archivos a menos que elija la que exe para copia basada en el objective.

Esta solución también puede funcionar para ensamblajes no gestionados. He creado un ejemplo simple similar al gran ejemplo de Milan Gardian. El ejemplo que creé dinámicamente carga un dll Managed C ++ en un dll C # comstackdo para la plataforma Any CPU. La solución hace uso del paquete Nuget InjectModuleInitializer para suscribirse al evento AssemblyResolve antes se cargan las dependencias del assembly.

https://github.com/kevin-marshall/Managed.AnyCPU.git