c# - 如何引用2个具有相同名称的不同DLL?

标签 c# .net dll

我正在开发使用Matrox Imaging Library(MIL)的软件。
该软件过去使用的是MIL版本9,现在我们移至v10。由于向后兼容,我们必须继续支持v9。

使用MIL及其DLL时会遇到一些困难:


MIL 9和MIL 10不能同时安装。这也没有任何意义。
MIL 9和MIL 10的C#DLL都命名为Matrox.MatroxImagingLibrary.dll
两个DLL中的名称空间是相同的。


尽管这对于可交换性非常有用(尽管某些功能已更改),但对于并行使用而言却是一个大问题。
由于文件名和名称空间相同,我无法在同一程序集中引用这两个DLL,因此我为每个程序都创建了一个程序集,
imaging-system_mil9
imaging-system_mil10
这是必要的,但到目前为止还没有用。我需要的是通用程序集中的基类,以便可以使用继承,因此我创建了程序集
imaging-system
在此程序集中,我为MIL命令添加了自己的包装器。
当我最初在安装了MIL 9的开发计算机上进行开发和测试时,这似乎是一个非常不错的解决方案,并且运行良好。当我转移到另一台计算机上并使用MIL 10进行开发和测试时,我发现包装程序中的某些命令需要修改,因为它们已在MIL 10 C#DLL中进行了更改。到目前为止,一切都很好。

今天,我回到我的MIL 9计算机上,想测试更多东西,但是我的测试程序无法启动,并说MissingMethodException。经过一番搜索,我发现我完全忘记了要解决的一个问题:相同的文件名:
我的测试程序引用了imaging-systemimaging-system_mil9imaging-system_mil10。最后两个都引用文件Matrox.MatroxImagingLibrary.dll,因此bin文件夹中的输出如下所示:

test.exe
imaging-system.dll
imaging-system_mil9.dll
imaging-system_mil10.dll
Matrox.MatroxImagingLibrary.dll (the one from MIL 9)
Matrox.MatroxImagingLibrary.dll (the one from MIL 10)


如您所见,最后两个文件具有相同的名称,因此基本上就像彩票一样,一个文件被另一个文件覆盖。

我必须解决此问题的第一个想法是将文件重命名为
Matrox.MatroxImagingLibrary9.dll
Matrox.MatroxImagingLibrary10.dll
这适用于第一个编译级别,当
imaging-system_mil9.dll
imaging-system_mil10.dll
之所以进行编译,是因为它们直接引用相应的文件。 bin文件夹之一中的输出:

imaging-system_mil10.dll
Matrox.MatroxImagingLibrary10.dll


但是,在编译不直接引用Matrox DLL的程序集时,它将在下一级别失败。编译器仅跳过重命名的文件,这很可能是因为程序集名称不再与文件名匹配。 bin文件夹在这里:

test.exe
imaging-system.dll
imaging-system_mil9.dll
imaging-system_mil10.dll
missing: Matrox.MatroxImagingLibrary9.dll, Matrox.MatroxImagingLibrary10.dll


此外,将重命名的文件手动复制到EXE的输出文件夹也无济于事,因为EXE不会“看到”它们。这很有道理:假设有1000个DLL,并且没有一个像程序正在寻找的程序集那样命名。应该如何找到它?它无法加载所有1000个DLL。因此,文件名必须与程序集名称匹配。

我的下一个想法是为Matrox DLL设置CopyLocal = false,并通过构建后事件将它们分别复制到dll\mil9中。 dll\mil10子文件夹。
每个程序集都将运行构建前或构建后的PowerShell脚本,该脚本将从所有引用的DLL的所有dll子文件夹中复制所有内容。
每个EXE都会获得一个适应的app.config文件,如How to save DLLs in a different folder when compiling in Visual Studio?中所述。

问题:因为没有必要,我之前没有做过。因此,我目前面临几个问题:
1)EXE是否会找到正确的Matrox DLL,因为在搜索子文件夹时会同时看到它们?这些DLL具有相同的名称,相同的区域性和相同的publicKeyToken,但是版本号不同,因此可以彼此区分开。
2)如何在构建过程中获取引用的DLL路径的列表,以馈入我的PowerShell脚本中,该脚本查找dll子文件夹并复制文件?我想到的唯一方法是读取csproj文件。



