链接库知识总结

链接库知识总结

链接库的概念

  • :所谓库文件,读者可以将其等价为压缩包文件,该文件内部通常包含不止一个目标文件(也就是二进制文件)。值得一提的是,库文件中每个目标文件存储的代码,并非完整的程序,而是一个个实用的功能模块。例如,C 语言库文件提供有大量的函数(如 scanf()、printf()、strlen() 等),C++ 库文件不仅提供有使用的函数,还有大量事先设计好的类(如 string 字符串类)。

  • 链接库:库文件的二进制版本,将开源的库文件进行编译、打包后可得到。实际开发中引入他人编写好的库文件可以省略某些功能的开发环节,提高项目的开发效率,但多数程序员并不会直接分享源代码,而是分享库文件的二进制版本——链接库。

  • 静态链接库:在生成可执行文件之前完成所有链接操作,使用的库文件称为静态链接库。

  • 动态链接库:将部分链接操作推迟到程序执行时才进行,此过程使用的库文件称为动态链接库。

    在 Linux 发行版中,静态链接库和动态链接库通常存放在 /usr/bin 或者 /bin 目录下。

    当有多个程序使用同一个动态链接库时,所有程序可以共享一份动态链接库的指令和数据,避免了空间的浪费。采用动态链接的方式也可以方便程序的更新和升级,当程序的某个模块更新后,只需要将旧的模块替换掉,程序运行时会自动将所有模板载入内存并动态地链接在一起。

windows下静态库的创建和使用

  • 静态链接库的创建

    1. 打开 VS2019,选择“创建新项目”

    2. 弹出的对话框,选择“静态库”

      img

    3. 自定义所要创建的项目名和存储位置

    4. 初始状态下,创建好的静态库项目中会自动包含以下几个文件。只保留 myMath.c 源文件(将 myMath.cpp 的后缀名改为 .c),其它三个文件直接删除即可。在此基础上,我们需要创建 myMath.c 对应的头文件,以便向他人分享静态链接库时展示具体的功能和用法。

      img

    5. 由于我们没有使用 VS 默认提供的 pch.h 等文件,需要修改一下 VS 的设置。右击项目名(本节自定义的项目名称为 myMath),选择“属性”,会弹出如下所示的对话框:

      img

    6. 完成以上操作后,就可以向 myMath.cmyMath.h 中添加代码了。菜单栏中依次选择“生成 -> 生成 myMath”(项目名称可能不同),或者直接按 Ctrl+B 组合键,VS 就成功创建好了静态链接库。

      img

  • 静态链接库的使用

    1. 若要在 myDemo 项目中使用 myMath.lib 静态链接库,首先要引入 myMath.h 头文件。具体做法是将 myMath.h 文件拷贝到 myDemo 项目中,鼠标右键点击图 7 中的 "myDemo" 项目名,选择“在文件资源管理器中打开文件夹”,即可找到 myDemo 项目的存储位置。

      img

    2. 由于.h 文件中各个函数的具体实现位于 myMath.lib 文件内,因此要保证项目在链接阶段能成功找到 myMath.lib 文件。

      • 方法:用 #pragma 预处理指令指明要使用的静态链接库。假设 myMath.lib 的存储路径为 "D://myMath.lib",则我们只需在 main.c 文件的开头加入代码:

        #pragma comment (lib,"D://myMath.lib")
        

Linux下静态链接库的创建和使用

  • 静态链接库的创建

    1. 编译mymath.c文件:gcc -c myMath.c -o myMath.o

    2. 将所有的目标文件打包成一个静态链接库:ar rcs libmyMath.a myMath.o add.o sub.o div.o ,将 myMath.o 等所有目标文件打包为 libmyMath.a

      ar 命令常用于创建静态链接库,其中 r、c、s 是 ar 命令创建静态链接库所需要设定的参数。

      需要注意的是,Linux 平台上静态链接库的名称不是随意的,通常遵循 libxxx.a 格式

  • 静态链接库的使用

    1. main.c 执行编译操作得到对应的目标文件,执行如下指令:gcc -c main.c -o main.o

    2. 将它们链接成一个可执行文件,实现链接的指令有两种:

      • 第一种:gcc -static main.o libmyMath.a -o main

        -static 的作用是强制 GCC 编译器使用静态链接库,最终生成的可执行文件的名称为 main

      • 第二种:gcc main.o -o main -static -lmyMath -L /home/test/myMath

        -l 后紧跟静态链接库的名称 myMath(它是 libxxx.a 中的 xxx 部分),-L /home/test/myMath 用于指定 libmyMath.a 的存储位置。

