共享对象 (.so)、静态库 (.a) 和 DLL (.so) 之间的区别?

新手上路,请多包涵

我参与了一些关于 Linux 库的辩论,并想确认一些事情。

据我了解(如果我错了,请纠正我,稍后我会编辑我的帖子),在构建应用程序时有两种使用库的方法:

  1. 静态库(.a 文件):在链接时,将整个库的副本放入最终应用程序中,以便库中的函数始终可供调用应用程序使用
  2. 共享对象(.so 文件):在链接时,对象只是通过相应的头文件 (.h) 对其 API 进行验证。该库直到运行时才真正使用,在需要它的地方。

静态库的明显优势是它们允许整个应用程序自包含,而动态库的好处是可以替换“.so”文件(即:如果由于安全原因需要更新它bug) 无需重新编译基础应用程序。

我听说有些人区分共享对象和动态链接库(DLL),即使它们都是“.so”文件。在 Linux 或任何其他符合 POSIX 的操作系统(即:MINIX、UNIX、QNX 等)上进行 C/C++ 开发时,共享对象和 DLL 之间有什么区别吗?有人告诉我,一个关键的区别(到目前为止)是共享对象只在运行时使用,而 DLL 必须首先使用应用程序中的 dlopen() 调用打开。

最后,我还听到一些开发人员提到“共享档案”,据我了解,它本身也是静态库,但从未被应用程序直接使用。相反,其他静态库将链接到“共享档案”,以将一些(但不是全部)功能/资源从共享档案中提取到正在构建的静态库中。

预先感谢大家的帮助。

更新


在向我提供这些术语的上下文中,实际上是一组必须学习 Linux 的 Windows 开发人员使用的错误术语。我试图纠正它们,但(不正确的)语言规范卡住了。

  1. 共享对象:程序启动时自动链接到程序中的库,并作为独立文件存在。该库在编译时包含在链接列表中(即: LDOPTS+=-lmylib 用于名为 mylib.so 的库文件)。 该库必须在编译时以及应用程序启动时存在。
  2. 静态库:在构建时合并到实际程序本身的库,用于单个(较大)应用程序,包含应用程序代码和在构建程序时自动链接到程序中的库代码,以及包含两者的最终二进制文件主程序和库本身作为一个独立的二进制文件存在。该库在编译时包含在链接列表中(即: LDOPTS+=-lmylib 用于名为 mylib.a 的库文件)。 该库必须在编译时存在。
  3. DLL:本质上与共享对象相同,但不是在编译时包含在链接列表中,而是通过 dlopen() / dlsym() 命令加载库,因此库不会需要在构建时出现才能编译程序。 此外,在应用程序启动或编译时不需要(必然)存在该库,因为它仅在 dlopen / dlsym 调用时需要。
  4. 共享存档:本质上与静态库相同,但使用“export-shared”和“ -fPIC ”标志编译。该库在编译时包含在链接列表中(即: LDOPTS+=-lmylibS 对于名为 mylibS.a 的库文件)。两者之间的区别在于,如果共享对象或 DLL 想要将共享存档静态链接到它自己的代码中并且能够使共享对象中的函数对其他程序可用,而不仅仅是使用它们,则需要这个附加标志DLL 内部。这在有人为您提供静态库并且您希望将其重新打包为 SO 的情况下很有用。 该库必须在编译时存在。

额外更新

DLL ”和“ shared library ”之间的区别只是我当时工作的公司的一种(懒惰,不准确的)俗语(Windows开发人员被迫转向Linux开发, 和术语卡住), 遵守上面提到的描述。

此外,在“共享档案”的情况下,库名称后面的尾随“ S ”文字只是该公司使用的惯例,而不是一般行业中的惯例。

原文由 Cloud 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.2k
2 个回答

我一直认为 DLL 和共享对象只是同一事物的不同术语 - Windows 称它们为 DLL,而在 UNIX 系统上它们是共享对象,通用术语 - 动态链接库 - 涵盖两者(甚至函数在 UNIX 上打开一个 .so 被称为 dlopen() 在“动态库”之后)。