到目前为止,我已经在#1测试过:
我已经用一个包含控制台EXE和2个DLL的测试解决方案进行了几次测试,它们复制了这种情况。
我使用了“ CopyLocal = false”和“ SpecificVersion = true”,我在<probing>文件中尝试了codebase>app.config,但是它仅适用于以下DLL之一:

测试文件夹结构:

test.exe
dll\testDLL9.DLL
dll\testDLL10.DLL
mil9-x64\mil.net\Matrox.MatroxImagingLibrary.dll
mil10-x64\mil.net\Matrox.MatroxImagingLibrary.dll


测试EXE:

private static void Main ()
{
  Mil10 ();  // when stepping into this, dll\testDLL10.dll is loaded
  Mil9 ();   // when stepping into this, dll\testDLL9.dll is loaded
}

private static void Mil10 ()  // when arriving here, dll\testDLL10.dll has been loaded
{
  testDLL10.CDLL10.Work ();   // when stepping into this, mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll is loaded
}

private static void Mil9 ()  // when arriving here, dll\testDLL9.dll has been loaded
{
  testDLL9.CDLL9.Work ();   // when stepping into this, MissingMethodException is thrown, which is correct, because the EXE uses the already loaded DLL, which is the wrong one.
}


现在,首先调用Mil9()时,它还会加载
mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll
调用testDLL9.CDLL9.Work()时,这显然是完全错误的。为什么会这样?
它仅在删除对testDLL10的引用并注释掉相关功能时才有效。

app.config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" />
  </startup>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="dll" />

      <dependentAssembly>
        <assemblyIdentity name="Matrox.MatroxImagingLibrary"
                          publicKeyToken="5a83d419d44a9d98"
                          culture="neutral" />
        <codeBase version="9.2.1109.1" href="mil9-x64\Mil.net\Matrox.MatroxImagingLibrary.dll" />
      </dependentAssembly>

      <dependentAssembly>
        <assemblyIdentity name="Matrox.MatroxImagingLibrary"
                          publicKeyToken="5a83d419d44a9d98"
                          culture="neutral" />
        <codeBase version="10.30.595.0" href="mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>


最后说明:


不必同时加载DLL,因为这是不可能的,因为无法并行安装MIL 9和MIL 10,请参见上文。
我已经读过Referencing DLL's with same name,但是到目前为止,我不想按照答案的第3步中的建议手动加载DLL。我希望CLR为我加载正确的DLL。
我已经读过How to reference different assemblies with the same name?。我不能使用GAC,因为仅通过更改文件夹就可以在不同版本的软件之间进行切换。与操作系统的连接尽可能少。

最佳答案

EXE是否会找到正确的Matrox DLL [...]?



否,如果使用<probing>,因为这只会将某些文件夹添加到搜索引用的程序集时检查的文件夹列表中。如果找到具有所请求名称的文件,则使用该文件。不执行任何版本检查。如果这是正确的文件,则可以使用。如果文件错误,则失败。并且,如果已加载了具有所请求名称的文件,则无论该版本是否与所请求版本匹配,都将在以后重用。
是的,如果使用<codebase>,因为这包括版本检查。

经过大量测试并在互联网上进一步阅读后,我发现加载错误文件的问题原因:
MS Doc "How the Runtime Locates Assemblies"在“步骤1:检查配置文件”一章中指出:


  首先,公共语言运行时检查应用程序配置文件中的信息,这些信息将覆盖存储在调用程序集清单中的版本信息。


所以我想:“好吧,让我们看一下清单,看看其中是否包含一些有用的数据。”我打开它,发现...什么都没有。因此,我检查了输出文件夹中的其他文件,而testAPP.exe.config引起了我的注意。到那时,我还以为这是我创建的app.config的简单副本,但是令人惊讶的是,除了我的内容之外,它还包含另一个非常相关的块,立即引起了我的注意:

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
  <dependentAssembly>
    <assemblyIdentity name="Matrox.MatroxImagingLibrary" publicKeyToken="5a83d419d44a9d98" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-10.30.595.0" newVersion="10.30.595.0" />
  </dependentAssembly>
</assemblyBinding>


