static library 在编译的时候是hardcopy,

缺点就是如果library更新了,那么软件需要重新编译

1
2
#include "Your Static Library" //首先导入header文件,知道要用什么function
#pragma comment(lib, "xxxxx.lib") //再导入lib文件,编译的时候知道在哪找到function

DLL 是动态链接的,所以当dll更新了之后,软件不需要重新编译:

原理就是exe在载入的时候读取导入表,导入dll到内存空间。exe在使用dll里面的function的时候直接call加载到内存的dll内存空间里面的function。

创建dll的时候如下:

1
2
3
4
extern "C" _declspec(dllexport) int __stdcall Plus(int x, int y);
extern "C" _declspec(dllexport) int __stdcall Minus(int x, int y);
extern "C" _declspec(dllexport) int __stdcall Multi(int x, int y);
extern "C" _declspec(dllexport) int __stdcall Divide(int x, int y);

extern “C”:代表以C的方式编译,这样避免编译器自动修改函数名。C++编译方式会自动在函数名后再加一些东西避免重载

_declspec(dllexport): 让编译器知道这个函数是dll导出

__stdcall: 堆栈平衡时内平衡。Windows常用 __stdcall

使用dll分为两种方式:

隐式连接:

1
2
3
4
5
#pragma comment(lib, "DllTest.lib")
extern "C" _declspec(dllimport) int __stdcall Plus(int x, int y);
extern "C" _declspec(dllimport) int __stdcall Minus(int x, int y);
extern "C" _declspec(dllimport) int __stdcall Multi(int x, int y);
extern "C" _declspec(dllimport) int __stdcall Divide(int x, int y);

_declspec(dllimport): 这个声明了这个函数需要导入dll,让编译器自己去找。所以叫做隐式连接。

导入的lib里面存放了dll信息,所以编译器可以找到dll

显式连接:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <Windows.h>

// 先定义函数指针类型
typedef int (__stdcall *lpPlus)(int,int);
typedef int (__stdcall *lpMinus)(int,int);
typedef int (__stdcall *lpMulti)(int,int);
typedef int (__stdcall *lpDivide)(int,int);


int main()
{
// 定义函数指针
lpPlus myPlus;
lpMinus myMinus;
lpMulti myMulti;
lpDivide myDivide;

// 导入Dll,并获得Dll导入的地址
HMODULE hModule = LoadLibrary(TEXT("DllTest.dll"));
// 从Dll导入的地址获取函数
myDivide = (lpDivide)GetProcAddress(hModule, "_Divide@8");
myPlus = (lpPlus)GetProcAddress(hModule, "_Plus@8");
myMulti = (lpMulti)GetProcAddress(hModule, "_Multi@8");
myMinus = (lpMinus)GetProcAddress(hModule, "_Minus@8");
//使用函数
int v = myPlus(3, 2);
std::cout << v << std::endl;
}

如果正常导出,dll里面的名称和函数都是暴露的。

我们可以通过创建Module Def文件来设置dll导出时函数的名称和名字 (相当于头文件,但是这个头文件可以设置导出函数的名称和id让类似Depenency Walker不能读取到)

1
2
3
4
5
6
EXPORTS

Plus @12
Minus @15 NONAME
Multi @13
Divide @16

我们用Dependency Walker查一下生成的dll:

0.png

我们可以看到ordinal 已经被我们改变,并且Minus这个函数没有名字,无法通过GetProcAddress调用。

Conclusion:

Static library 只是简单的copy代码到exe文件里面,写死了。

Dll 提供一张导出表,给exe用。那么exe需要调用这个dll的时候只需要在导出表里写上需要使用的dll名和函数名就可以加载并运行dll中的代码。所以就算dll更新了,每次打开exe都会重新加载dll进入内存,那么更新的dll就加载进内存了,只需要通过函数名在dll的导出表里面找地址就又可以使用dll里面的函数了。只要dll中function的名字和参数不变,那么exe依然可以使用dll的更新过的函数。

动态连接,就是当所有程序(exe, dll…)都加载完毕后,再连接函数,通过IAT连接

动态链接过程如下:

1.png