如何引用两个具有相同名称的不同DLL?

时间:2019-02-21 14:24:05

标签: c# .net dll

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

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

  1. 不能同时安装MIL 9和MIL 10。也没有任何意义。
  2. MIL 9和MIL 10的C#DLL都命名为Matrox.MatroxImagingLibrary.dll
  3. 两个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都会得到一个How to save DLLs in a different folder when compiling in Visual Studio?中所述的经过改编的app.config文件。

问题:由于没有必要,我之前没有做过。因此,我目前面临几个问题:
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,因为仅通过更改文件夹就可以在不同版本的软件之间进行切换。尽可能减少与操作系统的连接。

1 个答案:

答案 0 :(得分:1)

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

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

经过大量测试并在Internet上进行了进一步阅读,我发现加载错误文件的问题原因:
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>