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

问题描述:

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

当应用程序动态选择它的处理器架构时,是否可以使用并行程序集清单分别加载x86或x64程序集?或者还有另一种方法可以在文件复制部署中完成此操作(例如,不使用GAC)?

我创建了一个简单的解决方案,它能够从编译为AnyCPU的可执行文件加载平台特定的程序集。使用可归纳为技术如下:

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

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

注1:“csc.exe”是C-sharp编译器。本教程假定它是在你的路径(我的测试中,使用“C:\ WINDOWS \ Microsoft.NET \框架\ 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:主程序分为两部分。 “引导程序”包含主入口点的可执行文件和它注册在当前的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; 
     } 
    } 
} 

“程序”是“真正的”执行的应用程序(注意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

我们使用类似的方法,但该事件附加在静态构造函数 - 这种方式在某些情况下,附件之前发生。NET尝试加载另一个程序集。 – Yurik 2012-03-21 04:00:26

+3

请更新以使用Environment.Is64BitProcess - 因为它可能与机器上的CPU不同。否则 - 很好地回答 - 我们正在使用类似的东西。 – Yurik 2012-03-30 22:57:00

+1

PROCESSOR_ARCHITECTURE是正确的 - 它实际上反映了进程而不是机器。 – Fowl 2013-06-27 06:58:44

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

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

  • 作品对于未找到所有dll
  • 在开与关
  • ​​3210被关是用来代替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; 
    } 
} 

看看SetDllDirectory。我用它来动态加载用于x64和x86的IBM spss程序集。它也解决了在我的情况下,非程序集支持dll由程序集加载的路径与spss dll的情况。

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

该解决方案还可以将非托管程序正常工作。我创建了一个类似于米兰加迪安伟大榜样的简单例子。我创建的示例动态地将Managed C++ dll加载到为Any CPU平台编译的C#dll中。该解决方案使用InjectModuleInitializer nuget包在加载程序集的依赖关系之前订阅AssemblyResolve事件。

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