使用同一个 DLL 的多个版本

Posted

技术标签:

【中文标题】使用同一个 DLL 的多个版本【英文标题】:Using multiple versions of the same DLL 【发布时间】:2011-05-06 20:47:57 【问题描述】:

我的任务是为应用程序创建一个新模块,因此,我正在向项目中添加新的 DLL。这一切都很好。

但是,在我的 DLL 中,我想使用新版本的外部 DLL(我无法控制)。如果我只是引用新的 DLL 并只使用那个,我的代码将工作,但旧代码将停止运行。

Could not load file or assembly 'itextsharp, Version=5.0.6.0, Culture=neutral,
PublicKeyToken=8354ae6d2174ddca' or one of its dependencies. The located assembly's
manifest definition does not match the assembly reference. (Exception from HRESULT:
0x80131040)

我尝试了一个更改 DLL 名称的简单技巧,但这显然对我来说有点太天真了,认为它会起作用。我试过使用外部别名(通过在我的引用中定义它们),但我仍然不知道如何将两个同名文件放入一个 BIN 文件夹中......

我该怎么办?

【问题讨论】:

【参考方案1】:

假设您的项目结构如下:

...其中AB 是类库,C 是可执行类型项目(例如单元测试或控制台项目)。

假设文件夹结构是这样的:

ABC.sln
A/A.csproj
A/...
B/B.csproj
B/...
C/C.csproj
C/...
lib/thirdparty4/thirdparty.dll
lib/thirdparty5/thirdparty.dll

如果我们试图天真地引用我们的项目,我们就会遇到问题:thirdparty.dll 的两个版本将被复制到同一个文件夹中(C 的输出(即 bin)目录)。我们需要一种方法让C 将这两个 dll 复制到其输出目录中,并提供一种机制来引用其中一个。

为了解决这个问题,我修改了C.csproj 以包含以下内容:

<ItemGroup>
  <Content Include="..\lib\thirdparty4\thirdparty.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <Link>thirdparty4\thirdparty.dll</Link>
  </Content>
  <Content Include="..\lib\thirdparty5\thirdparty.dll">
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
    <Link>thirdparty5\thirdparty.dll</Link>
  </Content>
</ItemGroup>

这将指示它在其输出目录中创建thirdparty4\thirdparty.dllthirdparty5\thirdparty.dll

现在,在构建 C 之后,它的输出目录如下所示:

C\bin\Debug\A.dll
C\bin\Debug\B.dll
C\bin\Debug\C.dll
C\bin\Debug\thirdparty4\thirdparty.dll
C\bin\Debug\thirdparty5\thirdparty.dll

为了指示 C 使用这两个 dll,我向其中添加了一个 App.config 文件,其中包含以下内容:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="thirdparty" culture="neutral" publicKeyToken="1234567890123445"/>
        <bindingRedirect oldVersion="4.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
        <codeBase version="4.0.0.0" href="thirdparty4\thirdparty.dll" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="thirdparty" culture="neutral" publicKeyToken="1234567890123445"/>
        <bindingRedirect oldVersion="5.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
        <codeBase version="5.0.0.0" href="thirdparty5\thirdparty.dll" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

这将指示程序集根据需要的版本使用一个 DLL 或另一个,这两个都将在输出目录的子文件夹中可用。 (bindingRedirect 元素是可选的,但如果您需要对其进行一系列修订,则可以使用它们。)

【讨论】:

根据您的建议,可以在运行时选择加载哪个程序集?我有 3 个不同的 DLL,它们的方法大多相同等等,但内部工作方式不同,所以我希望能够选择一个版本并在整个过程中使用它。 @coloboxp 如果程序集在编译时已知并且都可以引用,那么可以使用extern alias 和外观。 很好的信息:如果程序集被强命名(例如,Newtonsoft.Json.dllNewtonsoft.Json.12.dll),也可以将程序集放在具有不同名称的相同输出目录中。 嗨 Jay,我的第三方 dll 是 Microsoft.Azure.DocumentDB.Core.dll。我有相同的项目结构:项目 A 引用 dll 的 v2.1.3(具有批量导入功能)和项目 B 引用 v2.5.1(没有批量导入上限。但有一些其他功能)。在项目 A 中调用方法时,我无法让你的东西正常工作并得到“找不到方法”。你知道吗?谢谢。 对于最终来到这里的任何人仅供参考:在旧 ASP.NET Web 窗体项目的 Web.Config 中执行此操作,我必须将 标记上的 href 属性设置为“bin \thirdparty5\thirdparty.dll" 以便它找到 DLL【参考方案2】:

