在第一篇技术博客"动态链接库简介"中说到了调用约定对函数名改编的影响,当时没有详细说明,这里跟大家一起讨论一下调用约定
stdcall
cdecl
fastcall
thiscall
naked call
VC中的定义
#defineCALLBACK __stdcall
#defineWINAPI __stdcall
#defineWINAPIV __cdecl
#defineAPIENTRY WINAPI
#defineAPIPRIVATE __stdcall
#definePASCAL __stdcall
#definecdecl _cdecl
#ifndefCDECL
#defineCDECL _cdecl
#endif
函数参数的压栈顺序
由调用者还是被调用者把参数弹出栈
产生函数修饰名的方法(由调用约定和编译方式共同决定)
standard call,标准调用约定,就是WINAPI的调用约定,Pascal程序的缺省调用方式,通常用于windowsAPI中,Delphi调用约定,每一个WINDOWSAPI函数都是__stdcall类型的
windows API都是函数参数个数固定。所以由函数自身清理堆栈由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。
2. 函数采用从右到左的压栈方式
3. 被调函数(函数自己)在退出时清空堆栈
函数修饰名随调用约定和编译种类(C或C++)的不同而变化
C编译方式
在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。
格式为:_functionname@number
C++编译方式
__stdcall调用约定:
1、以“?”标识函数名的开始,后跟函数名;
2、函数名后面以“@@YG”标识参数表的开始,后跟参数表;
3、参数表以代号表示:
X--void ,
D--char,
E--unsignedchar,
F--short,
H--int,
I--unsignedint,
J--long,
K--unsignedlong,
M--float,
N--double,
_N--bool,
....
PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;
4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。
其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”
例如
int Test1(char*var1,unsigned long)-----“?Test1@@YGHPADK@Z”
void Test2()-----“?Test2@@YGXXZ”
示例:
导出到DLL,查看
__stdcall可以写成_stdcall
在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现)。
那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用函数自己事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。
1. 也叫做C调用约定(是C Declaration的缩写(declaration,声明))
_cdecl:C和C++程序的缺省调用约定(也是VC默认的方式)
__cdecl:C/C++和MFC程序默认使用的调用约定
由于C/C++中函数可以是可变参数的,所以由调用函数清理堆栈
2. 函数参数按照从右到左的顺序入栈
3. 由调用函数把参数弹出栈以清理堆栈(实现可变参数的函数只能使用该调用约定)
4. 函数修饰名随调用约定和编译种类(C或C++)的不同而变化
C 编译方式
输出函数名不变。
VS编译的时候,还是原函数名
C++编译方式
__cdecl调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。
导出函数到DLL文件,查看
由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大
当遇到可变参数的时候,使用_cdecl
程序中没有涉及可变参数,最好使用_stdcall关键字
用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall
C编译方式
__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。
导出函数到DLL文件
C++编译时
__fastcall调用约定:
规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。
导出函数到DLL文件
thiscall仅仅应用于"C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。
naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。
到这里,关于第一篇博客"动态链接库简介"里的全部内容都写完了,由于当时笔记整理的比较乱,各位博友如果发现有不足和错误的地方,希望能提出宝贵的意见和建议