调用约定是指程序在函数调用时传递參数和获取返回值所採用的方法:通过寄存器、或通过栈、或者是两者的混合。
用于指定Calling Convention的修饰符主要有:__cdecl,__stdcall,__fastcall等。调用约定能够通过project设置:Setting...C/C++ AdvancedCallingConvention 项进行选择,缺省状态为__cdecl。它们决定下面内容:
1)、函数參数的压栈顺序。
2)、由调用者还是被调用者把參数弹出栈,
3)、以及产生函数修饰名的方法。
它们的各自特征例如以下(以VS2005工具为例):
1、_cdecl是C和C++程序的缺省调用方式:
1.1、參数按从右到左的顺序传递,放于栈中
1.2、栈的清空由主调函数完毕
1.3、在生成的汇编代码中。函数名下面划线_ 开头
编译选项:/Gd。 对于变參函数,如printf,仅仅能用这样的方式
2、标准的调用约定 __stdcall:
2.1、函数的參数自右向左通过栈传递
2.2、被调用的函数在返回前清理传送參数的内存栈
2.3、在生成的汇编代码中。函数名下面划线_ 开头。以@和全部參数所占用的字节数结尾。
如call _sumExample@8
编译选项:/Gz。Win32程序中的WINAPI即是__stdcall:#define WINAPI __stdcall。因为栈是由被调函数自己清空。其产生的运行代码要小于__cdecl方式所产生的代码。
3、__fastcall:
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)