你可以load another version into a specific AppDomain

可能太详细了,但这里有一篇文章演示了在有用的环境中使用 AppDomain 以及它们是如何工作的:

http://msdn.microsoft.com/en-us/magazine/cc164072.aspx

在一个非常基本的意义上,它归结为这个示例代码:

    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
    ...

    static System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
    
        if (/*some condition*/)
            return Assembly.LoadFrom("DifferentDllFolder\\differentVersion.dll");
        else
            return Assembly.LoadFrom("");
    

【讨论】:

没有我希望的那么漂亮,但它完成了工作。谢谢! 我允许自己编辑您的答案,以包含我用来解决问题的代码示例。但主要原因是第二个环节早就死了。您能否偶然将类似的资源链接到旧资源? @Shaamaan 我无法找到原始链接所指的内容:(这是一个很好的入口页面,可以很好地替代:msdn.microsoft.com/en-us/library/ms172158(v=vs.110).aspx @Shaamaan 。我假设您的 DLL 依赖于其他 dll(具有不同版本)。所以你添加了上面的代码来解决这个问题。是对的吗 ?在您的 DLL 源代码中,您在何处/何时执行上述代码以挂钩到 ResolveEventHandler()。这应该发生在 DLL 初始化中对吧?您是否为此使用了 PreApplicationStartMethod() 的方法?我无法让 PreApplicationStartMethod() 方法工作。 @Feru 请记住这个问题是 5 年前提出的,所以我对确切用例的记忆此时有点模糊。通常,我正在开发的应用程序具有“模块”(想想带有大按钮的主屏幕)。我们的任务是创建一个新模块,因此我们决定将其保留为一个单独的库。然后我们的库使用了一个从未使用过的共享库版本(我认为它与 PDF 相关)。我认为解析代码在主应用程序中,但由于模块化性质,我认为它可以连接到按钮按下。不要引用我的话——再说一遍,那是很久以前的事了。【参考方案3】:

如果 AppDomains 解决方案不适用于您的情况,您将面临时间压力,有相互冲突的要求(例如 曾经发生过),并且不介意可笑的人为黑客攻击:

使用ildasm 工具(Visual Studio 中包含的开发人员命令提示符的一部分)反编译较新版本的程序集 编辑生成的 .il 文件以查找/替换程序集命名空间引用。使用引用的示例,这将是从 itextsharp.Xitextsharp.new.X 的更改 同样,编辑 AssemblyTitleAttribute 的值。这需要将 ASCII 字符转换为十六进制。 使用ilasm重新编译.il文件 请注意,可能需要对任何依赖程序集重复此操作(例如 - someassembly.core.whatever) 使用不同的名称将新的 .dll 添加到您的项目中并明确引用它们(而不是通过 nuget 或其他方式)

嘿,别这样看我。我确实说过可笑做作的黑客......

【讨论】:

【参考方案4】:

另一个可行的选择是使用extern,如下所述:

http://blogs.msdn.com/b/abhinaba/archive/2005/11/30/498278.aspx

【讨论】:

这不能解决运行时问题。 链接已损坏。 docs.microsoft.com/en-us/dotnet/csharp/language-reference/…【参考方案5】:

您还可以为强命名程序集依赖程序集绑定重定向,如http://msdn.microsoft.com/en-us/library/2fc472t2.aspx 中所述。

您将只有一个版本的文件(最新的),两个引用都会解析到它。

【讨论】:

以上是关于使用同一个 DLL 的多个版本的主要内容,如果未能解决你的问题,请参考以下文章

Jenkins 和 MSBuild 不会构建包含相同 dll 的多个版本的 WiX .msi

如果在同一个 AppDomain 中加载了多个版本,WPF 无法选择正确的 DLL

程序集里包含多个版本dll引用 ,强制低版本到制定版本dll引用

dll多个版本问题

如何让多个PHP版本在同一个Apache服务下并存

UE4多个分支版本兼容相同的工程dll