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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/108971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 14:17:40  来源:igfitidea点击:

Using Side-by-Side assemblies to load the x64 or x32 version of a DLL

提问by Adam L

We have two versions of a managed C++ assembly, one for x86 and one for x64. This assembly is called by a .net application complied for AnyCPU. We are deploying our code via a file copy install, and would like to continue to do so.

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

Is it possible to use a Side-by-Side assembly manifest to loading a x86 or x64 assembly respectively when an application is dynamically selecting it's processor architecture? Or is there another way to get this done in a file copy deployment (e.g. not using the GAC)?

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

采纳答案by Milan Gardian

I created a simple solution that is able to load platform-specific assembly from an executable compiled as AnyCPU. The technique used can be summarized as follows:

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

  1. Make sure default .NET assembly loading mechanism ("Fusion" engine) can't find either x86 or x64 version of the platform-specific assembly
  2. Before the main application attempts loading the platform-specific assembly, install a custom assembly resolver in the current AppDomain
  3. Now when the main application needs the platform-specific assembly, Fusion engine will give up (because of step 1) and call our custom resolver (because of step 2); in the custom resolver we determine current platform and use directory-based lookup to load appropriate DLL.
  1. 确保默认的 .NET 程序集加载机制(“Fusion”引擎)找不到特定于平台的程序集的 x86 或 x64 版本
  2. 在主应用程序尝试加载特定于平台的程序集之前,在当前 AppDomain 中安装自定义程序集解析器
  3. 现在当主应用程序需要特定于平台的程序集时,Fusion 引擎将放弃(因为第 1 步)并调用我们的自定义解析器(因为第 2 步);在自定义解析器中,我们确定当前平台并使用基于目录的查找来加载适当的 DLL。

To demonstrate this technique, I am attaching a short, command-line based tutorial. I tested the resulting binaries on Windows XP x86 and then Vista SP1 x64 (by copying the binaries over, just like your deployment).

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

Note 1: "csc.exe" is a C-sharp compiler. This tutorial assumes it is in your path (my tests were using "C:\WINDOWS\Microsoft.NET\Framework\v3.5\csc.exe")

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

Note 2: I recommend you create a temporary folder for the tests and run command line (or powershell) whose current working directory is set to this location, e.g.

注 2:我建议您为测试创建一个临时文件夹并运行命令行(或 powershell),其当前工作目录设置为此位置,例如

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

Step 1: The platform-specific assembly is represented by a simple C# class library:

步骤 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();
        }
    }
}

Step 2: We compile platform-specific assemblies using simple command-line commands:

第 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

Step 3: Main program is split into two parts. "Bootstrapper" contains main entry point for the executable and it registers a custom assembly resolver in current appdomain:

第 3 步:主程序分为两部分。“Bootstrapper”包含可执行文件的主要入口点,并在当前应用程序域中注册自定义程序集解析器:

// 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" is the "real" implementation of the application (note that App.Run was invoked at the end of Bootstrapper.Main):

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

Step 4: Compile the main application on command line:

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

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

Step 5: We're now finished. The structure of the directory we created should be as follows:

第 5 步:我们现在完成了。我们创建的目录结构应该如下:

(C:\TEMP\CrossPlatformTest, root dir)
    platform (dir)
        amd64 (dir)
            library.dll
        x86 (dir)
            library.dll
    program.exe
    *.cs (source files)

If you now run program.exe on a 32bit platform, platform\x86\library.dll will be loaded; if you run program.exe on a 64bit platform, platform\amd64\library.dll will be loaded. Note that I added Console.ReadLine() at the end of the Worker.Run method so that you can use task manager/process explorer to investigate loaded DLLs, or you can use Visual Studio/Windows Debugger to attach to the process to see the call stack etc.

如果现在在32位平台上运行program.exe,会加载platform\x86\library.dll;如果在 64 位平台上运行 program.exe,将会加载 platform\amd64\library.dll。请注意,我在 Worker.Run 方法的末尾添加了 Console.ReadLine(),以便您可以使用任务管理器/进程资源管理器来调查加载的 DLL,或者您可以使用 Visual Studio/Windows Debugger 附加到进程以查看调用栈等

When program.exe is run, our custom assembly resolver is attached to current appdomain. As soon as .NET starts loading the Program class, it sees a dependency on 'library' assembly, so it tries loading it. However, no such assembly is found (because we've hidden it in platform/* subdirectories). Luckily, our custom resolver knows our trickery and based on the current platform it tries loading the assembly from appropriate platform/* subdirectory.

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

回答by Rob Walker

You can use the corflagsutility to force an AnyCPU exe to load as an x86 or x64 executable, but that doesn't totally meet the file copy deployment requirement unless you choose which exe to copy based on the target.

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

回答by Yurik

My version, similar to @Milan, but with several important changes:

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

  • Works for ALL DLLs that were not found
  • Can be turned on and off
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBaseis used instead of Path.GetFullPath()because the current directory might be different, e.g. in hosting scenarios, Excel might load your plugin but the current directory will not be set to your DLL.

  • Environment.Is64BitProcessis used instead of PROCESSOR_ARCHITECTURE, as we should not depend on what the OS is, rather how this process was started - it could have been x86 process on a x64 OS. Before .NET 4, use IntPtr.Size == 8instead.

  • 适用于所有未找到的 DLL
  • 可以打开和关闭
  • AppDomain.CurrentDomain.SetupInformation.ApplicationBase被使用而不是Path.GetFullPath()因为当前目录可能不同,例如在托管方案中,Excel 可能会加载您的插件,但当前目录不会设置为您的 DLL。

  • Environment.Is64BitProcess使用而不是PROCESSOR_ARCHITECTURE,因为我们不应该依赖于操作系统是什么,而应该依赖于这个进程是如何启动的——它可能是 x64 操作系统上的 x86 进程。在 .NET 4 之前,请IntPtr.Size == 8改用。

Call this code in a static constructor of some main class that is loaded before all else.

在某个主类的静态构造函数中调用此代码,该类在所有其他方法之前加载。

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

回答by wvd_vegt

Have a look at SetDllDirectory. I used it around the dynamically loading of an IBM spss assembly for both x64 and x86. It also solved paths for non assembly support dll's loaded by the assemblies in my case was the case with the spss dll's.

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

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

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

回答by Kevin Marshall

This solution can work for non managed assemblies as well. I have created a simple example similar to Milan Gardian's great example. The example I created dynamically loads a Managed C++ dll into a C# dll compiled for the Any CPU platform. The solution makes use of the InjectModuleInitializer nuget package to subscribe to the AssemblyResolve event before the dependencies of the assembly are loaded.

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

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

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