我编写了一些代码,利用开源库来完成一些繁重的工作。这项工作是在linux中完成的,包括单元测试和cmake,以帮助将其移植到Windows。需要让它在两个平台上运行。
我喜欢Linux,我喜欢cmake,我喜欢自动生成可视工作室文件。就像现在一样,在Windows上,一切都将编译,它将链接,它将生成测试可执行文件。
然而,为了达到这一点,我必须与Windows斗争几天,学习所有关于清单文件和可再发行组件包。
据我所知:
借助VS 2005,微软创建了Side By Side dlls。这样做的动机是,之前,多个应用程序将安装相同dll的不同版本,导致先前安装和正在运行的应用程序崩溃(即“Dll Hell”)。并排dll修复此问题,因为现在每个可执行文件/ dll附加一个“清单文件”,用于指定应执行的版本。
这一切都很好。应用程序不应再神秘崩溃。然而...
微软似乎每次发布Visual Studio都会发布一套新的系统dll。另外,正如我之前提到的,我是一名试图链接到第三方库的开发人员。通常,这些东西作为“预编译的dll”分发。现在,当使用一个版本的可视工作室编译的预编译的dll链接到使用另一个版本的可视工作室的应用程序时会发生什么?
从我在互联网上看到的内容发生了不好的事情。幸运的是,我从来没有这么做 - 在运行可执行文件时我一直遇到“MSVCR80.dll not found”问题,因此我开始涉足整个清单问题。
我终于得出结论,让它工作的唯一方法(除了静态链接一切)是所有第三方库必须使用相同版本的Visual Studios编译 - 即不使用预编译的dll - 下载来源,构建一个新的dll并使用它。
这实际上是真的吗?我错过了什么吗?
此外,如果情况确实如此,那么我不禁会认为微软出于各种原因故意这样做。
如果您碰巧为使用第三方专有库的软件公司工作,那么它不仅会破坏所有预编译的二进制文件,使得使用预编译的二进制文件变得更加困难,那么每当它们升级到最新版本的可视化工作室时 - 你的公司现在必须做同样的事情,否则代码将不再运行。
顺便说一下,linux如何避免这种情况?虽然我说我喜欢开发它并且我理解链接的机制,但我没有维护任何应用程序足够长的时间来遇到这种低级别的共享库版本问题。
最后,总结一下:是否可以在这个新的清单方案中使用预编译的二进制文件?如果是的话,我的错误是什么?如果不是,那么微软是否真的认为这会让应用程序开发变得更容易?
更新 - 更简洁的问题: Linux如何避免使用清单文件?
答案 0 :(得分:27)
应用程序中的所有组件必须共享相同的运行时。如果不是这种情况,则会遇到奇怪的问题,比如在删除语句上断言。
在所有平台上都是一样的。这不是微软发明的东西。
您可以通过了解运行时可能会回来的位置来解决这个“仅一个运行时”问题。 这主要是在一个模块中分配内存并在另一个模块中释放内存的情况。
a.dll
dllexport void* createBla() { return malloc( 100 ); }
b.dll
void consumeBla() { void* p = createBla(); free( p ); }
当a.dll和b.dll链接到不同的浏览时间时,会崩溃,因为运行时函数会实现自己的堆。
您可以通过提供destroyBla函数轻松避免此问题,必须调用该函数以释放内存。
有几点可能会遇到运行时问题,但大多数都可以通过包装这些结构来避免。
供参考:
但这不是清单的问题。
清单包含模块使用的运行时的版本信息,并由链接器嵌入到二进制文件(exe / dll)中。加载应用程序并解决其依赖关系时,加载程序会查看exe文件中嵌入的清单信息,并使用WinSxS文件夹中的运行时dll的相应版本。您不能只将运行时或其他模块复制到WinSxS文件夹。您必须安装Microsoft提供的运行时。 Microsoft提供的MSI软件包可以在测试/最终用户计算机上安装软件时执行。
因此,在使用应用程序之前安装运行时,不会出现“缺少依赖性”错误。
(更新到“Linux如何避免使用清单文件”问题)
什么是清单文件?
引入了清单文件,用于在现有的可执行文件/动态链接库旁边放置消歧信息,或者直接嵌入到此文件中。
这是通过指定启动app /加载依赖项时要加载的dll的特定版本来完成的。
(您可以使用清单文件执行其他一些操作,例如可以在此处放置一些元数据)
为什么要这样做?
由于历史原因,该版本不是dll名称的一部分。所以“comctl32.dll”在它的所有版本中都以这种方式命名。 (因此Win2k下的comctl32与XP或Vista中的不同)。要指定您真正想要的版本(并已经过测试),请将版本信息放在“appname.exe.manifest”文件中(或嵌入此文件/信息)。
为什么这样做?
许多程序将其dll安装到systemrootdir上的system32目录中。这样做是为了允许为所有相关应用程序轻松部署共享库的错误修正。在内存有限的时代,当多个应用程序使用相同的库时,共享库减少了内存占用。
当他们将所有dll安装到此目录中时,许多程序员都滥用了这个概念;有时用旧版本覆盖较新版本的共享库。有时库的行为会默默地改变,因此依赖的应用程序崩溃了。
这导致了“在应用程序目录中分发所有dll”的方法。
为什么这么糟糕?
当出现错误时,必须更新分散在多个目录中的所有dll。 (gdiplus.dll)在其他情况下甚至不可能(Windows组件)
清单方法
这种方法解决了上述所有问题。您可以将dll安装在中央位置,程序员可能不会干扰它。这里可以更新dll(通过更新WinSxS文件夹中的dll),加载器加载'right'dll。 (版本匹配由dll-loader完成。)
为什么Linux没有这种机制?
我有几个猜测。 (这真的只是猜测......)
答案 1 :(得分:3)
如果第三方DLL将分配内存并且您需要释放它,则需要相同的运行时库。如果DLL具有分配和解除分配功能,则可以正常。
第三方DLL使用std
容器,例如vector
等,您可能会遇到问题,因为对象的布局可能完全不同。
可以让事情发挥作用,但也存在一些局限性。我遇到了上面列出的两个问题。
答案 2 :(得分:3)
如果第三方DLL分配您需要释放的内存,那么DLL已经破坏了运送预编译DLL的主要规则之一。正是出于这个原因。
如果DLL仅以二进制形式发布,那么它还应该发送它链接的所有可再发行组件,其入口点应该将调用者与任何潜在的运行时库版本问题隔离开来,例如不同的分配器。如果他们遵守这些规则那么你就不应该受苦。如果他们不这样做,你要么会有痛苦,要么你需要向第三方作者投诉。
答案 3 :(得分:1)
我终于得出结论,让它工作的唯一方法(除了静态链接一切)是所有第三方库必须使用相同版本的Visual Studios编译 - 即不使用预编译的dll - 下载来源,构建一个新的dll并使用它。
或者(我们必须在我工作的地方使用的解决方案)是,如果您需要使用的第三方库都是使用相同的编译器版本构建(或可用于构建),您可以“只”使用那个版本。例如,它可能是“必须”使用VC6的拖累,但是如果有一个库你必须使用它的源不可用而且它是如何来的,否则你的选择可能会受到限制。
......据我所知。 :)
(我的工作不在Windows中,尽管我们不时从用户角度对Windows上的DLL进行操作,但是我们必须使用特定版本的编译器并获得所有版本的第三方软件使用相同的编译器构建。值得庆幸的是,所有供应商都倾向于保持最新状态,因为他们多年来一直在做这种支持。)