Utilizzare gli assembly Side-by-Side per caricare la versione x64 o x32 di una DLL

Abbiamo due versioni di un assembly C ++ gestito, uno per x86 e uno per x64. Questo assembly è chiamato da un’applicazione .net conforms per AnyCPU. Stiamo implementando il nostro codice tramite un’installazione di copia file e vorremmo continuare a farlo.

È ansible utilizzare un manifest di assembly Side-by-Side per caricare rispettivamente un assembly x86 o x64 quando un’applicazione sta selezionando dynamicmente la sua architettura di processore? O c’è un altro modo per ottenere questo risultato in una distribuzione di copia di file (ad esempio non utilizzando il GAC)?

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

  1. Assicurati che il meccanismo di caricamento dell’assembly predefinito di .NET (motore “Fusion”) non trovi la versione x86 o x64 dell’assembly specifico della piattaforma
  2. Prima che l’applicazione principale tenti di caricare l’assembly specifico della piattaforma, installare un resolver di assemblaggio personalizzato nell’AppDomain corrente
  3. Ora, quando l’applicazione principale richiede l’assembly 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 resolver personalizzato determiniamo la piattaforma corrente e usiamo 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 su Vista SP1 x64 (copiando i binari sopra, proprio come la tua distribuzione).

Nota 1 : “csc.exe” è un compilatore C-sharp. Questo tutorial presuppone che si trovi nel tuo percorso (i miei test utilizzavano “C: \ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe”)

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

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

Passo 1 : l’assembly specifico della piattaforma è rappresentato da una semplice libreria di classi 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 assembly 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. “Bootstrapper” contiene il punto di ingresso principale per l’eseguibile e registra un resolver di assemblaggio personalizzato nell’appodominio 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” è l’implementazione “reale” dell’applicazione (notare 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 : compilare l’applicazione principale sulla 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 esegui 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 ansible utilizzare task manager / explorer di processo per indagare sulle DLL caricate, oppure è ansible utilizzare Visual Studio / Debugger di Windows per collegarsi al processo per vedere il pila di chiamate ecc.

Quando viene eseguito program.exe, il nostro risolutore dell’assieme personalizzato è collegato all’appdomain corrente. Non appena .NET inizia a caricare la class Program, vede una dipendenza dall’assembly ‘library’, quindi prova a caricarlo. Tuttavia, non viene trovato alcun assembly di questo tipo (perché lo abbiamo nascosto nelle sottodirectory platform / *). Fortunatamente, il nostro risolutore personalizzato conosce il nostro inganno e sulla base della piattaforma corrente tenta di caricare l’assembly dalla sottodirectory piattaforma / * appropriata.

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

  • Funziona per TUTTE le DLL che non sono state trovate
  • Può essere acceso e spento
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase viene utilizzato al posto di Path.GetFullPath() poiché la directory corrente potrebbe essere diversa, ad esempio 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 cosa sia il sistema operativo, piuttosto da come è stato avviato questo processo – potrebbe essere stato il processo x86 su un sistema operativo x64. Prima di .NET 4, utilizzare invece IntPtr.Size == 8 .

Chiama questo codice in un costruttore statico di una class principale che viene caricata prima di tutto.

 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 attorno al caricamento dinamico di un assembly spss IBM per x64 e x86. Ha anche risolto percorsi per DLL di supporto non assembly caricati dagli assembly nel mio caso era il caso con le DLL di spss.

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

È ansible utilizzare l’utilità corflags per forzare un exe AnyCPU da caricare come eseguibile x86 o x64, ma ciò non soddisfa completamente il requisito di distribuzione della copia del file a meno che non si scelga quale file da copiare in base alla destinazione.

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

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