Windows下动态链接库的创建和使用

  • 动态链接库的创建

    Windows 平台上,创建动态链接库文件(.dll)的同时还必须生成一个引入库文件(.lib)。.lib 是静态库文件的后缀名,虽然引入库文件的后缀名也是 .lib,但两者有着本质上的区别。

    • 库文件作用:负责记录动态库中哪些函数和变量允许被外界调用,记录的信息包含函数和变量的名称以及它们在动态库中的存储位置。(动态链接库负责存储定义好的函数和变量)

    • 步骤:

      1. 创建一个动态链接库的项目,选择“创建新项目”

      2. 弹出的对话框中,选择创建“动态链接库(DLL)”项目,然后点击“下一步”

        img

      3. 自定义项目名称和存储位置,其它选项保持默认即可

      4. 点击“创建”按钮后,动态链接库的项目就创建完成了,初始状态下的项目如下所示,其中最重要的是 dllmain.cpp 文件,这些文件可以全部删除。

        img

      5. 由于项目中没有使用 VS 提供的 pch.h 等文件,需要修改一下 VS 的设置,具体做法是:鼠标右击项目名,选择 "属性",在下图所示的对话框中完成如下修改操作:

        img

      在 Windows 平台上,动态链接库中的函数或者变量要想被外界调用,必须用 __declspec(dllexport) 修饰,可以显式地“告诉”编译器哪些函数和变量能被外界调用,这些函数和变量的信息(名称、存储位置)保存在引入库文件(.lib)中,而它们的定义保存在动态链接库文件(.dll)中。

  • 动态链接库的使用

    • 分享动态链接库:

      1. 分享动态链接库时需要准备 3 个文件:动态链接库的头文件、动态链接库文件和引入库文件。
      2. 将其内部的所有 __declspec(dllexport) 改为 __declspec(dllimport),经过修改后的头文件才能被用户使用。
    • 加载动态链接库:

      1. 将修改过的 myMath.h 以及 myDLL.libmyDLL.dll 文件拷贝至 MyDemo 项目中。

        MyDemo 项目的存储位置可以通过 "右击资源管理器中的项目名 -> 在资源管理器中打开文件夹" 找到

      2. myMath.h 头文件和 myDLL.lib 引入到 myDemo 项目中,引入后的 MyDemo 如下图所示。

        img

      3. main.c 文件中添加#include "myMath.h"语句后,运行 MyDemo 项目。

Linux下动态链接库的创建和使用

Linux 平台默认情况下动态链接库中定义的所有函数和变量都允许被外界调用,动态链接库中不仅保存了所有函数和变量的定义,还保存了能被外界调用的所有函数和变量的信息,所以不需要生成引入库文件。

  • 动态链接库的创建

    • 方法1:使用 GCC 编译器将 myMath.c 转换成动态链接库文件:gcc -shared -fPIC myMath.c -o libmyMath.so

      • -shared:表示生成动态链接库;

      • -fPIC:也可以写成 -fpic,功能是令 GCC 编译器生成动态链接库时,用相对地址表示库中各个函数和变量的存储位置。这样做的好处是,无论动态链接库被加载到内存的什么位置,都可以被多个程序(进程)同时调用;

      • -o libmyMath.so-o 选项用于指定生成文件的名称,此命令最终生成的动态链接库文件的文件名为 libmyMath.so

        Linux 平台上,动态链接库文件的命名格式为 libxxx.so。

    • 方法2:也可以先将 myMath.c 编译为目标文件,然后再将目标文件转换为动态链接库。

      1. 先执行 gcc -c -fPIC myMath.c ,转换为目标文件。
      2. 再执行gcc -shared myMath.o -o libmyMath.so,生成动态链接库。
  • 动态链接库的使用

    1. main.cmyMath.h 放在同一目录,向 main.c 文件中引入 myMath.h 头文件

    2. 执行如下指令,即可生成可执行文件:gcc main.c libmymath.so -o main.exe

      main.exe 执行时需要将 libmyMath.so 一起载入内存,否则会报错

    3. 查看 main.exe 执行时需要调用的所有动态链接库,以及它们各自的存储位置: ldd main.exe

    4. 确保程序运行时可以找到所有需要的动态链接库:

      • 方法1:将链接库文件移动到标准库目录下(例如 /usr/lib/usr/lib64/lib/lib64);
      • 方法2:在终端输入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx,其中xxx为动态链接库文件的绝对存储路径(此方式仅在当前终端有效,关闭终端后无效);
      • 方法3:修改~/.bashrc~/.bash_profile文件,即在文件最后一行添加export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxxxxx为动态库文件的绝对存储路径)。保存之后,执行source .bashrc指令(此方式仅对当前登陆用户有效)。

