使用并行程序集加载DLL的x64或x32版本

我们有两个版本的托pipeC ++程序集,一个用于x86,一个用于x64。 这个程序集是由AnyCPU编译的.net应用程序调用的。 我们正在部署我们的代码通过文件复制安装,并希望继续这样做。

当应用程序dynamicselect处理器体系结构时,是否可以使用并行程序集清单分别加载x86或x64程序集? 还是有另一种方式来完成文件复制部署(例如不使用GAC)?

我创build了一个简单的解决scheme,可以从编译为AnyCPU的可执行文件加载平台特定的程序集。 所使用的技术可以总结如下:

  1. 确保默认的.NET程序集加载机制(“Fusion”引擎)找不到x86或x64版本的特定于平台的程序集
  2. 在主应用程序尝试加载平台特定的程序集之前,在当前的AppDomain中安装一个自定义程序集parsing器
  3. 现在当主应用程序需要特定于平台的程序集时,Fusion引擎将放弃(由于步骤1)并调用我们的自定义parsing器(由于步骤2); 在自定义parsing器中,我们确定当前平台,并使用基于目录的查找来加载适当的DLL。

为了演示这种技术,我附上一个简短的基于命令行的教程。 我testing了在Windows XP x86和Vista SP1 x64上产生的二进制文件(通过复制二进制文件,就像部署一样)。

注1 :“csc.exe”是C-sharp编译器。 本教程假设它在你的path中(我的testing使用“C:\ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe”)

注2 :我build议你为当前工作目录设置为这个位置的testing和运行命令行(或者PowerShell)创build一个临时文件夹,例如

(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 

第三步 :主程序分为两部分。 “Bootstrapper”包含可执行文件的主要入口点,并在当前的appdomain中注册一个自定义程序集parsing器:

 // 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”是应用程序的“真实”实现(注意App.Run是在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(); } } } 

第4步 :在命令行上编译主应用程序:

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

第五步 :我们现在完成了。 我们创build的目录的结构应该如下所示:

 (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(),以便您可以使用任务pipe理器/进程资源pipe理器来调查加载的DLL,或者可以使用Visual Studio / Windowsdebugging器附加到进程以查看调用堆栈等

当program.exe运行时,我们的自定义程序集parsing器被附加到当前的appdomain。 一旦.NET开始加载Program类,它就会看到“库”组件的依赖关系,所以它会尝试加载它。 但是,没有find这样的程序集(因为我们已经将它隐藏在platform / *子目录中)。 幸运的是,我们的自定义parsing器知道我们的诡计,并根据当前平台尝试从适当的platform / *子目录加载程序集。

我的版本,类似于@Milan,但有几个重要的变化:

  • 适用于所有未find的DLL
  • 可以打开和closures
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase代替Path.GetFullPath()因为当前目录可能不同,例如,在托pipescheme中,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; } } 

看看SetDllDirectory。 我用它来dynamic加载x64和x86的IBM spss程序集。 它也解决了path的非程序集支持dll的加载组件在我的情况是与spss DLL的情况下。

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

您可以使用corflags实用工具来强制AnyCPU exe作为x86或x64可执行文件加载,但除非您根据目标select要复制的exe文件,否则不会完全满足文件复制部署要求。

此解决scheme也可以用于非托pipe程序集。 我创造了一个类似于米兰加迪安伟大榜样的简单例子。 我创build的这个例子dynamic地将一个Managed C ++ dll加载到为Any CPU平台编译的C#dll中。 该解决scheme使用InjectModuleInitializer nuget包订阅程序集的依赖关系加载之前的AssemblyResolve事件。

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