C/C++ 动态链接如何在不同平台上工作

C/C++ How Does Dynamic Linking Work On Different Platforms?

本文关键字:平台 工作 C++ 动态 链接      更新时间:2023-10-16

动态链接通常如何工作?

在Windows(LoadLibrary)上,您需要在运行时调用.dll,但在链接时,您需要提供相应的.lib文件,否则程序将无法链接....lib 文件包含什么?.dll方法的描述?这不是标题包含的内容吗?

相关地,在 *nix 上,您不需要库文件...编译器如何知道标头中描述的方法在运行时可用?

作为一个新手,当你想到这两个方案中的任何一个,然后是另一个时,它们都没有意义......

一一回答您的问题:

  • 动态链接将链接过程的一部分推迟到运行时。它可以通过两种方式使用:隐式和显式。隐式地,静态链接器会将信息插入到可执行文件,这将导致库加载并解析必要的符号。 明确地,您必须调用LoadLibrary或 手动dlopen,然后为每个GetProcAddress/dlsym您需要使用的符号。 隐式加载用于事物就像系统库一样,实现将依赖于系统的版本,但界面是有保证的。显式加载用于插件之类的东西,其中要加载的库将在运行时确定。

  • .lib文件仅对隐式加载是必需的。 它包含库实际提供的信息符号,因此链接器不会抱怨该符号是未定义,它告诉链接器在哪个库中的符号位于,因此可以插入必要的信息以引起此库要自动加载。 所有头文件告诉编译器符号将存在于某个地方;这链接器需要.lib才能知道在哪里。

  • 在 Unix 下,所有信息都从 .so . 为什么 Windows 需要两个单独的文件,而不是将所有信息放在一个文件中,我不知道;它实际上复制了大部分信息,因为.lib中所需的信息也需要.dll。(也许是许可问题。 您可以分发您的程序.dll,但没有人可以链接到库,除非他们有一个.lib

要保留的主要内容是,如果你想要隐式加载,您必须向链接器提供适当的信息,带有.lib.so文件,以便它可以插入信息放入可执行文件中。 如果你想要明确的加载中,您无法引用库中的任何符号径直;你必须打电话给GetProcAddress/dlsym才能得到他们的解决你自己(并做一些有趣的选角来使用它们)。

Windows 上的 .lib 文件不是加载动态库所必需的,它只是提供了一种方便的方法。

原则上,您可以使用LoadLibrary加载 dll,然后使用 GetProcAddress 访问该 dll 提供的函数。在这种情况下,封闭程序的编译不需要访问 dll,它仅在运行时(即LoadLibrary实际执行时)需要。MSDN 有一个代码示例。

这里的缺点是您需要手动编写代码以从 dll 加载函数。如果您首先自己编译了 dll,则此代码只是复制编译器可以从 dll 源代码中自动提取的知识(例如导出函数的名称和签名)。

这就是.lib文件的作用:它包含对 Dlls 导出函数的GetProcAddress调用,这些函数由编译器生成,因此您不必担心它。在 Windows 术语中,这称为加载时动态链接,因为在加载封闭程序,Dll 由 .lib 文件中的代码自动加载(与手动方法相反,称为运行时动态链接)。

动态链接通常如何工作?

动态链接库(又名共享对象)文件包含机器代码指令和数据,以及一个元数据表,说明该代码/数据中的哪些偏移量与哪些"符号"有关,符号的类型(例如函数与数据),数据中的字节数或字数,以及其他一些事情。 不同的操作系统往往具有不同的共享对象文件格式,实际上相同的操作系统可能支持多种,但这就是它的要点。

因此,假设共享库是一大块字节,索引如下:

SYMBOL       ADDRESS        TYPE        SIZE
my_function  1000           function    2893
my_number    4800           variable    4

通常,不需要在元数据表中捕获符号的确切类型 - 预计库头文件中的声明包含所有缺失的信息。 与 C 相比,C++有点特别,因为重载可能意味着有多个具有相同名称的函数,并且命名空间允许其他符号,否则这些符号的名称会不明确 - 因此,名称重整通常用于将命名空间和函数参数的某些表示连接到函数名称,形成在库对象文件中唯一的东西。

想要使用共享对象的程序通常可以执行以下两项操作之一:

  • 让操作系统大约同时加载自身和共享对象(在执行main()之前),操作系统加载器负责查找符号并检查程序文件图像中有关这些符号使用的元数据,然后在程序使用的内存中修补符号地址,这样程序就可以运行并正常工作,就好像它在第一次运行时就知道符号地址一样编译(但可能慢一点)

  • 或者,在main运行后的某个时间显式地在其自己的源代码调用dlopen,然后使用 dlsym 或类似方式获取符号地址,根据程序员对预期数据类型的了解将它们保存到(函数/数据)指针中,然后使用指针显式调用它们。

在Windows(LoadLibrary)上,您需要在运行时调用.dll,但是在链接时,您需要提供相应的.lib文件,否则程序将无法链接...

这听起来不对。 我认为应该是一个或另一个。

wtf .lib 文件包含吗?.dll方法的描述?这不是标题包含的内容吗?

在这个描述级别上,lib 文件与共享对象文件几乎相同......主要区别在于编译器在程序发布和运行之前找到符号地址。

现代 *nix 系统从 Solaris OS 派生动态链接过程。 特别是 Linux 不需要单独的 .lib 文件,因为所有外部依赖项都包含在 ELF 格式中。 ELF文件的.interp部分表明此可执行文件中存在需要动态解析的外部符号。这适用于动态链接

有一种方法可以在用户空间中处理动态链接。此方法称为动态加载。这是使用系统调用从外部 *.so 获取指向方法的函数指针时。

更多信息可以从本文中找到 http://www.ibm.com/developerworks/library/l-dynamic-libraries/。

相关地,在OS X上(我假设*nix...dlopen),你不需要库文件...编译器如何知道标头中描述的方法在运行时可用?

编译器或链接器不需要此类信息。您(程序员)需要处理您尝试通过dlopen()打开的共享库可能不存在的情况。

您可以通过两种方式在 Windows 中使用 DLL 文件: 要么你链接到它,你就完成了,没有什么可做的了。 或者您在运行时动态加载它。

如果与它链接,则使用 DLL 库文件。链接库包含链接器用来实际知道要加载哪个 DLL 以及 DLL 函数中的位置的信息,以便它可以调用它们。加载程序时,操作系统也会为您加载DLL,基本上它为您调用LoadLibrary

在其他操作系统(如OS X和Linux)中,它以类似的方式工作。不同之处在于,在这些系统上,链接器可以直接查看动态库(.so/.dynlib文件),并找出需要什么,而无需像在Windows上那样使用单独的静态库。

要动态加载库

,您无需链接到与要加载的库相关的任何内容。

就像其他人已经说过的:Windows上.lib文件中包含的内容直接包含在Linux/OS X上的.so/.dynlib中。但主要问题是...为什么?*nix 解决方案不是更好吗?我认为是的,但.lib有一个优势。链接到 DLL 的开发人员实际上不需要有权访问 DLL 文件本身。

这样的场景在现实世界中经常发生吗?是否值得为每个DLL文件维护两个文件?我不知道。

编辑:好的,伙计们让我们让事情变得更加混乱!您可以使用MinGW直接链接到Windows上的DLL。所以整个导入库问题与Windows本身没有直接关系。摘自MinGW维基的sampleDLL文章:

由 "--out-implib" 链接器选项创建的导入库是 必需的 iff(==if 且仅当)DLL 应从某些接口 C/C++ 编译器,而不是 MinGW 工具链。MinGW工具链是 非常乐意直接链接到创建的DLL。更多详情 可以在 LD.exe 信息文件中找到,这些文件是 binUtils 的一部分 包(是工具链的一部分)。

Linux 也需要链接,但反对 .Lib 库需要链接到动态链接器/lib/ld-linux.so.2,但这通常在使用 GCC 时发生在幕后(但是如果使用汇编程序,您确实需要手动指定它)。

这两种方法,要么 视窗 .LIB方法或Linux动态链接器链接方法实际上被认为是静态链接。但是,有一个区别,即在Windows中,部分工作是在链接时完成的,尽管它在加载时仍然有工作(我不确定,但我认为.LIB 文件只是为了让链接器知道物理库名称,但符号仅在加载时解析),而在 Linux 中,除了链接到动态链接器之外的所有内容都发生在加载时。

动态链接通常是指在运行时手动打开DLL文件(例如使用LoadLinrary()),在这种情况下,负担完全由程序员承担。

在共享库中,例如 .dll .dylib.so ,有一些关于符号名称和地址的信息,如下所示:

------------------------------------
| symbol's name | symbol's address |
|----------------------------------|
| Foo           | 0x12341234       |
| Bar           | 0xabcdabcd       |
------------------------------------

加载功能,例如 LoadLibrarydlopen ,加载共享库并使其可供使用。

GetProcAddressdlsym找到您的符号地址。例如:

HMODULE shared_lib = LoadLibrary("asdf.dll");
void *symbol = GetProcAddress("Foo");
// symbol is 0x12341234

在窗口中,有.lib文件可以使用.dll。当你链接到这个.lib文件时,你不需要调用LoadLibraryGetProcAddress,只需使用共享库的功能,就好像它们是"普通"函数一样。它是如何工作的?

实际上,.lib包含导入信息。是这样的:

void *Foo; // please put the address of Foo there
void *Bar; // please put the address of Bar there

当操作系统加载您的程序(严格来说,您的模块)时,操作系统会自动执行LoadLibraryGetProcAddress

如果你写的代码,如Foo();,编译器会自动将其转换为(*Foo)();。因此,您可以像使用"正常"功能一样使用它们。