动态链接库的应用基础
A, 调用约定,调用约定(Calling convention)决定:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。编程调用DLL时,要注意调用约定的一致。
调用约定包括:
__cdecl
MFC默认调用约定。按从右至左的顺序压参数入栈,有调用者把参数弹出栈。对于C函数,__cdecl方式的名字修饰约定是在函数名称前添加一个下划线,对于C++函数使用不同的名字修饰方式。
__stdcall
所有的Win32 API函数都遵循该约定。按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于C函数,__stdcall的名称修饰方式是在函数名字前添加下划线,在函数名字后添加@和函数参数的大小。
__fastcall
头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈。编译器使用两个@修饰函数名字,后跟十进制数表示的函数参数列表大小。
thiscall
仅仅应用与C++成员函数。This指针存放于CX寄存器,参数从右到左压栈。Thiscall不是C++的关键字,不能使用thiscall声明函数,它只能由编译器使用。
naked call
采用上述调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。Naked call不产生这样的代码。Naked call不是类型修饰符,因此必须和__declspec共同使用。
Windows编程中常见的集中函数类型声明宏都与__stdcall和__cdecl有关:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
对于C++编译器的函数名修饰规则:不管__cdecl, __fastcall还是__stdcall调用方式,函数修饰名都是以”?”开始,后面是函数在名字,再后面是参数表的开始标示和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标示是”@@YG”,对于__cdecl方式则是”@@YA”,对于__fastcall方式则是”@@YI”.
参数表的拼写代号
拼写代号
含义
拼写代号
含义
X
void
D
Char
E
Unsigned char
F
Short
H
Int
I
Unsigned int
J
Long
K
Unsigned long (DWORD)
M
Float
N
Double
_N
Bool
U
Struct
PA
指针(后面的代号表明指针类型)
PB
Const类型指针(后面的代号表明指针类型)
函数的返回值不作特殊处理。参数表后以”@Z”标示整个名字的结束,如果该函数无参数,则以”Z”标识结束。
B, 导出和导入函数
DLL内的函数分为两种:
DLL导出函数,可供应用程序调用
DLL内部函数,只能在DLL内部使用,应用程序无法调用它们。
对于导出函数,DLL文件包含一个导出函数表,它包含了DLL中函数的符号名,标识符和实际的地址,这些信息可以使用Depends工具查到。当应用程序加载DLL时动态建立一个函数调用与函数地址的对应表,所以应用程序并不需要知道要调用函数的实际地址,可以通过函数的符号名和标识号来调用函数。