静态链接库与动态链接库

静态链接库(.lib)

静态链接库(.lib)是编译时将库的代码直接链接到应用程序中的库。使用时,库的函数和符号在编译期间被解析并复制到最终的可执行文件中。

  1. 静态库使用方法

    隐式方式1:

    • .h.lib 文件复制到项目目录中。
    • 在需要使用的源文件中包含 #include "xxx.h"
    • 使用 #pragma comment(lib, "xxx.lib") 引用静态库。

    隐式方式2:

    • .h.lib 文件复制到项目目录中。
    • 在源文件中包含 #include "xxx.h"
    • 在项目属性的连接器设置中,选择“输入”->“附加依赖项”,并将 .lib 文件添加到此处。
  2. 编译时的行为

    • 编译时,静态库的代码被直接打包进最终的可执行文件中,因此每次修改库文件或更新库时,都需要重新编译可执行文件。
    • 这种方式的缺点是程序体积较大,且不易于更新。

平时包含一个头文件,就可以用里面的函数,且没有加#pragma comment(lib,"xxx.lib"),这是因为编译器已经把我们常用的库放在了项目下,所以我们只需要加个头文件就能用

如果用OD打开静态编译程序,发现lib也是包含在exe文件里的,并没有单独分出模块,说明静态链接库直接编译进exe文件里,所以要更新exe应用的话,就需要重新编译,不太灵活

动态链接库(.dll)

动态链接库(DLL)是运行时加载的库,不同于静态库,DLL文件在编译时不会被嵌入到可执行文件中,而是按需加载的。

动态链接库也有lib,但是二进制代码并不在lib中,此时动态链接库的lib只放了代码在哪里的信息。

  • 动态链接库导出函数

    编写DLL时,导出函数需要使用 __declspec(dllexport) 来告诉编译器哪些函数需要被导出。

    1
    extern "C" __declspec(dllexport) int plus(int x, int y);
    • extern "C":告诉编译器按C语言约定进行名称修饰,防止C++名称重载。
    • __declspec(dllexport):告诉编译器这是一个导出的函数。
  • 使用动态链接库

    方式1:隐式连接

    • .dll.lib 文件放置在项目目录中。
    • 使用 #pragma comment(lib, "DLL名.lib") 引用动态链接库。
    • 在代码中声明导入的函数,使用 __declspec(dllimport) 指明是导入函数:
    1
    extern "C" __declspec(dllimport) int Plus(int x, int y);

    方式2:显式连接
    使用 LoadLibrary 动态加载DLL并获取函数地址。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //步骤1:定义函数指针
    typedef int(__stdcall *lpPlus)(int, int);
    typedef int(__stdcall *lpSub)(int, int);
    typedef int(__stdcall *lpMul)(int,int);
    typedef int(__stdcall *lpDiv)(int,int);
    //步骤2:声明函数指针变量
    lpPlus myPlus;
    lpSub mySub;
    lpMul myMul;
    lpDiv myDiv;
    //步骤3:动态加载dll到内存中
    HINSTANCE hModule = LoadLibrary(L"DllDemo.dll");
    //步骤4:获取函数地址(去库里面找对应的函数)
    //(因为用了__stdcall,因此函数名会有变化,函数名前面会加一个下划线,然后最后补一个参数类型大小之和)
    myPlus = (lpPlus)GetProcAddress(hModule, "_Plus@8");
    mySub = (lpSub)GetProcAddress(hModule, "_Sub@8");
    myMul = (lpMul)GetProcAddress(hModule, "_Mul@8");
    myDiv = (lpDiv)GetProcAddress(hModule, "_Div@8");
    //步骤5:调用函数
    int a = myPlus(10,2);
    int b = mySub(10,2);
    int c = myMul(10,2);
    int d = myDiv(10,2);
  • 使用 .def 文件导出

    有时,导出函数可以通过 .def 文件来管理,而不是直接在代码中使用 __declspec(dllexport)。这可以简化管理,但需要手动设置。

    步骤1:*.h头文件
    举例:
    int Plus (int x,int y);
    (不再使用 extern "C" __declspec(dllimport) __stdcall int Plus (int x,int y); )

    步骤2. *.cpp文件
    举例:

    1
    2
    3
    4
    int Plus(int x,int y)
    {
    return x+y;
    }

    步骤3:*.def

    1
    2
    EXPORTS
    Plus @12 NONAME

    Plus 是要导出的函数,@12 表示函数的序号,NONAME 表示没有名字,仅通过序号进行访问。

  • 加载DLL: 在运行时,程序通过 LoadLibrary 加载DLL,并通过 GetProcAddress 获取函数地址。注意,LoadLibrary 使用宽字符字符串(L"DllName.dll"),并且 GetProcAddress 中的函数名必须包含参数大小(如 "_Plus@8")。

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <windows.h>
    #include <iostream>
    using namespace std;
    typedef int(* ptr)(int, int);
    #pragma comment(lib,"Dll1.lib")

    int main()
    {
    ptr my_ptr=nullptr;
    HINSTANCE hModule = LoadLibrary(L"Dll1.dll");
    my_ptr = (ptr)GetProcAddress(hModule, "plus");
    my_ptr(1,2);
    return 0;
    }

    注:HINSTANCE hModule = LoadLibrary(L"Dll1.dll"); 字符串外面加一个L才行

    特别说明:

    • Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄
    • HMODULE 是代表应用程序载入的模块
    • HINSTANCEwin32下与HMODULE是相同的东西 Win16 遗留
    • HWND 是窗口句柄

    其实就是一个无符号整型,Windows之所以这样设计有2个目的:

    1. 可读性更好
    2. 避免在无意中进行运算

静态链接与动态链接的区别

特点 静态链接库 动态链接库
加载时机 编译时链接,静态链接库包含在EXE中 运行时加载,EXE需要通过DLL链接
可更新性 无法直接更新,必须重新编译 可以直接更新DLL文件,EXE不需要改动
程序大小 程序文件较大,因为包含了库代码 程序文件较小,只包含链接信息
性能 一次编译,性能较好 每次加载DLL会有一些性能损失
维护难度 更新库需要重新编译EXE文件 只需替换DLL即可更新功能
依赖关系 没有外部依赖,EXE和库绑定 需要确保DLL文件存在并正确加载

小结

  • 静态链接库适合那些不会频繁更新的库,能够提升性能,但程序体积较大。
  • 动态链接库更为灵活,适合需要频繁更新的库,并且能减小程序体积,但需要注意加载DLL时的依赖问题。