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


272

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

根据我的理解(如果我做错了,请纠正我,稍后我将编辑我的文章)在构建应用程序时有两种使用库的方法:

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

静态库的明显优点是它们允许整个应用程序独立运行,而动态库的优点是可以替换“ .so”文件(即:由于安全原因需要更新)错误),而无需重新编译基本应用程序。

我听说有人对共享库和动态链接库(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. 共享存档:本质上与静态库相同,但是使用“导出共享”和“ -fPIC”标志进行编译。该库在编译时包含在链接列表中(即:LDOPTS+=-lmylibS对于名为的库文件mylibS.a)。两者之间的区别是,如果共享库或DLL希望将共享归档文件静态链接到其自己的代码,并且能够使共享库中的函数可用于其他程序,而不仅仅是使用它们,则需要使用此附加标志。 DLL内部。当有人为您提供静态库并且您希望将其重新打包为SO时,这很有用。该库必须在编译时存在。

附加更新

DLL”和“ shared library” 之间的区别只是我当时工作的公司中的一种(懒惰,不准确的)口语(Windows开发人员被迫转移到Linux开发,并且这个术语陷入了僵局),并遵循上述说明。

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


14
对于.a文件,“ a”实际上代表“ archove”,它只是目标文件的存档。现代的链接器应该足够好,不必只包含while库,而只包含需要的归档中的对象文件,甚至可以只使用所引用的对象文件中的代码/数据部分。
某位程序员花了

4
DLL只是Windows的术语。它不是在unices上使用的。
R .. GitHub停止帮助ICE



2
@DevNull 当然是“ arch i ve”。:)
一些程序员伙计

Answers:


93

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

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

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


1
为什么我在Linux上看到的某些项目必须使用dlopen()调用才能访问“ .so”文件中的函数,而有些根本不需要这样做?谢谢,顺便说一句!

9
那些不这样做的人将由进程加载器(即linux的elf加载器)将函数交给它们。如果应用程序要打开并使用在编译时不存在的.so或.dll或仅添加诸如插件之类的附加功能,则dlopen存在。
rapadura 2012年

但是,如果在构建时不存在.so,应用程序将不会完全编译吗?是否可以强制链接器仅构建最终程序而根本不存在.so?谢谢。
2012年

1
我相信这取决于您如何使用.so中的功能,但是在此我的知识止步不前:/好问题。
rapadura 2012年

1
关于dlopen()及其函数系列,据我了解,该文件用于以编程方式打开/关闭dll,因此在整个应用程序运行期间不必将其加载到内存中。否则,您必须在链接器的命令行参数(即您的makefile)中告诉链接器您要加载库。它将在运行时加载并保持加载到内存中,直到应用程序退出。在OS级别上可能还会发生更多事情,但是就您的应用程序而言,这大概是发生的事情。
泰勒·普莱斯

197

静态库(.a)中是可直接连接到由接头产生的最终可执行库,它包含在它并没有必要有库入其中可执行将要部署的系统。

共享库(。所以)是链接而不是嵌入在最终的可执行库,当所述可执行启动和需要存在于其中的可执行部署系统,以便将被加载。

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

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

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


1
+1不错的简单说明。如果一个函数在DLL中声明为“内部”,是否意味着不能从库外部调用该函数?
Mike

23
所有符号都不一定在SO库中可用。隐藏符号是可能的并且值得推荐,因为没有充分的理由让图书馆用户看到您所有的符号。
Zan Lynx

3
仅供参考:g ++具有__attribute__用于选择性地“导出”符号的语法:#define DLLEXPORT __attribute__ ((visibility("default"))) #define DLLLOCAL __attribute__ ((visibility("hidden")))
Brian Haak 2014年

33

我可以详细说明Windows中DLL的详细信息,以帮助在* NIX-land上向我的朋友们阐明这些奥秘...

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

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

Windows DLL是通过编译和链接来构建的,就像对EXE(可执行应用程序)的构建一样,但是DLL并非独立存在,就像SO旨在由应用程序通过动态加载或使用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的绑定元数据。


4

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

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


使用dlopen()进行更多加载控制的一个例子是什么?如果SO / DLL在启动时自动加载,例如,dlopen()是否以不同的权限或限制关闭并重新打开它?谢谢。

1
我相信dlopen适用于插件或类似功能。权限/限制应与自动加载的权限/限制相同,无论如何,dlopen将递归加载依赖的库。
rapadura 2012年

DLL和.so不完全一样的东西。看到这个答案
Basile Starynkevitch 18/12/9
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.