2021-01-04
发布日期:2021-05-18 04:38:03 浏览次数:18 分类:精选文章

本文共 2625 字,大约阅读时间需要 8 分钟。

动态库的制作与使用指南

一、动态库的制作步骤

要制作动态库,通常需要遵循以下步骤:

  • 编译源文件生成目标文件

    使用gcc编译所有需要打包的源文件,生成与位置无关的.o文件:

    gcc -c -fPIC t*.c

    这里的t*.c表示将所有以t开头的C源文件编译为目标文件。生成的目标文件与依赖的位置无关。

  • 打包目标文件生成动态库

    将上述生成的目标文件打包成为动态库:

    gcc -shared -o libt_math.so *.o

    这里的*-shared参数表示生成共享库,*.o表示将所有的目标文件打包到动态库中。

  • 链接可执行文件

    使用动态连接器将动态库链接到可执行文件中:

    gcc main.c -L. -lt_math

    由于使用的是静态连接器,连接器会尝试加载动态库文件。如果找不到动态库,会抛出类似libt_math.so cant open的错误提示。

  • 二、常见错误处理

    在实际操作中,可能会遇到如下的问题:

  • 执行可执行文件时提示libt_math.so cant open

    这说明动态连接器无法找到预期的动态库文件。
    通过使用ldd a.out查看依赖关系,可以了解具体哪个动态库无法加载。

    $ ldd a.out
  • 通过查看依赖关系可以看到:

    ldd a.outldd: dynamic /lib/t_math.so: no such file or directory

    这表明/lib/t_math.so/usr/lib/t_math.so等常用路径中都不存在动态库文件。

  • 三、常见问题解决方法

    要解决上述问题,可以采取以下措施:

  • 手动指定动态库的路径

    在运行可执行文件时,通过设置LD_LIBRARY_PATH环境变量,手动告知动态连接器动态库的位置:

    export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib

    适用于临时需要查找动态库的场景。

  • 检查默认搜索路径

    动态连接器通常会查找以下路径中的动态库:

    /lib/usr/lib

    你可以手动检查这些路径下是否存在动态库文件。

  • 四、静态库与动态库的区别

    1. 关键特性对比

    • 静态库

      • 通常被编译到可执行文件中,生成的可执行文件不再依赖静态库。
      • 依赖静态库的可执行文件虽然体积较大,但无需额外的动态库支持。
    • 动态库

      • 被编译到可执行文件中,生成的可执行文件在运行时依赖动态库。
      • 动态连接器会在运行时根据需要加载所需的动态库,减少可执行文件的体积。

    2. 管理与使用特点

    • 静态库

      • 使用静态库的可执行文件在运行时无需外部动态库支持。
      • 但一旦需要修改字月代码,则需要重新编译整个可执行文件。
    • 动态库

      • 使用动态库的可执行文件需要动态连接器的支持。
      • 动态连接器负责在运行时动态地加载相关动态库文件,保持内核态与用户态的透明度。

    五、动态加载的原理与接口

    动态加载的核心是通过动态连接器实现的。常见的动态加载接口包括dlopendlclosedlsym等。

    #include 

    1. 动态连接器的功能

    • dlopen

      • 用于打开动态库文件。
      • 参数:filename(动态库路径) | flags(lazzy或eager绑定)。
      • 返回:成功则为非空指针,失败则为NULL。
    • dlclose

      • 用于关闭已经打开的动态库。
      • 返回:若成功则非零,若引用计数减到0则卸载动态库。
    • dlerror

      • 用于获取动态连接器失败的错误信息。
    • dlsym

      • 用于获取特定符号在动态库中的地址。

    2. 实际应用示例

    #include 
    #include
    typedef int (*sub_func)(int a, int b);int main(int argc, char *argv) { void *handle = dlopen(argv[1], RTLD_NOW); if (NULL == handle) { printf("dlopen error: %s\n", dlerror()); return -1; } sub_func sub = (sub_func)dlsym(handle, "sub"); if (NULL == sub) { printf("dlsym error: %s\n", dlerror()); return -1; } printf("sub(3-2) = %d\n", sub(3, 2)); dlclose(handle); return 0;}

    3. 常用库文件

    执行ldd dynamic时,可以看到常用的动态库文件:

    ldd: dynamic /lib/dl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f9debc40000)ldd: dynamic /libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9def0c0000)ldd: dynamic /lib64/ld-linux-x86-64.so.2 => /lib/x86_64-linux-gnu/lib64/ld-linux-x86-64.so.2 (0x00007f9de9b40000)ldd: dynamic /t_math.so => /usr/local/lib/t_math.so (0x00007f9de9d70000)

    从依赖关系可知,dynamic本身依赖于多个动态库,其中/usr/local/lib/t_math.so是我们的自定义动态库。

    4. 动态加载的实现细节

    从库文件的符号信息来看:

    nm dynamic:...dlsym@GLIBC_2.2.5dlclose@GLIBC_2.2.5dlopen@GLIBC_2.2.5dlerror@GLIBC_2.2.5...

    这些符号表明dynamic程序依赖于动态连接器的核心接口。但是,你会注意到动态库t_math.so中的符号并没有在这个输出中。

    六、总结

    动态库能够提供更高的灵活性和可扩展性,但也伴随着一定的使用成本。合理使用动态连接器的接口,能够更好地管理程序的依赖关系。这也是动态加载机制的核心优势所在。

    上一篇:2021-01-09
    下一篇:2020-12-05

    发表评论

    最新留言

    网站不错 人气很旺了 加油
    [***.192.178.218]2025年04月21日 18时39分14秒