使用共享库与单个可执行文件

时间:2009-09-16 12:53:13

标签: c++ c shared-libraries

我的同事声称我们应该将C ++应用程序(C ++,Linux)分解为共享库,以提高代码模块性,可测试性和重用性。

从我的观点来看,这是一个负担,因为我们编写的代码不需要在同一台机器上的应用程序之间共享,也不需要动态加载或卸载,我们可以简单地链接单个可执行应用程序。

此外,使用C函数接口IMHO包装C ++类会使它变得更加丑陋。

我还认为在客户的网站上远程升级单文件应用程序会更容易。

如果不需要在应用程序之间共享二进制代码而不加载动态代码,是否应该使用动态库?

10 个答案:

答案 0 :(得分:8)

我会说将代码拆分为共享库以改进而不考虑任何直接的目标是一个流行语出没的开发环境的标志。最好编写一些可以在某些时候轻松拆分的代码。

但为什么你需要将C ++类包装到C函数接口中,除了可能用于创建对象?

此外,拆分为共享库 here 听起来像是一种解释性语言思维模式。在编译语言中,您尽量不要推迟到运行时在编译时可以执行的操作。不必要的动态链接就是这种情况。

答案 1 :(得分:5)

实施共享库可确保库不具有循环依赖性。与最终应用程序链接之前没有任何链接相比,使用共享库通常会导致更快的链接和链接错误。如果您想避免向客户发送多个文件,可以考虑在开发环境中动态链接应用程序,并在创建发布版本时静态链接。

编辑:我真的没有理由说明为什么需要使用C接口包装C ++类 - 这是在幕后完成的。在Linux上,您可以使用共享库而无需任何特殊处理。但是,在Windows上,您需要___ declspec(导出)和___ declspec(导入)。

答案 2 :(得分:4)

即使没有,也可以改善重用?听起来不是一个强有力的论据。

代码的模块化和可测试性不需要依赖于最终部署的单位。我希望链接是一个迟到的决定。

如果真的有一件可交付成果并且从未预料到会有任何改变,那么听起来就像过度杀伤和不必要的复杂性一样。

答案 3 :(得分:2)

简答:不。

更长的答案:动态库不会添加任何东西来添加到测试,模块化或重用中,这在单片应用程序中无法轻松完成。关于我能想到的唯一好处是,可能会强制在没有自己的纪律的团队中创建API。

图书馆(动态或其他)没有什么神奇之处。如果您拥有构建应用程序的所有代码和各种库,您可以在一个可执行文件中轻松编译它们。

总的来说,我们发现必须处理动态库的成本是不值得的,除非有迫切需要(多个应用程序中的库,需要更新大量应用程序而无需重新编译,使用户能够向应用程序添加功能。)

答案 4 :(得分:2)

解析同事的论点

如果他认为将代码拆分为共享库将提高代码模块性,可测试性和重用性,那么我想这意味着他认为您的代码存在一些问题,而强制执行“共享库”架构会纠正它

模块化?

您的代码必须具有不期望的相互依赖性,而“库代码”和“使用库代码的代码”之间的分离更加清晰。

现在,这也可以通过静态库来实现。

测试?

您的代码可以更好地进行测试,也许可以为每个单独的共享库构建单元测试,并在每次编译时自动执行。

现在,这也可以通过静态库来实现。

重用代码?

您的同事希望重用一些未公开的代码,因为这些代码隐藏在整体应用程序的源代码中。

结论

静态库仍然可以实现第1点和第2点。 3将强制使用共享库。

现在,如果你有多个深度的库链接(我正在考虑将两个静态库链接在一起,这两个静态库被编译链接其他库),这可能很复杂。在Windows上,这会导致链接错误,因为某些函数(通常是静态链接时的C / C ++运行时函数)被多次引用,并且编译器无法选择要调用的函数。我不知道这在Linux上是如何工作的,但我想这也可能发生。

解析自己的论点

你自己的论点有些偏颇:

共享库的编译/链接负担?

与编译和链接到静态库相比,编译和链接到共享库的负担是不存在的。所以这个论点没有价值。

动态加载/卸载?

在非常有限的用例中,动态加载/卸载共享库可能是一个问题。在正常情况下,操作系统会在需要时加载/卸载磁带库而无需您的干预,无论如何,您的性能问题都存在于其他地方。

用C接口公开C ++代码?

至于为C ++代码使用C函数接口,我无法理解:您已经将静态库与C ++接口链接在一起。链接共享库也不例外。

如果您有不同的编译器来生成应用程序的每个库,那么您会遇到问题,但事实并非如此,因为您已经静态链接了库。

单个文件二进制文件更容易吗?

你是对的。

在Windows上,差异可以忽略不计,但是,仍然存在DLL Hell的问题,如果您将版本添加到库名称或使用Windows XP,它就会消失。

