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

时间:2012-03-13 16:37:54

标签: c++ c linux dll linker

我参与了有关Linux中库的一些争论,并想确认一些事情。

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

  1. 静态库(.a文件):在链接时,将整个库的副本放入最终应用程序,以便库中的函数始终可供调用应用程序使用
  2. 共享对象(.so文件):在链接时,只需通过相应的标头(.h)文件对其API进行验证。直到运行时才需要实际使用该库。
  3. 静态库的明显优势在于它们允许整个应用程序自包含,而动态库的好处是可以替换“.so”文件(即:如果需要更新到期) (不需要重新编译基础应用程序)。

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

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

    提前感谢大家的帮助。

    更新


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

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

      DLL”和“shared library”之间的区别只是我当时工作的公司(懒惰,不准确)的口语(Windows开发人员被迫转向Linux开发,并且术语卡住了),坚持上述说明。

      此外,在“共享档案”的情况下,库名后的“S”字尾只是该公司使用的惯例,而不是整个行业。

4 个答案:

答案 0 :(得分:168)

静态库(.a)是一个可以直接链接到链接器生成的最终可执行文件的库,它包含在其中,并且不需要将库放入将部署可执行文件的系统。

共享库(.so)是一个链接但未嵌入最终可执行文件的库,因此将在可执行文件启动时加载,并且需要存在于系统中可执行文件已部署。

Windows上的动态链接库(.dll)就像linux上的共享库(.so),但是与操作系统相关的两个实现之间存在一些差异(Windows vs Linux ):

DLL 可以定义两种功能:export和internal。导出的函数旨在由其他模块调用,也可以在定义它们的DLL中调用。内部函数通常仅用于在定义它们的DLL中调用。

Linux上的 SO 库不需要特殊的导出语句来指示可导出的符号,因为所有符号都可用于询问过程。

答案 1 :(得分:83)

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

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

静态库只是直接来自编译器的对象文件的捆绑包,就像您在项目编译过程中自己构建的一样,因此它们以完全相同的方式被拉入并提供给链接器,并且未使用的位以完全相同的方式丢弃。

答案 2 :(得分:29)

我可以详细说明Windows中的DLL的细节,以帮助我在* NIX-land的朋友们澄清这些谜......

DLL就像共享对象文件。两者都是图像,准备通过相应OS的程序加载器加载到存储器中。这些图像伴随着各种元数据,以帮助链接器和加载器进行必要的关联并使用代码库。

Windows DLL具有导出表。导出可以是名称,也可以是表位(数字)。后一种方法被认为是“老派”并且更加脆弱 - 重建DLL并改变表中函数的位置将以灾难告终,而如果按名称链接入口点则没有实际问题。所以,请忘记这个问题,但是如果你使用“恐龙”代码(如第三方供应商库),请注意它。

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的绑定元数据。

答案 3 :(得分:4)

您是正确的,因为静态文件在链接时被复制到应用程序,并且该共享文件仅在链接时验证并在运行时加载。

dlopen调用不仅适用于共享对象,如果应用程序希望在运行时代表它,则在应用程序启动时自动加载共享对象。 DLLS和.so是一回事。 dlopen的存在是为了为进程添加更细粒度的动态加载能力。您不必自己使用dlopen打开/使用DLL,这在应用程序启动时也会发生。