【问题标题】:Compile a version agnostic DLL in .NET在 .NET 中编译与版本无关的 DLL
【发布时间】:2010-09-21 15:01:15
【问题描述】:

场景

我有两个围绕 Microsoft Office 的包装器,一个用于 2003,一个用于 2007。由于同时运行两个版本的 Microsoft Office 是“不可能的”,微软也不推荐,所以我们有两个盒子,一个带有 Office 2003另一个使用 Office 2007。我们分别编译包装器。 DLL 包含在我们的解决方案中,每个框都有 same 结帐,但 Office 2003 或 2007 “已卸载”,因此它不会尝试编译该特定 DLL。由于 Office COM DLL 不可用,否则将在编译时引发错误。

我们使用 .NET 2.0 和 Visual Studio 2008。

事实

由于 Microsoft 在 2007 年神秘地更改了 Office 2003 API,重命名和更改了一些方法 (sigh) 从而使它们无法向后兼容,因此我们需要这两个包装器。 我们为每台构建机器提供了解决方案,并激活了一个 Office DLL。例如:装有 Office 2003 的机器已卸载“Office 2007”DLL,因此未对其进行编译。另一个盒子是同样的想法,但反过来。这一切都是因为我们不能在同一个盒子里有 2 个不同的 Office 用于编程目的。 (根据 Microsoft 的说法,从技术上讲,您可以同时拥有两个 Office)但 不是 用于编程并且并非没有一些问题。

问题

当我们更改应用程序版本(例如从 1.5.0.1 到 1.5.0.2)时,我们需要重新编译 DLL 以匹配应用程序的新版本,这是自动完成的,因为解决方案中包含 Office 包装器.由于包装器包含在解决方案中,它们继承了 APP 版本,但我们必须这样做两次,然后将另一个 DLL“复制”到创建安装程序的机器上。 (疼痛……)

问题

是否有可能编译一个适用于任何版本的应用程序的DLL,尽管它是“旧的”?我读过一些关于清单的东西,但我从来没有与那些互动过。任何指针将不胜感激。

这样做的秘密原因是我们没有改变“时代”的包装器,微软也没有改变他们古老的 API,但我们正在重新编译 DLL 以匹配每个上的应用程序版本发布我们制作。我想自动化这个过程,而不必依赖 两台 机器。

我无法从项目中删除 DLL(两者都没有),因为存在依赖项。

我可以创建第三个“主包装器”,但还没有考虑过。

有什么想法吗?其他有同样要求的人吗?

更新

澄清:

我有 1 个包含 N 个项目的解决方案。

“应用程序”+ Office11Wrapper.dll + Office12Wrapper.dll。

两个“包装器”都使用应用程序+解决方案中的其他库(数据层、业务层、框架等)的依赖项

每个包装器都有各自 Office 包(2003 和 2007)的引用。

如果我编译但没有安装 office 12,我会收到来自 Office12Wrapper.dll 的错误,找不到 Office 2007 库。 所以我有两台构建机器,一台使用 Office 2003,一台使用 Office 2007。在每台机器上进行完整的 SVN 更新 + 编译后,我们只需在“安装程序”中使用 office12.dll 来针对“相同”编译包装器代码,相同版本”。

注意:Office 2007 Build Machine 已“卸载”Office 2003 的 Wrapper,反之亦然。

提前致谢。

【问题讨论】:

  • 我修复了答案中的链接,该链接指向一篇关于如何并排安装两者的 MS 文章。

标签: c# .net visual-studio-2008 dll version


【解决方案1】:

当 .NET 程序集解析器在运行时无法找到引用的程序集时(在这种情况下,它无法找到应用程序链接到的特定包装 DLL 版本),它的默认行为是失败并实质上使应用程序崩溃。但是,可以通过挂钩 AppDomain.AssemblyResolve 事件来覆盖此行为。只要找不到引用的程序集,就会触发此事件,它使您有机会用另一个程序集替换缺少的程序集(前提是它们兼容)。因此,例如,您可以替换您自己加载的旧版本的包装 DLL。

我发现最好的方法是在应用程序的主类上添加一个静态构造函数来挂钩事件,例如:

using System.Reflection;

static Program()
{
    AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs e)
    {
        AssemblyName requestedName = new AssemblyName(e.Name);

        if (requestedName.Name == "Office11Wrapper")
        {
            // Put code here to load whatever version of the assembly you actually have

            return Assembly.LoadFile("Office11Wrapper.DLL");
        }
        else
        {
            return null;
        }
    }
}

