25

C++(和 C,尽管在那儿不太重要)标准规定程序中的所有翻译单元都需要具有相同的定义;这包括编译器开关之类的东西。例如,在 MSVC++ 上,必须在所有翻译单元中链接到正确版本的 C 运行时库( /MTvs /MDvs /MTdvs )。/MDd

但是,我们想使用一些第三方依赖项,其中有几件事:

  • 他们都使用不同的构建系统(有一个 autoconf,有一个 cmake,还有一个似乎有它自己的手卷的东西..)
  • 构建系统并非都在其配置中公开这些类型的开关,并且硬编码的开关在不同的系统中设置不同。(例如,一个图书馆力量/MDand /MDd,而另一个力量/MTand /MTd

我们不确定处理这类事情的最佳方法是什么。我们讨论了以下选项:

  • 围绕任何第三方依赖项构建我们自己的构建系统。
    • PRO:我们知道事情会匹配
    • PRO:我们知道我们可以以正确的方式进行跨平台支持
    • CON:我们并不确切知道每个第三方构建系统是如何工作的
    • 缺点:很多很多的工作
    • CON:如果第三方依赖发生变化则中断
  • 尝试使用第三方构建系统,并尝试修改它们以做我们需要的事情。
    • PRO:似乎工作量更少
    • CON:我们可能会破坏第三方系统
    • CON:如果第三方依赖发生变化则中断
    • CON:迫使我们自己的构建变得非常复杂

我们不知道该怎么做;我们无法相信只有我们一个人遇到这些问题。我们应该做上面的选择之一,还是我没有想到的第三种选择?

4

4 回答 4

7

严格来说,您并不需要所有库都链接到同一个运行时。假设它们是 DLL,那么只有当它们通过 DLL 边界传递 CRT 数据结构时才会出现问题。使用一个运行时在 DLL 中创建一个FILE*DLL,并从链接到另一个运行时的 DLL 中使用它是灾难的根源。使用一个运行时调用mallocnew从 DLL 调用,和free/delete从另一个运行时会导致很多有趣的问题。

但只要所有与 CRT 相关的内容都保存在 DLL 内部,您就可以安全地链接到使用另一个 CRT 的 DLL。这也意味着您的调试版本可以使用与发布 CRT 链接的库。同样,只要您不尝试跨库混合 CRT 数据结构。

另请注意,大多数编译器标志不会影响 ABI,因此它们可以在库(或文件)之间安全地不同。改变 ABI 的通常是显而易见的,例如强制打包或堆栈对齐。

所以我们所做的基本上是这样的:

  • 只要有可能,我们都会自己建造图书馆。在大多数情况下,这意味着我们可以通过相当简单的方式控制它应该链接到哪个运行时。这可能需要对构建系统进行少量调整,但通常只需指定是构建调试版本还是发布版本,大多数构建系统都有选择。(如果库使用 Visual Studio 作为其构建系统,我们会尽可能将其升级到 2010)
  • 我们使用图书馆自己的构建系统。其他任何事情都只是痛苦的秘诀。您希望能够相信构建系统实际上与库源代码保持同步,而确保这一点的最简单方法是使用源代码附带的构建系统。
  • 如果构建库不可行,我们只需按原样使用它,然后我们只需分发它链接的任何运行时。我们试图避免这种情况,但是当没有其他选项可用时这样做是足够安全的。
  • 当我们建立一个库时,我们会在我们的内部 wiki 上准确记录它是如何完成的。如果我们必须升级到更新版本的库或重建它,这将是一个巨大的帮助。

我们目前依赖于三个不同的 VS 运行时(2005、2008 和 2010),处理起来很痛苦,但它确实有效。对于其中一两个,我们总是使用发布版本,即使是在我们自己的代码的调试版本中。

维护起来有点混乱,但它确实有效。而且我真的找不到更好的方法来做到这一点。

当然,你应该尽量减少你有多少第三方依赖,当你选择第三方库时,它的构建系统绝对应该是一个考虑因素。有些人其他人更痛苦。

但最终,您可能最终不得不使用一些没有良好构建系统的库,或者构建起来非常痛苦以至于不值得努力,或者您可以使用的库。 t 创建库的调试版本。然后拿走你能得到的。让它使用它喜欢使用的任何运行时。

于 2012-06-01T18:37:06.527 回答
2

我假设您故意不提及任何特定的库?

无论如何,你应该问问自己,你是否真的需要在你的构建系统中使用这个 3rd 方代码。

我们使用的第 3 方库编译一次(使用它们各自的构建脚本)并检查正确的 VC 开关,然后将 DLL 或 LIB 文件检入使用该库的应用程序的源代码控制中。

因此,第 3 方库的编译是我们在每个第 3 方版本中只做一次的事情,并且我们不会因为构建第 3 方库的复杂性而给构建系统带来负担。

我想这两种方法都有有效的论据,也许您可​​以在问题中提供一些详细信息,为什么您需要/想要在构建系统中拥有 3rd 方库。

于 2012-06-01T18:13:44.910 回答
2

你是对的——你不是唯一一个遇到这些问题的人!

根据我们的经验,松散的依赖关系(我猜是手动复制文件的一种奇特方式)和修改第 3 方构建系统是最有效的——尤其是在为 Windows 构建时。

我们发现一些有用的东西:-

记录您必须为特定版本应用的更改(我们使用 wiki 页面),并包括此处所需的任何步骤/依赖项(例如,构建 OpenSSL 所需的 Perl 解释器)并在使用构建之前运行任何/所有包含的测试。

我们发现重命名输出库以便始终根据 ABI 标记它们在这里非常有用,而不是使用第 3 方生成的名称。

所以第 3 方 C++ 依赖 X 最终在我们的目录结构中(提交给 svn)为

X/[version_number]/include(使用 lib 的头文件)

X/[version_number]/lib/Windows(手动构建、测试和重命名的库)

例如

X-vc100.lib X-vc100-gd.lib

ETC

(我们实际上从提升名称http://www.boost.org/doc/libs/1_49_0/more/getting_started/windows.html#library-naming复制了我们的命名,因为它们看起来完全合理)

然后,您的构建系统可以选择特定的第 3 方依赖项(对于 VS,我们使用继承的属性表,其中所有依赖项都命名为用户宏,因此只需将 $(x_inc) 添加到项目的包含目录和 $(x_lib ) 添加到库中 - 然后这些宏选择该特定项目所需的版本和 abi。

于 2012-06-01T18:28:30.200 回答
1

没有明确的答案,这取决于第三方代码接口是如何设计的。如果接口是紧耦合的,例如共享非透明数据类型,最好使用您自己的构建和选项重新构建它们。您必须分析接口并确定如何集成它们。另一方面,如果接口简单且易于解耦,则可以将它们构建为dll并按需调用。当然,您将有不同版本的 C 库加载到应用程序中,它们都有不同的 io 缓冲区、内存管理等实例。

如果您有可用的源代码,更好的选择是投入更多时间将它们集成到您自己的构建中。

于 2012-06-01T19:07:38.457 回答