这就是为什么我的测试程序总是尝试加载v10库的原因。我的下一个问题是:到底是怎么回事?
因此,我搜索了“ c#编译器添加了程序集版本重定向”,并找到了How to: Enable and Disable Automatic Binding Redirection,其中说:


  默认情况下,针对.NET Framework 4.5.1和更高版本的Windows桌面应用程序会启用自动绑定重定向。编译应用程序时,绑定重定向将添加到输出配置(app.config)文件中,并覆盖否则可能发生的程序集统一。源app.config文件未修改。


在VS 2015及更低版本中,必须手动编辑csproj文件:
<AutoGenerateBindingRedirects>设置为false
您也可以删除整个条目,但是设置为false可以确保以后不会自动用true重新添加它。
进行此编辑后,输出配置文件与我的源文件100%相同(包括任何换行符和空白行)。最后,我的测试EXE恰好在正确的时间以正确的顺序加载了所需的DLL:

'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\dll\testDLL9.dll'. Symbols loaded.
'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\mil9-x64\Mil.net\Matrox.MatroxImagingLibrary.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\dll\testDLL10.dll'. Symbols loaded.
'testAPP.vshost.exe' (CLR v4.0.30319: testAPP.vshost.exe): Loaded 'D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\mil10-x64\Mil.net\Matrox.MatroxImagingLibrary.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.


是的! :-)

剩下的唯一问题是编译器警告:

1>------ Build started: Project: testAPP, Configuration: Debug x64 ------
1>  No way to resolve conflict between "Matrox.MatroxImagingLibrary, Version=10.30.595.0, Culture=neutral, PublicKeyToken=5a83d419d44a9d98" and "Matrox.MatroxImagingLibrary, Version=9.2.1109.1, Culture=neutral, PublicKeyToken=5a83d419d44a9d98". Choosing "Matrox.MatroxImagingLibrary, Version=10.30.595.0, Culture=neutral, PublicKeyToken=5a83d419d44a9d98" arbitrarily.
1>  Consider app.config remapping of assembly "Matrox.MatroxImagingLibrary, Culture=neutral, PublicKeyToken=5a83d419d44a9d98" from Version "9.2.1109.1" [] to Version "10.30.595.0" [] to solve conflict and get rid of warning.
1>C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.Common.CurrentVersion.targets(1820,5): warning MSB3276: Found conflicts between different versions of the same dependent assembly. Please set the "AutoGenerateBindingRedirects" property to true in the project file. For more information, see http://go.microsoft.com/fwlink/?LinkId=294190.
1>  testAPP -> D:\Visual Studio Projects\testTKS_vs2015\testAPP\bin\x64\Debug\testAPP.exe
========== Build: 1 succeeded, 0 failed, 2 up-to-date, 0 skipped ==========


也许我可以以某种方式禁用它。



如何在构建过程中获取引用的DLL路径列表,以馈入到我的PowerShell脚本中,该脚本查找dll子文件夹并复制文件?

很可能我做不到。

我将编写一个小型的C#程序来完成这项工作:它将使用项目文件的名称,进行读取,然后搜索所有项目引用和基于文件的引用。然后,它将在这些引用的项目和文件的文件夹中查找dll子文件夹,并将内容复制到本地dll子文件夹。



最后说明:


在测试期间,我使用<codebase>成功引用了我的弱汇编:


该程序集没有publicKeyToken,因此我将其保留:

  <!-- probing privatePath="dll" /-->
  <dependentAssembly>
    <assemblyIdentity name="testDLL9" culture="neutral" />
    <codeBase version="1.0.0.0" href="dll\testDLL9.dll" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="testDLL10" culture="neutral" />
    <codeBase version="1.0.0.0" href="dll\testDLL10.dll" />
  </dependentAssembly>

关于c# - 如何引用2个具有相同名称的不同DLL?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54809315/

相关文章:

c++ - 测试 DLL 函数的最佳方法是什么?

c# - 如何从编译的程序集中确定原始 .NET 语言?

c# - 如何动态添加按钮名称和文本到gridview按钮?

c# - 如何在不使用Assembly.Load的情况下加载程序集?

c# - 使用 C# ServiceController 和模拟启动/停止 Windows 服务

dll - 在 COM dll 上使用 tlbexp.exe

c - 在 Visual Studio 2012 中生成 .h 文件以及 DLL

c# - 自动在 Visual Studio c# 项目中嵌入 mercurial 修订信息

C# 找不到指定的文件

c# - 为什么要使用 HMAC.Initialize();