使用同一个 DLL 的多个版本

问题描述 投票:0回答:6

我的任务是为应用程序创建一个新模块,因此,我正在向项目添加新的 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 文件夹中......

我该怎么办?

c# visual-studio-2010 .net-4.0 reference
6个回答
59
投票

假设您的项目结构如下:

Project Diagram

...其中

A
B
是类库,
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.dll
thirdparty5\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 都将在输出目录的子文件夹中可用。 (bindingRedirect 元素是可选的,但如果您需要对其应用一系列修订,则可以使用它们。)


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

11
投票

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

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

嘿,别那样看着我。我确实说过可笑的人为黑客...


6
投票

另一个可行的选择是使用

extern
,如下所述:

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


1
投票

您还可以按照 http://msdn.microsoft.com/en-us/library/2fc472t2.aspx.

中所述为强命名程序集依赖程序集绑定重定向

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


0
投票

如果有人对如何在

ASP.NET-MVC
项目上实现这一目标感兴趣,请继续阅读:

我需要在运行时根据应用程序设置密钥(来自

web.config
)切换特定版本的数据库连接器,所以我就是这样做的。

在您的

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);
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.