Windows下显式调用动态链接库

  • 动态链接库载入内存的两种方式

    1. 当可执行文件载入内存时,由动态链接器将所有动态库文件载入内存;

    2. 由程序自己控制动态库载入内存的时机,并且可以在不需要的时候将其卸载(释放),这种加载动态库的方式又称运行时加载

      通常情况下,支持动态链接的系统都支持以“运行时加载”的方式载入动态库。

  • 运行时加载介绍

    • 特点:程序边执行边载入所需的动态库文件,而不是一开始就将它们全部载入内存。

    • 优点:可以缩减程序的启动时间,节省程序运行过程中占用的内存空间。此外,采用运行时载入的程序执行时可以重新载入某个动态链接库,整个过程无需重新启动。

      通常情况下,支持动态链接的系统都支持以“运行时加载”的方式载入动态库。

  • 显式调用动态链接库

    1. 加载库文件:

      • LoadLibrary() 函数可以加载指定的动态库文件;
      • 语法格式:HMODULE LoadLibrary(LPCTSTR lpFileName)lpFileName 参数表示被加载的动态链接库的名称(必要时需指明存储路径)。
    2. 从库文件查找要用的资源:

      • GetProcAddress() 函数可以获取库文件中的指定资源(函数、变量等)。

      • 语法格式:FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpFileName)hModule 参数用于指定动态链接库模块的句柄,即 LoadLibrary() 函数的返回值,lpFileName 参数用于指定要获取的函数或变量的名称。

    3. 卸载已加载的库文件:

      • FreeLibrary() 函数的功能恰好和 LoadLibrary() 相反,它用于卸载一个已加载的动态库。

        同一个动态库文件可以载入多次,每个动态库文件都配置一个初始值为 0 的计数器,调用 LoadLibrary() 函数加载它时,计数器加 1;调用 FreeLibrary() 函数卸载它时,计数器减 1。只有当计数器减至 0 时,动态库才会被真正地卸载掉。

      • 语法格式:BOOL FreeLibrary(HMODULE hLibModule)

  • 程序示例:

    #include<stdio.h>
    #include<stdlib.h>
    #include<windows.h>  // 必须包含 windows.h
    
    typedef int (*FUNADDR)();  // 指向函数的指针
    
    int main() {
        int a = 10, b = 5;
        //加载 myDLL.dll 动态库
        HINSTANCE dllDemo = LoadLibrary("myDLL.dll");
        FUNADDR add, sub;
        if (dllDemo) {
            //找到动态库中的 add() 和 sub() 函数
            add = (FUNADDR)GetProcAddress(dllDemo, "add");
            sub = (FUNADDR)GetProcAddress(dllDemo, "sub");
        }
        else {
            printf("Fail to load DLL!\n");
            //获取加载失败的具体原因
            printf("%d\n", GetLastError());  
            system("pause");
            exit(1);
        }
    
        printf("a+b=%d\n", add(a, b));
        printf("a-b=%d\n", sub(a, b));
        //卸载 myDLL.dll 动态库
        FreeLibrary(dllDemo);
    
        //重新加载 myDLL.dll
        dllDemo = LoadLibrary("myDLL.dll");
        FUNADDR mul, div;
        if (dllDemo) {
            mul = (FUNADDR)GetProcAddress(dllDemo, "mul");
            div = (FUNADDR)GetProcAddress(dllDemo, "div");
        }
        else {
            printf("Fail to load DLL!\n");
            system("pause");
            exit(1);
        }
    
        printf("a*b=%d\n", mul(a, b));
        printf("a/b=%d\n", div(a, b));
        FreeLibrary(dllDemo);
    
        system("pause");
        return 0;
    }
    

Linux下显式调用动态链接库

  • 隐式调用(静态调用):将动态链接库和其它源程序文件(或者目标文件)一起参与链接;
  • 显式调用(动态调用):手动调用动态链接库中包含的资源,同时用完后要手动将资源释放。
  • 显式调用动态链接库
    1. 打开库文件,要想显示调用某个动态链接库提供的资源,首先要做的就是打开该库文件:

      • 函数 dlopen()语法格式:void *dlopen (const char *filename, int flag);
        1. filename :用于指定目标动态库的存储路径和库文件名;
        2. flag 参数的值有以下 2 种:
          • RTLD_NOW:将库文件中所有的资源都载入内存;
          • RTLD_LAZY:暂时不将库文件中的资源载入内存,使用时才载入。
        3. 返回值:dlopen() 函数成功加载库文件时,会返回一个指向它的指针,这个指针在调用 dlsym()dlclose() 函数时会用到;如果加载失败,则返回 NULL。
    2. 从库文件中查找需要的资源,借助 dlsym() 函数可以获得指定函数在内存中的位置:

      • 函数dlsym() 语法格式:void *dlsym(void *handle, char *symbol);
        1. hanle :已打开的库文件的指针;
        2. symbol :用于指定要调用的函数的名称。
        3. 返回值:如果 dlsym() 函数成功找到指定函数,会返回一个指向该函数的指针;反之如果查找失败,函数会返回 NULL。
    3. 获取调用失败的原因,借助 dlerror() 函数,我们可以获得最近一次 dlopen()dlsym() 或者 dlclose() 函数操作失败的错误信息:

      • 函数 dlerror() 语法格式:const char *dlerror(void);
        1. 返回值:如果返回值为 NULL,表明上一次调用的 dlopen()dlsym()dlclose() 函数执行成功;反之,则返回相应的错误信息。
    4. 关闭打开的库文件:

      • 函数dlclose() 语法格式:int dlclose (void *handle);
        1. handle :已打开的库文件指针。
        2. 返回值:当函数返回 0 时,表示函数操作成功;反之,函数执行失败。
posted on 2024-01-22 10:36  WilliamMoa  阅读(73)  评论(0)    收藏  举报