使用同一DLL的多个版本

63

我被要求为一个应用程序创建一个新模块,因此,我正在将新的DLL添加到项目中。这很好。

然而,在我的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文件夹中...

我该怎么办?

6个回答

64
假设你有一个项目结构如下:

Project Diagram

...其中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或另一个DLL,这两个DLL都将在输出目录的子文件夹中可用。(bindingRedirect元素是可选的,但如果您需要适用于一系列修订版本,可以使用它们。)

根据您的建议,是否可以在运行时选择要加载的程序集?我有3个不同的DLL,大部分都是相同的方法等,但内部工作方式不同,因此我希望能够选择一个版本并在整个过程中使用它。 - coloboxp
2
好消息:如果它们已被强命名(例如,我有 Newtonsoft.Json.dllNewtonsoft.Json.12.dll),则程序集也可以放在相同的输出目录中,但具有不同的名称。 - user2864740
嗨Jay,我的第三方dll是Microsoft.Azure.DocumentDB.Core.dll。我有同样的项目结构:proj A引用了dll的v2.1.3(具有批量导入功能),而proj B则引用了v2.5.1(没有批量导入功能,但有其他一些功能)。我无法让你的东西工作,并且在调用proj A中的方法时得到“找不到方法”的错误。你有什么想法吗?谢谢。 - kevinob
我按照@Jay提到的所有步骤进行了操作,但仍然出现错误:“无法加载文件或程序集'itextsharp_5_2'或其某个依赖项。所定位的程序集清单定义与程序集引用不匹配。程序集引用与找到的程序集定义不匹配。” - Ulana
2
仅供参考,对于任何最终到达此处的人:在旧的ASP.NET Web表单项目的Web.Config中执行此操作时,我必须将<codeBase>标记上的href属性设置为“bin\thirdparty5\thirdparty.dll”,以便找到DLL。 - zfrank
显示剩余3条评论

25
你可以将另一个版本加载到特定的AppDomain中
可能过于详细,但这篇文章展示了在实用环境中使用AppDomains以及它们的工作原理:

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

2
不如我所希望的那么漂亮,但它完成了工作。谢谢! - MBender
2
我允许自己编辑你的回答,包括我用来解决问题的代码示例。但主要原因是第二个链接已经失效了。你能否偶然提供一个类似于旧资源的链接? - MBender
1
@Shaamaan,我无法找到原始链接所指的内容:( 这是一个不错的入口页面,可以作为替代品: https://msdn.microsoft.com/en-us/library/ms172158(v=vs.110).aspx - sehe
@Shaamaan。我猜你的DLL依赖于其他版本的dll。所以你添加了上述代码来解决这个问题。是这样吗? 在你的DLL源代码中,你在哪里/何时执行上述代码来钩入ResolveEventHandler()?这应该发生在DLL初始化中,对吧? 你使用了PreApplicationStartMethod()的方法吗?因为我无法让PreApplicationStartMethod()的方法工作。 - Feru
@sehe - 那么第二个AppDomain在哪里?没有第二个。 - N73k
显示剩余5条评论

13
如果AppDomains解决方案不适用于您的情况,您有时间压力,有冲突的要求(像那样经常发生),并且不介意使用荒唐的技巧:
  • 使用ildasm工具(Visual Studio附带的开发人员命令提示符的一部分)反编译程序集的新版本
  • 编辑生成的.il文件以查找/替换程序集命名空间引用。 使用上面的示例,这将从itextsharp.X更改为itextsharp.new.X
  • 同样地,编辑AssemblyTitleAttribute的值。这需要将ASCII字符转换为十六进制。
  • 使用ilasm重新编译.il文件
  • 请注意,这可能需要为任何依赖程序集(例如-someassembly.core.whatever)重复执行
  • 使用不同的名称添加新的.dll到您的项目中,并明确引用它们(而不是通过nuget或其他方式)

嘿,不要那样看着我。 我确实说了 荒唐的技巧...


6

这并不能解决运行时问题。 - user2864740
4
链接已经失效。 - avenmore
1
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extern - fozylet
我甚至会在这里指出 "extern alias" https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/extern-alias - Maxter

0

0
如果有人对如何在ASP.NET-MVC项目上实现此操作感兴趣,请继续阅读:
我有一个要求,根据来自web.config的appsetting键,在运行时切换特定版本的DB连接器,以下是我的做法。
在您的Global.asax文件中,在Application_Start方法下,我只需将AssemblyResolve事件处理程序添加到AppDomain.CurrentDomain中:
using System;
using System.Reflection;
using System.IO;
using System.Configuration;

protected void Application_Start()
{
    AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolveForMyConnector);

    //rest code omitted for brevity 
}

然后我创建了一个方法,基于我的配置设置,在运行时执行切换。在这个过程中,我将新版本的dll文件放在NewVesion文件夹下,原始版本的dll文件位于bin文件夹中:
static Assembly CurrentDomain_AssemblyResolveForMyConnector(object sender, ResolveEventArgs args)
{
    bool AssemblySwitch = Convert.ToBoolean(ConfigurationManager.AppSettings["AssemblySwitch"]);
    if (AssemblySwitch)
    {
        //Get custom path where new version of dll resides
        string appPath = AppDomain.CurrentDomain.BaseDirectory;
        string latestdllpath = Path.Combine(appPath, "NewVersion");
        return Assembly.LoadFrom(latestdllpath+"\\mynewversion.dll");
    }
    else
    {
        //Get bin path of the project
        var binpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath ?? "");
        return Assembly.LoadFrom(binpath+"\\myoriginalversion.dll");
    }
}

完整的Global.asax文件:

using System;
using System.Reflection;
using System.IO;
using System.Configuration;

namespace MyProject
{
    public class MvcApplication : System.Web.HttpApplication
    {
        static Assembly CurrentDomain_AssemblyResolveForMyConnector(object sender, ResolveEventArgs args)
        {
            bool AssemblySwitch = Convert.ToBoolean(ConfigurationManager.AppSettings["AssemblySwitch"]);
            if (AssemblySwitch)
            {
                //Get custom path where new version of dll resides
                string appPath = AppDomain.CurrentDomain.BaseDirectory;
                string latestdllpath = Path.Combine(appPath, "NewVersion");
                return Assembly.LoadFrom(latestdllpath + "\\mynewversion.dll");
            }
            else
            {
                //Get bin path of the project
                var binpath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.RelativeSearchPath ?? "");
                return Assembly.LoadFrom(binpath + "\\myoriginalversion.dll");
            }
        }
        protected void Application_Start()
        {
            AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolveForMyConnector);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接