PE3-23 静态链接库-动态链接库
静态链接库与动态链接库
静态链接库(.lib)
静态链接库(.lib)是编译时将库的代码直接链接到应用程序中的库。使用时,库的函数和符号在编译期间被解析并复制到最终的可执行文件中。
静态库使用方法:
隐式方式1:
- 将
.h
和.lib
文件复制到项目目录中。 - 在需要使用的源文件中包含
#include "xxx.h"
。 - 使用
#pragma comment(lib, "xxx.lib")
引用静态库。
隐式方式2:
- 将
.h
和.lib
文件复制到项目目录中。 - 在源文件中包含
#include "xxx.h"
。 - 在项目属性的连接器设置中,选择“输入”->“附加依赖项”,并将
.lib
文件添加到此处。
- 将
编译时的行为:
- 编译时,静态库的代码被直接打包进最终的可执行文件中,因此每次修改库文件或更新库时,都需要重新编译可执行文件。
- 这种方式的缺点是程序体积较大,且不易于更新。
平时包含一个头文件,就可以用里面的函数,且没有加#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
4int Plus(int x,int y)
{
return x+y;
}步骤3:*.def
1
2EXPORTS
Plus @12 NONAMEPlus
是要导出的函数,@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
using namespace std;
typedef int(* ptr)(int, int);
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
是代表应用程序载入的模块HINSTANCE
在win32
下与HMODULE
是相同的东西Win16
遗留HWND
是窗口句柄
其实就是一个无符号整型,Windows之所以这样设计有2个目的:
- 可读性更好
- 避免在无意中进行运算
静态链接与动态链接的区别
特点 | 静态链接库 | 动态链接库 |
---|---|---|
加载时机 | 编译时链接,静态链接库包含在EXE中 | 运行时加载,EXE需要通过DLL链接 |
可更新性 | 无法直接更新,必须重新编译 | 可以直接更新DLL文件,EXE不需要改动 |
程序大小 | 程序文件较大,因为包含了库代码 | 程序文件较小,只包含链接信息 |
性能 | 一次编译,性能较好 | 每次加载DLL会有一些性能损失 |
维护难度 | 更新库需要重新编译EXE文件 | 只需替换DLL即可更新功能 |
依赖关系 | 没有外部依赖,EXE和库绑定 | 需要确保DLL文件存在并正确加载 |
小结
- 静态链接库适合那些不会频繁更新的库,能够提升性能,但程序体积较大。
- 动态链接库更为灵活,适合需要频繁更新的库,并且能减小程序体积,但需要注意加载DLL时的依赖问题。