【问题标题】:Working with Visual Studios C++ manifest files使用 Visual Studios C++ 清单文件
【发布时间】:2009-02-26 01:20:25
【问题描述】:

我编写了一些代码,利用开源库来完成一些繁重的工作。这项工作是在 linux 中完成的,使用单元测试和 cmake 来帮助将其移植到 Windows。要求它在两个平台上运行。

我喜欢 Linux,我喜欢 cmake,我喜欢我可以自动生成 Visual Studios 文件。就像现在一样,在 Windows 上,一切都会编译,它会链接并生成测试可执行文件。

然而,为了达到这一点,我不得不与 windows 斗争了几天,学习所有关于清单文件和可再发行包的知识。

就我的理解而言:

在 VS 2005 中,Microsoft 创建了 Side By Side dll。这样做的动机是,以前,多个应用程序会安装同一个 dll 的不同版本,导致以前安装和工作的应用程序崩溃(即“Dll Hell”)。并排 dll 解决了这个问题,因为现在每个可执行文件/dll 都附加了一个“清单文件”,用于指定应该执行哪个版本。

这一切都很好。应用程序不应再神秘地崩溃。不过……

Microsoft 似乎在每次发布 Visual Studios 时都会发布一组新的系统 dll。另外,正如我之前提到的,我是一名试图链接到第三方库的开发人员。通常,这些东西作为“预编译的 dll”分发。现在,当使用一个版本的 Visual Studio 编译的预编译 dll 链接到使用另一个版本的 Visual Studio 的应用程序时会发生什么?

根据我在互联网上阅读的内容,发生了不好的事情。幸运的是,我从来没有走到那一步——我在运行可执行文件时一直遇到“MSVCR80.dll not found”问题,因此我开始着手解决整个清单问题。

我最终得出的结论是,让这个工作(除了静态链接所有内容)的唯一方法是所有第三方库必须使用相同版本的 Visual Studios 编译 - 即不要使用预编译的 dll - 下载源,构建一个新的 dll 并改用它。

这是真的吗?我错过了什么吗?

此外,如果看起来确实如此,那么我不禁认为微软出于邪恶的原因故意这样做。

它不仅会破坏所有预编译的二进制文件,从而使使用预编译的二进制文件变得不必要地困难,如果您碰巧为一家使用第三方专有库的软件公司工作,那么每当他们升级到最新版本的视觉工作室时 -您的公司现在必须做同样的事情,否则代码将不再运行。

顺便说一句,linux 如何避免这种情况?虽然我说我更喜欢在它上进行开发并且我了解链接机制,但我维护任何应用程序的时间还不够长,无法遇到这种低级共享库版本控制问题。

最后,总结一下:这种新的清单方案是否可以使用预编译的二进制文件?如果是,我的错误是什么?如果不是,Microsoft 是否真的认为这会使应用程序开发变得更容易?

更新 - 一个更简洁的问题:Linux 如何避免使用 Manifest 文件?

【问题讨论】:

  • 我不明白为什么这个问题被否决了......似乎是经过深思熟虑的......虽然,微软因挫折而有点害羞,脾气很好,愿意推理。跨度>
  • 我猜要阅读的文字很多!如果问题更简洁,他可能会有更好的运气。
  • 当一个问题的标题问“x 是不是想作恶?”当人们反对它时,不要感到惊讶。 :)
  • 我想这是真的。仔细考虑这一点,这是一种相当迂回的作恶方式。 “永远不要归因于恶意......”。一个更简洁的问题:linux 如何避免需要清单文件。
  • Windows 在 VS2003 之前也不需要清单。它们是绕过 dll 地狱的障碍,主要是通过引入不同的问题 - dll 扩散:)

标签: c++ visual-studio dll manifest


【解决方案1】:

应用程序中的所有组件必须共享相同的运行时。如果不是这种情况,您会遇到奇怪的问题,例如在删除语句上断言。

这在所有平台上都是一样的。这不是微软发明的。