在Linux上,除了上面的Windows问题之外,你还有一个事实是,默认情况下,共享库需要在一些系统默认目录中才能使用,所以你必须在安装时将它们复制到那里(可能是一种痛苦...)或改变一些默认的环境设置(也可能是一种痛苦......)

结论:谁是对的?

现在,你的问题不是“我的同事是对的吗?”。他是。像你一样。

你的问题是:

  1. 你真正想要实现的目标是什么?
  2. 这项任务所需的工作是否值得?
  3. 第一个问题非常重要,因为在我看来,你的论点和同事的论点偏向于导致对你们每个人看起来更自然的结论。

    换句话说:你们每个人都已经知道理想的解决方案应该是什么(根据每个观点),你们每个人都会堆积参数来达到这个解决方案。

    没有办法回答这个隐藏的问题......

    ^ _ ^

答案 5 :(得分:1)

共享库带来了令人头疼的问题,但我认为共享库是正确的方法。我想说在大多数情况下,您应该能够将您的应用程序的某些部分模块化,并在您的业务的其他地方重复使用。此外,根据此单片可执行文件的大小,可能更容易上传一组更新的库而不是一个大文件。

IMO,库通常会带来更好的代码,更可测试的代码,并允许以更有效的方式创建未来的项目,因为您不会重新发明轮子。

简而言之,我同意你的同事。

答案 6 :(得分:1)

进行简单的成本/收益分析 - 您真的需要模块化,可测试性和重用吗?您是否有时间重构代码以获取这些功能?最重要的是,如果你进行重构,你获得的好处是否可以证明执行重构所需的时间是合理的?

除非您现在遇到测试问题,否则我建议您按原样退出应用。模块化很棒,但Linux有自己的版本“DLL地狱”(见ldconfig),你已经表明重用不是必需的。

答案 7 :(得分:1)

在Linux(和Windows)上,您可以使用C ++创建共享库,而不必使用C函数导出来加载它。

也就是说,将classA.cpp构建到classA.so中,然后将classB.cpp构建到classB(.exe)中,该类链接到classA.so。你所做的只是将你的应用程序分成多个二进制文件。这确实具有以下优点:编译速度更快,更易于管理,您可以编写仅加载库代码进行测试的应用程序。

一切仍然是C ++,一切都是链接,但你的.so与静态链接的应用程序是分开的。

现在,如果你想在运行时加载一个不同的对象(也就是说,你不知道在运行时加载哪一个),那么你需要创建一个带有c-exports的共享对象,但你也将是手动加载这些功能;您将无法使用链接器为您执行此操作。

答案 8 :(得分:1)

如果你问的问题并且答案不明显,那就留在原地。如果你还没有达到构建整体应用程序需要太长时间的程度,或者你的团队一起工作太麻烦,那么就没有令人信服的理由搬到图书馆。您可以构建一个适用于应用程序文件的测试框架,如果您想要现有的,或者您可以简单地创建另一个使用相同文件的项目,但是附加测试API并使用该文件构建库。

出于运输目的,如果您要构建库并发送一个大型可执行文件,您可以始终静态链接到它们。

如果模块化有助于开发,即你总是与其他开发人员在文件修改方面交锋,那么库可能有所帮助,但这也不能保证。使用良好的面向对象的代码设计无论如何都会有所帮助。

并且没有理由使用C-callable接口来包装任何函数来创建库,除非您希望它可以从C调用。

答案 9 :(得分:0)

首先,让我免除您问题中的错误假设。你不需要用 C 接口包装你的 C++。

现在让我们来看看案例。

缺点:

  • 将您的应用程序分解为模块是很有用的。
  • 您(很可能)会发现相互依赖,使其更加有效。
  • 您必须确保库在运行时进入正确的位置。与 Windows 上的 exe 相同的目录,以及 Linux/*nix 上的 LD_LIBRARY_PATH 中的某处。

非问题:

  • 性能。加载后,调用 dll 中的函数应该是零性能损失。它甚至可以提高某些系统上的内存效率,因为加载程序可以在不需要时卸载/写入时复制等。

好处:

  • 模块化。如果功能区域完全分开,则很容易在多个开发人员之间拆分工作。
  • 测试。可以单独测试(通过单元测试)模块,而无需考虑其他代码区域。
  • 认知负荷。在编写应用程序时,很难保持整个应用程序的心理图景。两年后当你回到它升级时就更难了。想象一下,如果一名新开发者使用该应用程序会是什么样子。

结论

  • 我喜欢模块,但这取决于您的情况。如果您距离发货还有一周的时间,并且大多数事情都正常运行,并且您完全有可能修复少数剩余的小错误 - 为什么现在要改变。
  • 另一方面,如果您正在努力解决难以处理的错误、代码在无关的事情发生变化时神秘地中断,或者处于开发的中期并且落后于计划,那么将其分解为模块可能是一个很好的选择降低复杂性的想法。