它们确实只在应用程序启动时链接,但是您对头文件进行验证的概念是不正确的。头文件定义了编译使用库的代码所需的原型,但在链接时,链接器会查看库本身以确保它需要的函数确实存在。链接器必须在链接时在某处找到函数体,否则会引发错误。它还在运行时执行此操作,因为正如您正确指出的那样,库本身可能在程序编译后发生了变化。这就是为什么 ABI 稳定性在平台库中如此重要的原因,因为 ABI 的变化会破坏针对旧版本编译的现有程序。

静态库只是直接从编译器中取出的目标文件包,就像您在项目编译中自己构建的那些文件一样,因此它们以完全相同的方式被拉入并馈送到链接器,而未使用的位是以完全相同的方式下降。

原文由 Matthew Walton 发布,翻译遵循 CC BY-SA 3.0 许可协议

我可以详细说明 Windows 中 DLL 的详细信息,以帮助我在 *NIX 领域的朋友澄清这些谜团……

DLL 类似于共享对象文件。两者都是图像,准备好由各自操作系统的程序加载器加载到内存中。这些图像伴随着各种元数据,以帮助链接器和加载器进行必要的关联并使用代码库。

Windows DLL 有一个导出表。导出可以按名称或按表位置(数字)。后一种方法被认为是“老派”并且更加脆弱——重建 DLL 和更改函数在表中的位置将以灾难告终,而如果通过名称链接入口点则没有真正的问题。因此,请忘记这是一个问题,但请注意,如果您使用“恐龙”代码(例如 3rd-party 供应商库),它就在那里。

Windows DLL 是通过编译和链接构建的,就像您对 EXE(可执行应用程序)所做的那样,但 DLL 不是独立的,就像 SO 旨在由应用程序使用一样,通过动态加载或通过链接时绑定(对 SO 的引用嵌入在应用程序二进制文件的元数据中,并且 OS 程序加载器将自动加载引用的 SO)。 DLL 可以引用其他 DLL,就像 SO 可以引用其他 SO一样。

在 Windows 中,DLL 将只提供特定的入口点。这些被称为“出口”。开发人员可以使用特殊的编译器关键字使符号外部可见(对其他链接器和动态加载器),或者可以在模块定义文件中列出导出,该文件在链接时使用 DLL 本身正在创建。现代的做法是用关键字装饰函数定义以导出符号名称。还可以创建带有关键字的头文件,这些关键字将声明该符号为要从当前编译单元之外的 DLL 导入的符号。查找关键字 __declspec(dllexport) 和 __declspec(dllimport) 以获取更多信息。

DLL 的一个有趣特性是它们可以声明一个标准的“加载/卸载”处理函数。每当加载或卸载 DLL 时,DLL 都可以执行一些初始化或清理,视情况而定。这很好地映射为将 DLL 作为面向对象的资源管理器,例如设备驱动程序或共享对象接口。

当开发人员想要使用已构建的 DLL 时,她必须引用 DLL 开发人员在创建 DLL 时创建的“导出库”(*.LIB),或者必须在运行时显式加载 DLL 并请求通过 LoadLibrary() 和 GetProcAddress() 机制按名称命名的入口点地址。大多数时候,链接到一个 LIB 文件(它只包含 DLL 的导出入口点的链接器元数据)是使用 DLL 的方式。动态加载通常保留用于在程序行为中实现“多态性”或“运行时可配置性”(访问附加组件或后来定义的功能,即“插件”)。

Windows 的做事方式有时会引起一些混乱;系统使用 .LIB 扩展名来引用普通静态库(存档,如 POSIX *.a 文件)和在链接时将应用程序绑定到 DLL 所需的“导出存根”库。因此,应始终查看 *.LIB 文件是否具有同名的 *.DLL 文件;如果不是,那么 *.LIB 文件很有可能是一个静态库存档,而不是导出 DLL 的绑定元数据。

原文由 JoGusto 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题