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

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

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

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

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

我听说有些人在共享对象和动态链接库(DLL’s)之间做了区分,尽管它们都是“。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"图书馆名称后面的文字,在“共享档案”的情况下;只是那家公司的惯例,而不是整个行业的惯例。

211396 次浏览

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

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

静态库只是直接从编译器中取出的目标文件束,就像你自己编译项目的一部分一样,所以它们以完全相同的方式被拉入并提供给链接器,未使用的位以完全相同的方式被删除。

你在静态文件在链接时复制到应用程序共享文件只是在链接时进行验证,并在运行时加载上是正确的。

dlopen调用不仅适用于共享对象,如果应用程序希望在运行时代表它这样做,否则共享对象将在应用程序启动时自动加载。dll和.so是同一个东西。dlopen的存在是为了为进程添加更细粒度的动态加载能力。你不必自己使用dlopen来打开/使用dll,这在应用程序启动时也会发生。

静态库(。)是一个库,可以直接链接到链接器生成的最终可执行文件中,它包含在可执行文件中,没有必要将该库链接到部署可执行文件的系统中。

共享库(所以)是一个库,它被链接但没有嵌入到最终的可执行文件中,所以当可执行文件启动时将被加载,并且需要出现在部署可执行文件的系统中。

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

DLL可以定义两种函数:导出函数和内部函数。导出的函数可以由其他模块调用,也可以从定义它们的DLL中调用。内部函数通常只能从定义它们的DLL中调用。

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

我可以详细介绍Windows中dll的细节,以帮助我在* nix领域的朋友们澄清这些谜团……

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

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

Windows DLL是通过编译和链接构建的,就像EXE(可执行应用程序)一样,但DLL不是独立的,就像SO是由应用程序使用的一样,要么通过动态加载,要么通过链接时绑定(对SO的引用嵌入在应用程序二进制的元数据中,操作系统程序加载器将自动加载引用的SO)。dll可以引用其他dll,就像SOs可以引用其他SOs一样。

在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所需的“导出存根”库。所以,我们应该总是看a *。LIB文件有一个同名*.DLL文件;如果没有,很有可能*。LIB文件是一个静态库存档,并不能导出绑定元数据为DLL。

我怀疑这里存在某种误解,但是头文件,至少是用于编译源代码的.h类型的头文件,在链接期间绝对不会被检查。

.h,以及就此而言,.c/.cpp文件只在包括预处理的编译阶段涉及。一旦创建了目标代码,头文件在链接器开始处理之前就已经很好地完成了。