首页 文章

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

提问于
浏览
219

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

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

  • 静态库(.a文件):在链接时,将整个库的副本放入最终应用程序中,以便库中的函数始终可用于调用应用程序

  • 共享对象(.so文件):在链接时,对象仅通过相应的头文件(.h)对其API进行验证 . 直到运行时才需要实际使用该库 .

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

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

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

提前谢谢大家的帮助 .

更新


In the context in which these terms were provided to me, it was effectively erroneous terms used by a team of Windows developers that had to learn Linux. I tried to correct them, but the (incorrect) language norms stuck.

  • 共享对象:程序启动时自动链接到程序的库,作为独立文件存在 . 该库在编译时包含在链接列表中(即: LDOPTS+=-lmylib 用于名为 mylib.so 的库文件) . The library must be present at compile time, and when the application starts.

  • 静态库:在构建时合并到实际程序本身的库,用于包含应用程序代码的单个(更大)应用程序和在构建程序时自动链接到程序的库代码,以及包含的最终二进制文件主程序和库本身都作为单独的二进制文件存在 . 该库在编译时包含在链接列表中(即: LDOPTS+=-lmylib 用于名为mylib.a的库文件) . The library must be present at compile time.

  • DLL:基本上与共享对象相同,但不是在编译时包含在链接列表中,而是通过 dlopen() / dlsym() 命令加载库,以便在构建时不需要将库存在于程序中编译 . Also, the library does not need to be present (necessarily) at application startup or compile time ,因为只有在进行 dlopen / dlsym 调用时才需要它 .

  • 共享存档:基本上与静态库相同,但使用"export-shared"和"-fPIC"标志进行编译 . 该库在编译时包含在链接列表中(即:LDOPTS = -lmylib S ,用于名为mylib S .a的库文件) . 两者之间的区别在于,如果共享对象或DLL想要将共享存档静态链接到其自己的代码并且能够使共享对象中的函数可用于其他程序而不是仅仅使用它们,则需要此附加标志 . DLL的内部 . 当有人为您提供静态库,并且您希望将其重新打包为SO时,这非常有用 . The library must be present at compile time.

其他更新

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

此外,尾随的“ S " literal after the library name, in the case of "共享档案”只是该公司使用的惯例,而不是整个行业 .

4 回答

  • 3

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

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

    dynamic link library on windows(.dll) 就像Linux上的共享库(.so),但有与操作系统相关的两个实现之间存在一些差异(Windows vs Linux):

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

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

  • 77

    我可以详细说明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的导出绑定元数据 .

  • 24

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

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

  • 159

    我've always thought that DLLs and shared objects are just different terms for the same thing - Windows calls them DLLs, while on UNIX systems they'共享对象,通用术语 - 动态链接库 - 涵盖两者(甚至在UNIX上打开.so的函数在'dynamic library'之后称为 dlopen() ) .

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

    静态库只是直接来自编译器的目标文件包,就像你在项目编译过程中自己构建的一样,所以它们以完全相同的方式被引入并提供给链接器,未使用的位是以完全相同的方式掉落 .

相关问题