第八章不是很感兴趣,直接跳到第九章。
DLL即动态链接库(Dynamic Link Library)的缩写.
一个DLL中有两个数据段,一个进程间共享,另一个私有.
当我们使用“__declspec(dllexport)”时表示该符号是从本DLL导出的符号。“__declspec(dllimport)”表示该符号是从别的DLL导入的符号。
创建DLL:
/*math.c*/
__declspec(dllexport) double Add(double a, double b)
{
return a+b;
}
__declspec(dllexport) double Sub(double a, double b)
{
return a-b;
}
__declspec(dllexport) double Mul(double a, double b)
{
return a*b;
}
使用cl编译器进行编译:
cl /LDd Math.c
参数/LDd表示产生Debug版的DLL,不加任何参数则表示产生EXE可执行文件,可以使用/LD来编译生成Release版的DLL。
通过dumpbin 查看DLL的导出符号:
D:\Program Files\home\*>dumpbin /EXPORTS Math.dll
Microsoft (R) COFF/PE Dumper Version 11.00.50214.1
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file Math.dll
File Type: DLL
Section contains the following exports for math.dll
00000000 characteristics
4F878A19 time date stamp Fri Apr 13 10:06:17 2012
0.00 version
1 ordinal base
3 number of functions
3 number of names
ordinal hint RVA name //三个导出函数即相对地址
1 0 00001000 Add
2 1 00001040 Mul
3 2 00001020 Sub
Summary
3000 .data
9000 .rdata
3000 .reloc
1E000 .text
使用DLL:
/*test_math.c*/
#include <stdio.h>
__declspec(dllexport) double Sub(double a, double b);
int main(int argc, char *argv[])
{
double result=Sub(3.0, 2.0);
printf("result=%f\n", result);
return 0;
}
编译:
D:\Program Files\home\*>cl /c test_math.c
D:\Program Files\home\*> link test_math.obj math.lib
声明DLL中的某个函数为导出函数的办法有两种,使用“__declspec(dllexport)”扩展;另一种就是采用模块定义(.def)文件声明。
创建math.def文件:
/*math.def*/
LIBRARY Math
EXPORTS
Add
Sub
Mul
Div
编译:
D:\Program Files\home\*>cl math.c /LD /DEF math.def
最终输出跟第一种方法相同的结果。
DLL支持运行时链接,Windows提供了3个API为:
·LoadLibrary,这个函数用来装载一个DLL到进程的地址空间,它的功能跟dlopen类似。
·GetProcAddress, 用来查找某个符号的地址,与dlsym类似
·FreeLibrary, 用来卸载某个已加载的模块,与dlclose类似
/*RunDllSimple.c*/
#include <windows.h>
#include <stdio.h>
typedef double (*Func)(double, double);
int main(int argc, char *argv[])
{
Func function;
double result;
HINSTANCE hinstLib=LoadLibrary("math.dll");
if (hinstLib==NULL)
{
printf("ERROR:unable to load DLL.\n");
return 1;
}
function=(Func)GetProcAddress(hinstLib, "Add");
if (function==NULL)
{
printf("ERROR:unable to find DLL function.\n");
FreeLibrary(hinstLib);
return 1;
}
result=function(1.0, 2.0);
FreeLibrary(hinstLib);
printf("result=%f\n", result);
return 0;
}
D:\Program Files\home\*>cl RunDllSimple.c
D:\Program Files\home\*>RunDllSimple.exe
result = 3.000000
符号导出导入表:
当一个PE需要将一些函数或变量提供给其他PE文件使用时,我们把这种行为叫做符号导出。导出表提供了一个符号名与符号地址的映射关系。
在ELF中,“.rel.dyn”和“.rel.plt”两个段中分别保存了该模块所需要导入的变量和函数的符号以及所在的模块等信息。而“.got”和“.got.plt”则保存着这些变量和函数的真正地址。
查看依赖了哪些DLL:
D:\Program Files\home\*>dumpbin /IMPORTS math.dll
我们的math.dll导入了KERNEL32.dll
在PE文件中,导入表是一个IMAGE_IMPORT_DESCRIPTOR的结构数组,每个IMAGE_IMPORT_DESCRIPTOR结构对应一个被导入的DLL。
EXE文件的基地址默认为0x00400000,而DLL文件基地址默认为0x10000000
DLL绑定:把导出函数的地址保存到模块的导入表中,可以省去每次启动时符号解析的过程。
DLL绑定的方法:
D:\Program Files\home\*>editbin /BIND test_math.exe
Microsoft (R) COFF/PE Editor Version 11.00.50214.1
Copyright (C) Microsoft Corporation. All rights reserved.
D:\Program Files\home\*>dumpbin /IMPORTS test_math.exe
Microsoft (R) COFF/PE Dumper Version 11.00.50214.1
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file test_math.exe
File Type: EXECUTABLE IMAGE
Section contains the following imports:
math.dll
40C100 Import Address Table
40DF30 Import Name Table
FFFFFFFF time date stamp
FFFFFFFF Index of first forwarder reference
10001020 2 Sub
KERNEL32.dll
40C000 Import Address Table
40DE30 Import Name Table
FFFFFFFF time date stamp
FFFFFFFF Index of first forwarder reference
77E330E2 540 SetUnhandledExceptionFilter
77E30AFD D0 CreateFileW
77E3296C 1DA GetCommandLineA
….
….
DLL绑定实现,editbin对被绑定的程序的导入符号进行遍历查找,找到以后就把符号的运行时的目标地址写入到被绑定程序的导入表内。
c++与动态链接
指导意见:
(1)所有接口函数都应该是抽象的。所有的方法都应该是纯虚的。(或者inline的方法也可以)
(2)所有的全局函数都应该使用extern “C”来防止名字修饰的不兼容。并且导出函数的都应该是__stdcall调用规范(COM的DLL都使用这样的规范)。这样即使用户本身的程序默认以__cdecl方式编译的,对于DLL的调用也能正确。
(3)不要使用C++标准库STL
(4)不要使用异常
(5)不要使用虚析构函数。可以创建一个destroy()方法并且重载delete操作符并且调用destroy()
(6)不要在DLL里面申请内存。而且在DLL外释放(或者相反)。不同的DLL和可执行文件可能使用不同的堆,在一个堆里面申请内存而在另外一个堆里面释放会导致错误。比如,对于内存分配相关的函数不应该是inline的,以防止它在编译时被展开到不同的DLL和可执行文件
(7)不要在接口中使用重载方法。因为不同的编译器对于vtable的安排可能不同。
3. DLL HELL(dll噩梦)
预防方法:
静态链接
防止DLL覆盖(windows文件保护实现)
避免DLL冲突(让每个应用程序拥有自己依赖的DLL)
.Net下DLL Hell的解决方案(Manifest,XML描述文件,(side-by-side manager)SxS Manager实现对相应版本的DLL的加载)