1. 动态库和静态库的区别

动态链接库(Dynamic Link Library,DLL)和静态链接库(Static Link Library)是在软件开发中常用的两种库文件格式,它们在使用和加载方式上存在一些区别:

  1. 静态链接库:

    • 静态链接库是在编译时将库代码和应用程序代码合并到一个可执行文件中。

    • 在使用静态链接库时,库的代码将被完整地复制到每个使用该库的应用程序中。

    • 这意味着每个应用程序都包含库的完整副本,因此应用程序的大小将增加。

    • 静态链接库在编译时静态地与应用程序绑定,因此不需要额外的运行时依赖关系。

    • 由于每个应用程序都包含库的副本,因此对静态链接库的更新需要重新编译和重新发布所有使用它的应用程序。

  2. 动态连接库:

    • 动态连接库是在运行时加载和链接的库文件。

    • 在使用动态连接库时,应用程序只保存对库的引用,而不包含库的实际代码。

    • 库的代码是独立存在的,可以被多个应用程序共享使用。

    • 动态连接库的加载是在运行时进行的,因此应用程序的大小不会因为库的增加而增加。

    • 动态连接库需要在运行时存在,并且应用程序需要正确的运行时环境才能加载和使用库。

    • 当动态连接库更新时,只需要更新库本身即可,不需要重新编译和重新发布使用该库的应用程序。


2. 动态库的两种链接方式

动态链接库是一种在运行时加载和链接的共享库。它可以被多个程序共享,提供可重用的代码和函数。在使用动态连接库时,有两种主要的调用方式:

  1. 显式链接(Explicit Linking): 显式链接通过在程序中显式地加载和使用动态连接库中的函数的方式,进行调用。在这种方式下,需要使用特定的函数加载和释放动态连接库,通常通过操作系统提供的函数库实现。在加载动态连接库后,可以使用函数指针(Function Pointer)调用其中的函数。

    以下是使用显式链接调用动态连接库的一般步骤:

    • 加载动态连接库:使用操作系统提供的函数库(如 dlopen())加载动态连接库文件。

    • 获取函数地址:使用函数库提供的函数(如 dlsym())获取动态连接库中函数的地址。

    • 调用函数:通过函数指针调用动态连接库中的函数。

    显式链接适用于在编译时无法确定函数地址或需要更灵活的加载方式的情况。

  2. 隐式链接(Implicit Linking): 隐式链接是在编译时将动态连接库的函数链接到程序中,使得函数可以像普通的函数一样被调用,而无需显式地加载和释放动态连接库。在这种方式下,编译器在编译期间将函数的调用绑定到动态连接库中的实际函数地址。

    隐式链接的调用方式相对简单,但需要在编译期间确保动态连接库的可用性。需要在编译时指定动态连接库的名称和路径,以便编译器可以正确地链接到相应的函数。

    隐式链接通常使用特定的编译指令或选项指定动态连接库,如在 GCC 中使用 -l 选项指定动态连接库的名称。

无论是显式链接还是隐式链接,使用动态连接库都可以实现代码的模块化和重用,提高程序的灵活性和可维护性。选择哪种调用方式取决于具体的需求和场景。


3. 隐式链接示例

hi.c:

编译:

main.c:

编译:

使用隐式链接方式时,主程序可以直接调用动态库的函数,反之动态库也可以直接调用主程序中的函数。动态库是在程序运行时链接的,在运行主程序时,需要找到动态库。本示例,通过在编译时加入 -Wl,-rpath=. 选项的方式,指定程序需要搜索的路径。


4. 显式链接示例

hi.c 文件保持不变,同样编译为 libhi.so。

main.c:

编译:

通常情况下,编译器在生成可执行文件时只将程序所需的符号添加到符号表中,而将未被使用的符号省略。这样可以减小可执行文件的大小,提高执行效率。然而,对于某些场景,比如动态链接库需要调用可执行文件中的函数时,就需要将所有符号添加到动态符号表中,以便动态链接库能够正确地找到和使用这些符号。

使用 -rdynamic 选项可以告诉编译器将所有符号都添加到动态符号表中。这样,在生成的可执行文件中,所有的函数名称和全局变量都可以被动态链接库访问和使用。


5. dlopen 系列函数

dlopen()dlsym()dlerror()dlclose() 是一组函数,用于在 Linux 系统中进行动态链接库的加载、符号获取、错误处理和卸载。它们位于 <dlfcn.h> 头文件中,提供对动态链接库的动态操作。

5.1. dlopen()

函数原型:

dlopen() 函数用于加载动态链接库。它接受字符串参数 filename,表示动态链接库的路径或名称,以及一个整数参数 flag,表示加载的方式和选项。函数返回指向动态链接库的句柄(handle),如果加载失败,则返回 NULL

5.2. dlsym()

函数原型:

dlsym() 函数用于通过句柄和符号名称获取动态链接库中的符号地址。它接受指向动态链接库的句柄 handle,以及字符串参数 symbol,表示要获取的符号的名称。函数返回指向符号对应的地址的指针,如果获取失败,则返回 NULL

5.3. dlerror()

函数原型:

dlerror() 函数用于获取最近一次调用动态链接库函数发生的错误信息。它返回指向表示错误信息的字符串的指针,如果没有错误发生,则返回 NULL。需要注意的是,每次调用其它动态链接库函数后,都应该立即调用 dlerror() 检查是否有错误发生。

5.4. dlclose()

函数原型:

dlclose() 函数用于卸载动态链接库。它接受指向动态链接库的句柄 handle,尝试卸载该动态链接库。函数返回 0 表示成功,返回非零值表示失败。


6. 示例


7. 参考文档