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

时间:2020-03-06 14:29:24  来源:igfitidea点击:

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

当应用程序动态选择其处理器体系结构时,是否可以使用Side-by-Side程序集清单分别加载x86或者x64程序集?还是有另一种方法可以在文件副本部署中完成此操作(例如,不使用GAC)?

解决方案

我们可以使用corflags实用程序强制将AnyCPU exe加载为x86或者x64可执行文件,但是除非我们根据目标选择要复制的exe,否则这并不能完全满足文件副本部署要求。

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

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

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

注意1:" csc.exe"是C尖锐的编译器。本教程假定它在路径中(我的测试使用的是" C:\ WINDOWS \ Microsoft.NET \ Framework \ v3.5 \ csc.exe")

注意2:建议我们为测试创建一个临时文件夹,然后运行命令行(或者Powershell),将其当前工作目录设置为此位置,例如

(cmd.exe)
C:
mkdir \TEMP\CrossPlatformTest
cd \TEMP\CrossPlatformTest

步骤1:特定于平台的程序集由一个简单的Cclass库表示:

// 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;
        }
    }
}

"程序"是应用程序的"实际"实现(请注意,在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;否则,将运行DLL。如果在64位平台上运行program.exe,将加载platform \ amd64 \ library.dll。请注意,我在Worker.Run方法的末尾添加了Console.ReadLine(),以便我们可以使用任务管理器/进程资源管理器来研究已加载的DLL,或者可以使用Visual Studio / Windows调试器将其添加到该进程以查看调用堆栈等

当program.exe运行时,我们的自定义程序集解析器将添加到当前的appdomain。 .NET一旦开始加载Program类,它就会看到对"库"程序集的依赖,因此它将尝试加载它。但是,找不到这样的程序集(因为我们已将其隐藏在platform / *子目录中)。幸运的是,我们的自定义解析器知道我们的诡计,并基于当前平台尝试从适当的platform / *子目录加载程序集。