我们有两个版本的托管C ++程序集,一个用于x86,另一个用于x64。此程序集由符合AnyCPU的.net应用程序调用。我们正在通过文件复制安装部署我们的代码,并希望继续这样做。
当应用程序动态选择处理器体系结构时,是否可以使用并排程序集清单分别加载x86或x64程序集?或者是否有另一种方法可以在文件复制部署中完成此操作(例如,不使用GAC)?
答案 0 :(得分:63)
我创建了一个简单的解决方案,它能够从编译为AnyCPU的可执行文件加载特定于平台的程序集。使用的技术可归纳如下:
为了演示这种技术,我附上了一个简短的基于命令行的教程。我在Windows XP x86和Vista SP1 x64上测试了生成的二进制文件(通过复制二进制文件,就像部署一样)。
注1 :“csc.exe”是一个C-sharp编译器。本教程假设它在您的路径中(我的测试使用“C:\ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe”)
注意2 :我建议您为测试创建一个临时文件夹,并运行当前工作目录设置为此位置的命令行(或powershell),例如
(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest
第1步:特定于平台的程序集由一个简单的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();
}
}
}
第2步:我们使用简单的命令行命令编译特定于平台的程序集:
(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
第3步:主程序分为两部分。 “Bootstrapper”包含可执行文件的主入口点,它在当前appdomain中注册自定义程序集解析程序:
// 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”是应用程序的“真实”实现(请注意,在Bootstrapper.Main结束时调用了App.Run):
// file 'program.cs' in C:\TEMP\CrossPlatformTest
namespace Cross.Platform.Program
{
public static class App
{
public static void Run()
{
Cross.Platform.Library.Worker.Run();
}
}
}
第4步:在命令行上编译主应用程序:
(cmd.exe from Note 2)
csc /reference:platform\x86\library.dll /out:program.exe program.cs bootstrapper.cs
第5步:我们现在已经完成了。我们创建的目录结构应如下所示:
(C:\TEMP\CrossPlatformTest, root dir)
platform (dir)
amd64 (dir)
library.dll
x86 (dir)
library.dll
program.exe
*.cs (source files)
如果您现在在32位平台上运行program.exe,将加载platform \ x86 \ library.dll;如果您在64位平台上运行program.exe,将加载platform \ amd64 \ library.dll。请注意,我在Worker.Run方法的末尾添加了Console.ReadLine(),以便您可以使用任务管理器/进程资源管理器来调查加载的DLL,或者您可以使用Visual Studio / Windows调试程序附加到进程以查看调用堆栈等。
运行program.exe时,我们的自定义程序集解析程序将附加到当前的appdomain。一旦.NET开始加载Program类,它就会看到对'library'程序集的依赖,所以它会尝试加载它。但是,没有找到这样的程序集(因为我们将它隐藏在platform / *子目录中)。幸运的是,我们的自定义解析器知道我们的诡计,并且基于当前平台,它尝试从适当的平台/ *子目录加载程序集。
答案 1 :(得分:23)
我的版本,类似于@Milan,但有几个重要的变化:
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
代替Path.GetFullPath()
,因为当前目录可能不同,例如在托管方案中,Excel可能会加载您的插件,但当前目录将不会设置为您的DLL。
Environment.Is64BitProcess
代替PROCESSOR_ARCHITECTURE
,因为我们不应该依赖操作系统是什么,而是依赖于这个过程是如何启动的 - 它可能是x64操作系统上的x86进程。在.NET 4之前,请改用IntPtr.Size == 8
。
在一个主要类的静态构造函数中调用此代码,该构造函数首先加载。
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;
}
}
答案 2 :(得分:3)
看看SetDllDirectory。我使用它围绕动态加载x64和x86的IBM spss程序集。它还解决了非装配支持的路径dll由我的情况下装配的装配是spss dll的情况。
http://msdn.microsoft.com/en-us/library/ms686203%28VS.85%29.aspx
答案 3 :(得分:2)
您可以使用corflags实用程序强制AnyCPU exe作为x86或x64可执行文件加载,但这并不完全符合文件复制部署要求,除非您根据目标选择要复制的exe
答案 4 :(得分:1)
此解决方案也适用于非托管程序集。我已经创建了一个类似于米兰加迪安的简单例子。我创建的示例动态地将Managed C ++ dll加载到为Any CPU平台编译的C#dll中。该解决方案利用InjectModuleInitializer nuget包在加载程序集的依赖项之前订阅AssemblyResolve事件。