J'ai créé une solution simple qui est capable de charger un assemblage spécifique à une plateforme à partir d'un exécutable compilé en AnyCPU. La technique utilisée peut être résumée comme suit :
- Assurez-vous que le mécanisme de chargement d'assemblage .NET par défaut (moteur "Fusion") ne trouve pas la version x86 ou x64 de l'assemblage spécifique à la plate-forme.
- Avant que l'application principale ne tente de charger l'assemblage spécifique à la plate-forme, installez un résolveur d'assemblage personnalisé dans l'AppDomain actuel.
- Maintenant, lorsque l'application principale a besoin de l'assemblage spécifique à la plate-forme, le moteur Fusion abandonne (à cause de l'étape 1) et appelle notre résolveur personnalisé (à cause de l'étape 2) ; dans le résolveur personnalisé, nous déterminons la plate-forme actuelle et utilisons la recherche par répertoire pour charger la DLL appropriée.
Pour démontrer cette technique, je joins un court tutoriel basé sur la ligne de commande. J'ai testé les binaires résultants sur Windows XP x86 et ensuite Vista SP1 x64 (en copiant les binaires, tout comme votre déploiement).
Note 1 : "csc.exe" est un compilateur C-sharp. Ce tutoriel suppose qu'il est dans votre chemin (mes tests utilisaient " C:\WINDOWS\Microsoft.NET\Framework\v3.5\csc.exe ")
Note 2 : Je vous recommande de créer un dossier temporaire pour les tests et de lancer la ligne de commande (ou powershell) dont le répertoire de travail courant est défini à cet emplacement, par exemple.
(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest
Étape 1 : L'assemblage spécifique à la plateforme est représenté par une simple bibliothèque de classes 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();
}
}
}
Étape 2 : Nous compilons des assemblages spécifiques à une plateforme en utilisant de simples commandes en ligne de commande :
(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
Étape 3 : Le programme principal est divisé en deux parties. Le "Bootstrapper" contient le point d'entrée principal de l'exécutable et enregistre un résolveur d'assemblage personnalisé dans le domaine d'application actuel :
// 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;
}
}
}
"Program" est l'implémentation "réelle" de l'application (notez que App.Run a été invoqué à la fin 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();
}
}
}
Étape 4 : Compiler l'application principale en ligne de commande :
(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs
Étape 5 : Nous avons maintenant terminé. La structure du répertoire que nous avons créé devrait être la suivante :
(C:\TEMP\CrossPlatformTest, root dir)
platform (dir)
amd64 (dir)
library.dll
x86 (dir)
library.dll
program.exe
*.cs (source files)
Si vous exécutez maintenant program.exe sur une plate-forme 32 bits, la plate-forme \x86\library.dll sera chargé ; si vous exécutez program.exe sur une plateforme 64 bits, la plateforme \amd64\library.dll sera chargé. Notez que j'ai ajouté Console.ReadLine() à la fin de la méthode Worker.Run afin que vous puissiez utiliser le gestionnaire de tâches/explorateur de processus pour examiner les DLL chargées, ou que vous puissiez utiliser Visual Studio/Windows Debugger pour vous attacher au processus et voir la pile d'appels, etc.
Lorsque program.exe est exécuté, notre résolveur d'assemblage personnalisé est attaché au domaine d'application actuel. Dès que .NET commence à charger la classe Program, il voit une dépendance sur l'assemblage 'library', donc il essaie de le charger. Cependant, aucun assemblage de ce type n'est trouvé (parce que nous l'avons caché dans les sous-répertoires platform/*). Heureusement, notre résolveur personnalisé connaît notre astuce et, en fonction de la plate-forme actuelle, il essaie de charger l'assemblage à partir du sous-répertoire platform/* approprié.