您可以通过了解运行时可能会反击的地方来解决这个“只有一个运行时”的问题。 这主要是在您在一个模块中分配内存并在另一个模块中释放它的情况下。

a.dll
    dllexport void* createBla() { return malloc( 100 ); }

b.dll
    void consumeBla() { void* p = createBla(); free( p ); }

当 a.dll 和 b.dll 链接到不同的 rumtime 时,这会崩溃,因为运行时函数实现了自己的堆。

您可以通过提供一个destroyBla 函数来轻松避免这个问题,该函数必须被调用以释放内存。

您可能会在运行时遇到一些问题,但大多数都可以通过包装这些构造来避免。

供参考:

  • 不要跨模块边界分配/释放内存/对象
  • 不要在你的 dll 接口中使用复杂的对象。 (例如 std::string, ...)
  • 不要跨 dll 边界使用复杂的 C++ 机制。 (类型信息、C++ 异常、...)
  • ...

但这不是清单的问题。

清单包含模块使用的运行时的版本信息,并被链接器嵌入到二进制文件 (exe/dll) 中。当一个应用程序被加载并且它的依赖被解析时,加载器查看嵌入在 exe 文件中的清单信息,并使用 WinSxS 文件夹中相应版本的运行时 dll。您不能只将运行时或其他模块复制到 WinSxS 文件夹。您必须安装 Microsoft 提供的运行时。当您在测试/最终用户机器上安装软件时,可以执行 Microsoft 提供的 MSI 包。

因此,在使用您的应用程序之前安装您的运行时,您将不会收到“缺少依赖项”错误。


(更新为“Linux如何避免使用Manifest文件”问题)

什么是清单文件?

引入清单文件以将消歧信息放置在现有可执行文件/动态链接库旁边或直接嵌入到此文件中。

这是通过指定启动应用程序/加载依赖项时要加载的 dll 的特定版本来完成的。

(您可以对清单文件执行其他几项操作,例如,可以将一些元数据放在此处)

为什么要这样做?

由于历史原因,版本不是 dll 名称的一部分。所以“comctl32.dll”在它的所有版本中都是这样命名的。 (所以Win2k下的comctl32与XP或Vista下的comctl32不同)。要指定您真正想要的版本(并已针对哪个版本进行测试),请将版本信息放在“appname.exe.manifest”文件中(或嵌入此文件/信息)。

为什么会这样?

许多程序将其 dll 安装到 systemrootdir 上的 system32 目录中。这样做是为了允许为所有相关应用程序轻松部署共享库的错误修复。在内存有限的时代,当多个应用程序使用相同的库时,共享库减少了内存占用。

这个概念被很多程序员滥用,当他们将所有的dll都安装到这个目录中时;有时会用较旧的共享库覆盖较新版本的共享库。有时库的行为会悄无声息地改变,从而导致相关应用程序崩溃。

这导致了“分发应用程序目录中的所有dll”的方法。

为什么会这样?

当出现错误时,必须更新分散在几个目录中的所有 dll。 (gdiplus.dll) 在其他情况下,这甚至是不可能的(Windows 组件)

清单方法

这种方法解决了上述所有问题。您可以将 dll 安装在程序员可能不会干预的中心位置。在这里可以更新 dll(通过更新 WinSxS 文件夹中的 dll)并且加载程序加载“正确”的 dll。 (版本匹配由 dll-loader 完成)。

为什么 Linux 没有这种机制?

我有几个猜测。 (这真的只是猜测……)

  • 大多数东西都是开源的,因此重新编译以修复错误对于目标受众来说不是问题
  • 因为只有一个“运行时”(gcc 运行时),所以运行时共享/库边界问题不会经常发生
  • 许多组件在接口级别使用 C,如果操作正确,这些问题就不会发生
  • 在大多数情况下,库的版本嵌入在其文件名中。
  • 大多数应用程序都静态绑定到它们的库,因此不会发生 dll-hell。
  • GCC 运行时保持非常 ABI 稳定,因此不会发生这些问题。

