我写了一些代码,利用开源库来完成一些繁重的工作.这项工作是在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如何避免使用Manifest文件?
应用程序中的所有组件必须共享相同的运行时.如果不是这种情况,则会遇到奇怪的问题,比如在delete语句上断言.
在所有平台上都是一样的.这不是微软发明的东西.
您可以通过了解运行时可能会退回的位置来解决这个"仅一个运行时"问题.这主要是在一个模块中分配内存并在另一个模块中释放内存的情况.
a.dll dllexport void* createBla() { return malloc( 100 ); } b.dll void consumeBla() { void* p = createBla(); free( p ); }
当a.dll和b.dll链接到不同的朗姆酒时,这会崩溃,因为运行时函数实现了自己的堆.
您可以通过提供destroyBla函数轻松避免此问题,必须调用该函数以释放内存.
有几点可能会遇到运行时问题,但大多数都可以通过包装这些结构来避免.
以供参考 :
不跨模块边界分配/释放内存/对象
不要在dll界面中使用复杂对象.(例如std :: string,...)
不要跨dll边界使用复杂的C++机制.(typeinfo,C++异常,...)
...
但这不是清单的问题.
清单包含模块使用的运行时的版本信息,并由链接器嵌入到二进制文件(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没有这个机制?
我有几个猜测.(这真的只是猜测...)
大多数东西都是开源的,因此重新编译bug修复对于目标受众来说不是问题
因为只有一个"运行时"(gcc运行时),运行时共享/库边界的问题不会经常发生
许多组件在接口级别使用C,如果正确完成,这些问题就不会发生
在大多数情况下,库的版本嵌入在其文件名中.
大多数应用程序静态绑定到它们的库,因此不会发生dll-hell.
GCC运行时保持非常ABI稳定,因此不会发生这些问题.