Utilizzo degli assembly side-by-side per caricare la versione x64 o x32 di una DLL

StackOverflow https://stackoverflow.com/questions/108971

  •  01-07-2019
  •  | 
  •  

Domanda

Abbiamo due versioni di un assembly C ++ gestito, una per x86 e una per x64. Questo assembly viene chiamato da un'applicazione .net rispettata per AnyCPU. Stiamo distribuendo il nostro codice tramite l'installazione di una copia di file e vorremmo continuare a farlo.

È possibile utilizzare un manifest di assieme Side-by-Side per caricare un assieme x86 o x64 rispettivamente quando un'applicazione seleziona in modo dinamico la sua architettura di processore? Oppure c'è un altro modo per farlo in una distribuzione di copie di file (ad esempio non usando il GAC)?

È stato utile?

Soluzione

Ho creato una soluzione semplice in grado di caricare assembly specifici della piattaforma da un eseguibile compilato come AnyCPU. La tecnica utilizzata può essere sintetizzata come segue:

  1. Assicurati che il meccanismo di caricamento degli assembly .NET predefinito ("motore Fusion") non riesca a trovare la versione x86 o x64 dell'assembly specifico della piattaforma
  2. Prima che l'applicazione principale tenti di caricare l'assembly specifico della piattaforma, installa un risolutore di assiemi personalizzato nell'AppDomain corrente
  3. Ora, quando l'applicazione principale necessita dell'assemblaggio specifico della piattaforma, il motore Fusion si arrenderà (a causa del passaggio 1) e chiamerà il nostro risolutore personalizzato (a causa del passaggio 2); nel risolutore personalizzato determiniamo la piattaforma corrente e utilizziamo la ricerca basata su directory per caricare la DLL appropriata.

Per dimostrare questa tecnica, allego un breve tutorial basato sulla riga di comando. Ho testato i binari risultanti su Windows XP x86 e poi Vista SP1 x64 (copiando i binari, proprio come la tua distribuzione).

Nota 1 : " csc.exe " è un compilatore C-sharp. Questo tutorial presuppone che sia nel tuo percorso (i miei test stavano usando " C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe ")

Nota 2 : ti consiglio di creare una cartella temporanea per i test ed eseguire la riga di comando (o powershell) la cui directory di lavoro corrente è impostata in questa posizione, ad es.

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

Passaggio 1 : l'assembly specifico della piattaforma è rappresentato da una semplice libreria di classe C #:

// 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();
        }
    }
}

Passaggio 2 : compiliamo assiemi specifici della piattaforma utilizzando semplici comandi da riga di comando:

(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

Passaggio 3 : il programma principale è diviso in due parti. & Quot; Bootstrapper " contiene il punto di ingresso principale per l'eseguibile e registra un risolutore di assiemi personalizzato nell'appdominio corrente:

// 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;
        }
    }
}

" programma " è il "reale" implementazione dell'applicazione (si noti che App.Run è stato richiamato alla fine di 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();
        }
    }
}

Passaggio 4 : compila l'applicazione principale dalla riga di comando:

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

Passaggio 5 : ora abbiamo finito. La struttura della directory che abbiamo creato dovrebbe essere la seguente:

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

Se ora si esegue program.exe su una piattaforma a 32 bit, verrà caricata la piattaforma \ x86 \ library.dll; se si esegue program.exe su una piattaforma a 64 bit, verrà caricata la piattaforma \ amd64 \ library.dll. Si noti che ho aggiunto Console.ReadLine () alla fine del metodo Worker.Run in modo che sia possibile utilizzare Gestione attività / Esplora processi per esaminare le DLL caricate oppure utilizzare Visual Studio / Debugger di Windows per collegarsi al processo per vedere il call stack ecc.

Quando viene eseguito program.exe, il nostro risolutore di assiemi personalizzato è collegato all'attuale dominio. Non appena .NET inizia a caricare la classe Program, rileva una dipendenza dall'assembly 'libreria', quindi prova a caricarlo. Tuttavia, tale assembly non viene trovato (perché l'abbiamo nascosto nelle sottodirectory platform / *). Fortunatamente, il nostro risolutore personalizzato conosce i nostri inganni e in base alla piattaforma corrente tenta di caricare l'assembly dalla piattaforma / * sottodirectory appropriata.

Altri suggerimenti

La mia versione, simile a @Milan, ma con diverse importanti modifiche:

  • Funziona con TUTTE le DLL che non sono state trovate
  • Può essere attivato e disattivato
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase viene utilizzato anziché Path.GetFullPath () perché la directory corrente potrebbe essere diversa, ad es. negli scenari di hosting, Excel potrebbe caricare il plug-in ma la directory corrente non verrà impostata sulla DLL.

  • Environment.Is64BitProcess viene utilizzato al posto di PROCESSOR_ARCHITECTURE , poiché non dovremmo dipendere da ciò che è il sistema operativo, piuttosto da come è stato avviato questo processo - avrebbe potuto stato processo x86 su un sistema operativo x64. Prima di .NET 4, utilizzare invece IntPtr.Size == 8 .

Chiama questo codice in un costruttore statico di qualche classe principale che viene caricato prima di tutto il resto.

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;
    }
}

Dai un'occhiata a SetDllDirectory. L'ho usato per caricare dinamicamente un assembly spss IBM sia per x64 che x86. Ha anche risolto i percorsi per le DLL di supporto non assembly caricate dagli assembly nel mio caso era il caso delle DLL spss.

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

Puoi usare l'utilità corflags forzare un exe AnyCPU da caricare come eseguibile x86 o x64, ma ciò non soddisfa totalmente il requisito di distribuzione della copia dei file a meno che non si scelga quale exe copiare in base alla destinazione.

Questa soluzione può funzionare anche per assiemi non gestiti. Ho creato un semplice esempio simile al grande esempio di Milano Gardian. L'esempio che ho creato carica dinamicamente una DLL C ++ gestita in una DLL C # compilata per la piattaforma Any CPU. La soluzione utilizza il pacchetto nuget InjectModuleInitializer per iscriversi all'evento AssemblyResolve prima che vengano caricate le dipendenze dell'assembly.

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

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top