Nous avons deux versions d'un assemblage C++ géré, une pour x86 et une pour x64. Cet assembly est appelé par une application .net conforme pour AnyCPU. Nous déployons notre code via une installation de copie de fichiers et souhaitons continuer à le faire.
Est-il possible d'utiliser un manifeste d'assemblage côte à côte pour charger un assemblage x86 ou x64 respectivement lorsqu'une application sélectionne dynamiquement son architecture de processeur? Ou existe-t-il une autre façon de procéder dans un déploiement de copie de fichiers (par exemple, sans utiliser le GAC)?
J'ai créé une solution simple qui est capable de charger l'assembly spécifique à la plate-forme à partir d'un exécutable compilé en AnyCPU. La technique utilisée peut être résumée comme suit:
Pour illustrer cette technique, je joins un court didacticiel en ligne de commande. J'ai testé les binaires résultants sous Windows XP x86 puis 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 se trouve sur 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 d'exécuter la ligne de commande (ou powershell) dont le répertoire de travail actuel est défini à cet emplacement, par ex.
(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest
Étape 1: l'assembly 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 assemblys spécifiques à la plate-forme à l'aide 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: Le programme principal est divisé en deux parties. "Bootstrapper" contient le point d'entrée principal de l'exécutable et il 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 la "vraie" implémentation 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: Compilez 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éé doit ê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, platform\x86\library.dll sera chargé; si vous exécutez program.exe sur une plate-forme 64 bits, platform\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/l'Explorateur de processus pour enquêter sur les DLL chargées, ou vous pouvez utiliser Visual Studio/Windows Debugger pour attacher au processus pour voir le 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 à l'assembly "bibliothèque", il essaie donc de le charger. Cependant, aucun tel assembly n'est trouvé (car nous l'avons caché dans les sous-répertoires platform/*). Heureusement, notre résolveur personnalisé connaît notre ruse et en fonction de la plate-forme actuelle, il essaie de charger l'assembly à partir du sous-répertoire platform/* approprié.
Ma version, similaire à @Milan, mais avec plusieurs changements importants:
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
Est utilisé à la place de Path.GetFullPath()
car le répertoire courant peut être différent, par ex. dans les scénarios d'hébergement, Excel peut charger votre plugin mais le répertoire actuel ne sera pas défini sur votre DLL.
Environment.Is64BitProcess
Est utilisé à la place de PROCESSOR_ARCHITECTURE
, Car nous ne devrions pas dépendre de ce qu'est le système d'exploitation, mais plutôt de la façon dont ce processus a été démarré - il aurait pu s'agir d'un processus x86 sur un système d'exploitation x64. Avant .NET 4, utilisez plutôt IntPtr.Size == 8
.
Appelez ce code dans un constructeur statique d'une classe principale chargée avant tout.
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;
}
}
Jetez un œil à SetDllDirectory. Je l'ai utilisé pour le chargement dynamique d'un assemblage IBM SPSS pour x64 et x86. Il a également résolu des chemins pour les dll de support non Assembly chargés par les assemblys dans mon cas était le cas avec les dll spss.
http://msdn.Microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
Vous pouvez utiliser l'utilitaire corflags pour forcer un exe AnyCPU à se charger en tant qu'exécutable x86 ou x64, mais cela ne répond pas totalement aux exigences de déploiement de copie de fichier, sauf si vous choisissez l'exe à copier en fonction de la cible .
Cette solution peut également fonctionner pour les assemblys non gérés. J'ai créé un exemple simple similaire au grand exemple de Milan Gardian. L'exemple que j'ai créé charge dynamiquement une DLL C++ gérée dans une DLL C # compilée pour la plate-forme Any CPU. La solution utilise le package de nuget InjectModuleInitializer pour s'abonner à l'événement AssemblyResolve avant le chargement des dépendances de l'assembly.