Uso de ensamblajes lado a lado para cargar la versión x64 o x32 de una DLL


Tenemos dos versiones de un ensamblado C++ administrado, una para x86 y otra 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 usar un manifiesto de ensamblado Lado a lado para cargar un ensamblado x86 o x64 respectivamente cuando una aplicación está seleccionando dinámicamente su arquitectura de procesador? ¿O hay otra forma de hacer esto en una implementación de copia de archivos (por ejemplo, no usar el GAC)?

Author: Adam L, 2008-09-20

5 answers

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

  1. Asegúrese de que el mecanismo de carga predeterminado del ensamblaje. NET (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 ensamblado específico de la plataforma, instale un solucionador de ensamblado personalizado en el AppDomain actual
  3. Ahora cuando la aplicación principal necesita el ensamblaje específico de la plataforma, Fusion engine se rendirá (debido al paso 1) y llamará a nuestro solucionador personalizado (debido al paso 2); en el solucionador personalizado determinamos la plataforma actual y usamos la búsqueda basada en directorios para cargar la DLL adecuada.

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

Nota 1 : "csc.exe " es un compilador C-sharp. Este tutorial asume que está en su camino (mis pruebas estaban usando "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 esté establecido en esta ubicación, por ejemplo,

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

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

// 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 : Compilamos ensamblados específicos de la plataforma utilizando 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 programa principal se divide en dos partes. "Bootstrapper" contiene el punto de entrada principal para el ejecutable y registra un solucionador de ensamblado personalizado en appdomain 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;
        }
    }
}

"Programa" es la implementación "real" de la aplicación (tenga en cuenta que la aplicación.Run fue invocado al final de Bootstrapper.Principal):

// 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: Compilar la aplicación principal en la línea de comandos:

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

Paso 5: Hemos terminado. La estructura del directorio que hemos creado 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 programa.exe en una plataforma de 32 bits, platform \ x86 \ library.dll se cargará; si ejecuta el programa.exe en una plataforma de 64 bits, platform \ amd64 \ library.dll se cargará. Tenga en cuenta que he añadido Consola.ReadLine() al final del Worker.Ejecutar método para que pueda utilizar el administrador de tareas / explorador de procesos para investigue las DLL cargadas, o puede usar Visual Studio/Depurador de Windows para adjuntarlas al proceso para ver la pila de llamadas, etc.

Cuando programa.se ejecuta exe, nuestro solucionador de ensamblaje personalizado se adjunta al appdomain 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 tal ensamblaje (porque lo hemos escondido en los subdirectorios platform/*). Afortunadamente, nuestro solucionador personalizado conoce nuestro truco y se basa en plataforma actual intenta cargar el ensamblado desde el subdirectorio platform/* apropiado.

 62
Author: Milan Gardian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-01 02:42:21

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

  • Funciona para TODAS las DLL que no se encontraron
  • Se puede activar y desactivar
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase se utiliza en lugar de Path.GetFullPath() porque el directorio actual podría ser diferente, por ejemplo, en escenarios de alojamiento, Excel podría cargar su plugin, pero el directorio actual no se establecerá en su DLL.

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

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

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;
    }
}
 23
Author: Yurik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-04-01 16:26:42

Echa un vistazo a SetDllDirectory. Lo utilicé alrededor de la carga dinámica de un ensamblaje IBM spss para x64 y x86. También solucionó rutas para dll no compatibles con ensamblajes cargados por los ensamblajes en mi caso fue el caso con las dll de spss.

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

 3
Author: wvd_vegt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-28 12:01:43

Puede usar la utilidad corflags para forzar que un exe AnyCPU se cargue como ejecutable x86 o x64, pero eso no cumple totalmente con el requisito de implementación de copia de archivos a menos que elija qué exe copiar en función del destino.

 2
Author: Rob Walker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-20 18:56:17

Esta solución también puede funcionar para ensamblados no administrados. He creado un ejemplo simple similar al gran ejemplo de Milan Gardian. El ejemplo que creé carga dinámicamente una dll de C++ Administrada en una dll de C# compilada para cualquier plataforma de CPU. La solución hace uso del paquete nuget InjectModuleInitializer para suscribirse al evento AssemblyResolve antes de que se carguen las dependencias del ensamblado.

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

 1
Author: Kevin Marshall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-21 19:14:12