通过将它放在主应用程序类的静态构造函数中,它可以保证在任何代码尝试访问包装 DLL 中的任何内容之前运行,从而确保挂钩提前到位。

您也可以使用策略文件进行版本重定向,但这往往更复杂。

【讨论】:

  • 您能否简要解释一下或指导我如何使用策略文件来实现这一点?我目前正在尝试解决升级到较新 dll 版本时出现的问题(请参阅stackoverflow.com/questions/30165393/…
  • 我在使用此解决方案时遇到问题。静态代码在 dll 解析之前不运行。我怎么知道?我试图在静态初始化程序中抛出异常,但 dll 解析仍然是唯一出现的异常。但是,如果提供了 dll,那么我会看到我的异常。
  • 已解决,通过创建一个调用原始 Main 方法的包装器 Main 方法。另见:Redirecting Assembly Loads at Runtime
【解决方案2】:

只是一个想法 - 您能否使用 TlbExp 创建两个互操作程序集(具有不同的名称和程序集),并使用接口/工厂通过您自己的接口对两者进行编码?一旦你有了互操作 dll,你就不需要 COM 依赖(当然除了测试等)。

TlbImp 有一个版本的 /asmversion,所以它可以作为构建脚本的一部分来完成;但我敢肯定你甚至需要这个:只需确保参考(解决方案资源管理器)中的“特定版本”是错误的?

另外 - 我知道它没有帮助,但是带有 dynamic 和/或“无 PIA”的 C# 4.0 可能会有所帮助(将来;也许)。

【讨论】:

  • 谢谢 Marc,我也试试。我厌倦了这两个建筑盒子。 ;)
【解决方案3】:

我不确定我是否完全遵循你所说的一切,但让我试试吧:

听起来您有一个包含 2(?) 个项目的解决方案。一个是实际应用程序,另一个是 Office API 的包装器。然后,您的应用程序具有对您的 Office API 包装器的项目引用。我以前从未为 Office 编程过,但听起来编程 API 是一个通用组件,您只能在机器上拥有一个版本(即 2003 或 2007,而不是两者)。也许这就是问题所在,但是因为您有一个项目引用,所以包装器将首先被编译,复制到您的应用程序的 bin 目录,您的应用程序将链接到该包装器的构建。这将导致应用程序的清单在运行时专门请求该版本的包装器。

如果您将包装器放在单独的解决方案中,并添加了对已编译库而不是项目的引用,您将始终将您的应用程序链接到该包装器版本,这样就可以避免问题。

另一个可能的选择是程序集绑定重定向。这是更高级的,并带有它自己的一组问题,但你可以阅读它here

或者类似于 Marc 的想法,您可以提取一个接口并将一些通用对象拉入一个框架库,然后针对接口和通用对象编写您的应用程序。然后在运行时使用反射来加载程序集并实例化您想要的包装器。

我认为关键是如果可以的话,删除项目依赖。听起来包装器非常稳定并且没有改变,否则您不会要求链接到它的先前版本。

【讨论】:

  • 这很接近,但不正确。 ;) 我有三个项目的一个解决方案(更多,但相关......)。实际应用 & OfficeWrapper11.dll 和 OfficeWrapper12.dll。我会探索你的想法。泰。
【解决方案4】:

在同一台机器上并排安装 Office 2003 和 2007 is definitely possible - 我们在我们的组织中甚至在最终用户生产工作站上都​​这样做。

在那篇链接的文章中,Microsoft 建议您不要在实际使用时这样做。但在您的情况下,它似乎仅适用于单个构建机器,即您不会在该机器上实际使用任一版本的 Office。在这种情况下,我会尝试看看您是否可以进行并行安装。

我的假设可能是错误的,您正在尝试为每个开发人员的机器执行此操作。在这种情况下,你应该忽略这个答案:-)

【讨论】:

  • 我还没有真正尝试过,只是推测了一些谷歌搜索。我认为避免接触工作代码的一种方法是尝试安装两个办公软件包,从旧到新。 (2003 年第一,2007 年第二)。谢谢,我也试试。
【解决方案5】:

很好的侦探工作!我只是根据上面介绍的概念拼凑了一个实现,它的效果非常好:

static Assembly domain_AssemblyResolve(object sender, ResolveEventArgs args)
{
     string partialName = args.Name.Substring(0, args.Name.IndexOf(','));
     return Assembly.Load(new AssemblyName(partialName));
}

当然还有改进的余地,但这对我有用!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-04
    • 2011-02-06
    • 1970-01-01
    • 2019-04-16
    • 1970-01-01
    • 1970-01-01
    • 2021-11-26
    相关资源
    最近更新 更多