61

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

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

4

5 回答 5

66

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

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

为了演示这种技术,我附上了一个简短的基于命令行的教程。我在 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

第三步:主程序分为两部分。“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;
        }
    }
}

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

第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 类,它就会看到对“库”程序集的依赖,因此它会尝试加载它。但是,没有找到这样的程序集(因为我们已将其隐藏在 platform/* 子目录中)。幸运的是,我们的自定义解析器知道我们的诡计,并根据当前平台尝试从适当的 platform/* 子目录加载程序集。

于 2008-10-01T02:42:21.687 回答
24

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

  • 适用于所有未找到的 DLL
  • 可以打开和关闭
  • 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;
    }
}
于 2012-03-30T23:40:41.823 回答
4

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

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

于 2012-06-04T12:16:44.237 回答
2

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

于 2008-09-20T18:56:17.797 回答
1

此解决方案也适用于非托管程序集。我创建了一个类似于 Milan Gardian 的出色示例的简单示例。我创建的示例将托管 C++ dll 动态加载到为 Any CPU 平台编译的 C# dll 中。该解决方案利用 InjectModuleInitializer nuget 包在加载程序集的依赖项之前订阅 AssemblyResolve 事件。

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

于 2016-11-21T19:14:12.850 回答