【讨论】:

  • 它们必须共享相同的运行时是什么意思?我想在 linux 中,所有共享对象都应该链接到相同的标准 c lib,我不知道如果它们不链接会发生什么。但是,清单文件是一个完整的 microsft“发明”,似乎有些不必要。我希望 cmets 更长
  • “不同的运行时”通常表示“调试或发布”。内存分配器不同,因此 a.dll 中的分配器,如果它们不同,将内存传递给 b.dll 将使您的应用程序崩溃。解决方案总是在同一个 dll 中分配和释放内存。那么你就没有问题了。
  • (为什么 Linux 没有这个机制?) Linux 使用 ELF 存储版本信息。如果您使用 2.2 版的库,那么您的程序会以 2.3 运行,但会以 2.1 或 3.0 退出。每个人都使用兼容的 C 运行时 (glibc/eglibc),自 2002 年以来只有向后兼容的更改。静态链接极为罕见。库供应商对更改非常小心,DLL 地狱由发行版维护者处理,而不是系统管理员。 C++ ABI 更改很痛苦,但已经结束。当 GTK 3.0 到来时,它将与 GTK 2.0 一起安装,因此两者的应用程序都可以正常运行。
【解决方案2】:

如果第三方 DLL 将分配内存并且需要释放它,那么您需要相同的运行时库。如果 DLL 有 allocate 和 deallocate 函数,就可以了。

如果第三方 DLL 使用 std 容器,例如 vector 等,您可能会遇到问题,因为对象的布局可能完全不同。

可以让事情正常工作,但有一些限制。我遇到了上面列出的两个问题。

【讨论】:

  • 为什么使用 std 容器会导致内存布局不同?
  • 代码接口使用stl,是的! VC6 std 容器的编码与 VS2008 std 不同,两者都与 STLPort 不同。 dll 的编译代码有一个定义,但是包含它们的标头的代码会以不同的方式编译该代码。相信我,我已经看到了。
【解决方案3】:

如果第三方 DLL 分配了您需要释放的内存,则 DLL 违反了交付预编译 DLL 的主要规则之一。正是因为这个原因。

如果 DLL 仅以二进制形式发布,则它还应发布与其链接的所有可再发行组件,并且其入口点应将调用者与任何潜在的运行时库版本问题(例如不同的分配器)隔离开来。如果他们遵守这些规则,那么你就不应该受苦。如果他们不这样做,那么您要么会感到痛苦和痛苦,要么您需要向第三方作者投诉。

【讨论】:

    【解决方案4】:

    我最终得出的结论是,让这个工作(除了静态链接所有内容)的唯一方法是所有第三方库必须使用相同版本的 Visual Studios 编译 - 即不要使用预编译的 dll - 下载源,构建一个新的 dll 并改用它。

    或者(以及我们必须在我工作的地方使用的解决方案)是,如果您需要使用的所有第三方库都使用相同的编译器版本构建(或作为构建可用),您可以“只”使用那个版本。例如,“必须”使用 VC6 可能会很麻烦,但是如果有一个您必须使用的库并且它的源不可用,而这就是它的来源,那么您的选择就会很遗憾地受到限制。

    ...据我所知。 :)

    (我的工作范围不在 Windows使用相同的编译器构建。谢天谢地,所有供应商都倾向于保持最新,因为他们多年来一直在提供这种支持。)

    【讨论】:

    • 为什么静态链接是安全的?假设第 3 方 DLL 静态链接到 vc8,而您的 DLL 静态链接到 vc9,然后您尝试删除 DLL 中在第 3 方 DLL 中创建的对象,会发生什么?
    • @lzprgmr:不是,而且 - ka-blammo!
    • 在这种情况下,静态链接是安全的,因为您知道要链接的所有库的依赖关系是什么。换句话说,您不会尝试将两个依赖于不同运行时的库链接在一起。
    猜你喜欢
    • 2019-10-23
    • 1970-01-01
    • 2019-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多