调用约定是指程序在函数调用时传递参数和获取返回值所采用的方法:通过寄存器、或通过栈、或者是两者的混合。用于指定Calling Convention的修饰符主要有:__cdecl,__stdcall,__fastcall等。调用约定可以通过工程设置:Setting...\C/C++ \Advanced\CallingConvention 项进行选择,缺省状态为__cdecl。它们决定以下内容:
1)、函数参数的压栈顺序,
2)、由调用者还是被调用者把参数弹出栈,
3)、以及产生函数修饰名的方法。
它们的各自特征如下(以VS2005工具为例):
1.1、参数按从右到左的顺序传递,放于栈中
1.2、栈的清空由主调函数完成
1.3、在生成的汇编代码中,函数名以下划线_ 开头
编译选项:/Gd, 对于变参函数,如printf,只能用这种方式
2.1、函数的参数自右向左通过栈传递
2.2、被调用的函数在返回前清理传送参数的内存栈
2.3、在生成的汇编代码中,函数名以下划线_ 开头,以@和所有参数所占用的字节数结尾。如call _sumExample@8
编译选项:/Gz,Win32程序中的WINAPI即是__stdcall:#define WINAPI __stdcall,由于栈是由被调函数自己清空,其产生的执行代码要小于__cdecl方式所产生的代码。
3.1、前两个参数要求不超过32bits,分别放入ECX和EDX,其余参数按从右到左的顺序传递,放于栈中
3.2、参数由被调函数弹出栈
3.3、在生成的汇编代码中,函数名以@开头,以@和所有参数所占用的字节数结尾
编译选项:/Gr
除这三种之外,还有Thiscall,但它仅用于C++中类的成员函数:
1、参数按从右到左的顺序传递,放于栈中。this放于ECX中
2、栈的清空有被调函数完成
这是C++中类成员函数默认的calling convention。但如果类的成员函数包含可变参数,那该函数的调用约定则是__cdecl。
C 和C++ 对应不同的调用约定,产生的修饰符也各不相同,对于函数test(void)如下:
调用约定 |
extern "C" 或 .c 文件 |
.cpp、.cxx 或 /TP |
__cdecl |
_test |
?test@@ZAXXZ |
__fastcall |
@test@0 |
?test@@YIXXZ |
__stdcall |
_test@0 |
?test@@YGXXZ |
C++编译时函数名修饰约定规则:
__stdcall调用约定:
1)、以"?"标识函数名的开始,后跟函数名;
2)、函数名后面以"@@YG"标识参数表的开始,后跟参数表;
3)、参数表以代号表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以"0"代替,一个"0"代表一次重复;
4)、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数,则以"Z"标识结束。
其格式为"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",例如
intTest1(char *var1,unsigned long)----"?Test1@@YGHPADK@Z"
voidTest2()-----“?Test2@@YGXXZ”
__cdecl调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YA"。
__fastcall调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的"@@YG"变为"@@YI"。
VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用。
附:一个DLL在内存中只有一个实例
DLL程序和调用其输出函数的程序的关系:
1)、DLL与进程、线程之间的关系
DLL模块被映射到调用它的进程的虚拟地址空间。
DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。
DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。
DLLDLL可以有自己的数据段,但没有自己的堆栈,使用调用进程的栈,与调用它的应用程序相同的堆栈模式。
2)、关于共享数据段
DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread LocalStrorage)