Pregunta

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

¿Es posible utilizar un manifiesto de ensamblaje lado a lado para cargar un ensamblaje x86 o x64 respectivamente cuando una aplicación selecciona dinámicamente su arquitectura de procesador?¿O hay otra manera de hacer esto en una implementación de copia de archivos (p. ej.¿No estás usando el GAC)?

¿Fue útil?

Solución

Creé una solución simple que puede cargar un ensamblado específico de una 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 de ensamblajes .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 ensamblado específico de la plataforma, instale un solucionador de ensamblado personalizado en el dominio de aplicación actual.
  3. Ahora, cuando la aplicación principal necesita el ensamblaje específico de la plataforma, el motor Fusion se dará por vencido (debido al paso 1) y llamará a nuestro solucionador personalizado (debido al paso 2);en el solucionador personalizado determinamos la plataforma actual y utilizamos 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 archivos binarios resultantes en Windows XP x86 y luego en Vista SP1 x64 (copiando los archivos binarios, al igual que en su implementación).

Nota 1:"csc.exe" es un compilador C-Sharp.Este tutorial asume que está en su ruta (mis pruebas usaron "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 comando (o powershell) cuyo directorio de trabajo actual esté configurado 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 biblioteca de clases 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:Compilamos ensamblajes 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 ensamblaje personalizado 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;
        }
    }
}

"Programa" es la implementación "real" de la aplicación (tenga en cuenta que App.Run se invocó 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();
        }
    }
}

Etapa 4:Compile 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:Ya hemos terminado.La estructura del directorio que creamos debería 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á platform\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 las DLL cargadas, o puede usar Visual Studio/Windows Debugger para adjuntarlo al proceso y ver el pila de llamadas, etc.

Cuando se ejecuta program.exe, nuestro solucionador de ensamblaje personalizado se adjunta al dominio de aplicación actual.Tan pronto como .NET comienza a cargar la clase Programa, ve una dependencia del ensamblado de la 'biblioteca', por lo que intenta cargarla.Sin embargo, no se encuentra dicho ensamblado (porque lo hemos ocultado en los subdirectorios plataforma/*).Afortunadamente, nuestro solucionador personalizado conoce nuestro truco y, según la plataforma actual, intenta cargar el ensamblaje desde el subdirectorio plataforma/* apropiado.

Otros consejos

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

  • Funciona para TODAS las 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, p.e.En escenarios de alojamiento, Excel puede cargar su complemento, pero el directorio actual no se configurará en su DLL.

  • Environment.Is64BitProcess se utiliza en lugar de PROCESSOR_ARCHITECTURE, ya que no deberíamos depender de cuál sea 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 en cambio.

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

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 la carga dinámica de un ensamblaje IBM spss tanto para x64 como para x86.También resolvió rutas para archivos DLL que no son compatibles con ensamblajes cargados por los ensamblajes; en mi caso, fue el caso de los archivos DLL de spss.

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

Puedes usar el banderas corflas utilidad para forzar que un exe de AnyCPU se cargue como un 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 según el destino.

Esta solución también puede funcionar para ensamblajes no administrados.He creado un ejemplo sencillo 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 la plataforma Any CPU.La solución utiliza el 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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top