1.
_cdecl
(1).
是
C Declaration
的缩写,表示
C
语言默认的函数调用方法,实际上也是
C++
的默认的函数调用方法。
(2).
所有参数从右到左依次入栈,这些
参数由调用者清除
,称为手动清栈。具体所示:
调用方的函数调用
->
被调用函数的执行
->
被调用函数的结果返回
->
调用方清除调整堆栈
。
(3).
被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。
总的来说函数的参数个数可变的
(
就像
printf
函数一样
)
,因为只有调用者才知道它传给被调用函数几个参数,才能在调用结束时适当地调整堆栈。
(4).
因为每个调用的地方都需要生成一段调整堆栈的代码,所以最后生成的文件较大。
2.
_stdcall(CALLBACK/WINAPI)
(1).
是
Standard Call
的缩写,要想函数按照此调用方式必须在函数名加入
_stdcall
,通常
_ win32 api
应该是
_stdcall
调用规则
。通过
VC++
编写的
DLL
欲被其他语言编写的程序调用,应将函数的调用方式声明为
_stdcall
方式,
WINAPI
都采用这种方式。
(2).
所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是
this
指针。具体所示:
调用方的函数调用
->
被调用函数的执行
->
被调用方清除调整堆栈
->
被调用函数的结果返回
。
(3).
这些堆栈中的参数由
被调用的函数在返回后清除
,使用的指令是
retn X
,
X
表示参数占用的字节数,
CPU
在
ret
之后自动弹出
X
个字节的堆栈空间。称为自动清栈。
(4).
函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。总的来说,
就是函数的参数个数不能是可变的
。是从
_cdecl
修改而来
, _stdcall
不支持可变参数
,
并且清栈由被调用者负责
,
其他的都一样
(5).
因为只需在被调用函数的地方生成一段调整堆栈的代码,所以最后生成的文件较小。
3
.
PASCAL
是
Pascal
语言的函数调用方式,也可以在
C/C++
中使用,参数压栈顺序与前两者相反。返回时的清栈方式忘记了。。。
4.
_fastcall
是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此
_fastcall
通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和
_stdcall
相当。
5.
_thiscall
是为了解决类成员调用中
this
指针传递而规定的。
_thiscall
要求把
this
指针放在特定寄存器中,该寄存器由编译器决定。
VC
使用
ecx
,
Borland
的
C++
编译器使用
eax
。返回方式和
_stdcall
相当。
6.
_fastcall
和
_thiscall
涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以
Windows
上的
COM
对象接口都定义为
_stdcall
调用方式。
7.
C
中不加说明默认函数为
_cdecl
方式(
C
中也只能用这种方式),
C++
也一样,但是默认的调用方式可以在
IDE
环境中设置。
8.
带有可变参数的函数必须且只能使用
_cdecl
方式,例如下面的函数
:
int printf(char * fmtStr, ...);
int scanf(char * fmtStr, ...);
9.
函数名修饰
(1). _cdecl
:对于
_cdecl
而言,如果对于定义在
C
程序文件
(
编译器会通过后缀名为
.C
判断
)
的输出函数,函数名会保持原样;对于定义在
C++
程序文件中的输出函数,函数名会被修饰
(
见
10)
。为使函数名不被修饰,有两种方法:
A.
可通过在前面加上
extern “c”
以去除函数名修饰;
B.
可通过
.def
文件去除函数名修饰。
(2). _stdcall
:无论是
C
程序文件中的输出函数还是
C++
程序文件中的输出函数,函数名都会被修饰。对于定义在
C++
程序文件中的输出函数,好像更复杂,和
_cdecl
的情况类似。去除函数名修饰方法:只能通过
.def
文件去除函数名修饰。
10.
函数名修饰规则:
(1).
为什么要函数名修饰:
函数名修饰就是编译器在编译期间创建的一个字符串,用来指明函数的定义和原型。
LINK
程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。多少情况下程序员并不需要知道函数的名字修饰,
LINK
程序或其他工具会自动区分他们。当然,在某些情况下需要指定函数名修饰,例如在
c++
程序中,为了让
LINK
程序或其他工具能够匹配到正确的函数名字,就必须为重载函数后一些特殊函数
(
如构造函数和析构函数
)
指定名字修饰。另一种需要指定函数名修饰的情况是在汇编程序中调用
C
或
C++
函数。
(2). C
语言:
对于
_stdcall
调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个
“@”
符号和其参数的字节数,例如
_functionname@number
。
_cdecl
调用约定仅在输出函数名前加上一个下划线前缀,例如
_functionname
。
_fastcall
调用约定在输出函数名前加上一个
“@“
符号,后面也是一个
”@“
符号和其参数的字节数,例如
@functionname@number
。
(3). C++
语言:
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
类型的指针。后面的代号表明指针类型,如果相同类型的指针连续出现,以
“0”
代替,一个
“0”
代表一次重复。
U
表示结构类型,通常后跟结构体的类型名,用
“@@”
表示结构类型名的结束。函数的返回值不作特殊处理,它的描述方式和函数参数一样,紧跟着参数表的开始标志,也就是说,函数参数表的第一项实际上是表示函数的返回值类型。参数表后以
“@Z”
标识整个名字的结束,如果该函数无参数,则以
“Z”
标识结束。下面举两个例子,假如有以下函数声明:
int Function1(char *var1,unsigned long);
其函数修饰名为
“?Function1@@YGHPADK@Z”
,而对于函数声明:
oid Function2();
其函数修饰名则为
“?Function2@@YGXXZ”
。
对于
C++
的类成员函数(其调用方式是
thiscall
),函数的名字修饰与非成员的
C++
函数稍有不同,首先就是在函数名字和参数表之间插入以
“@”
字符引导的类名;其次是参数表的开始标识不同,公有(
public
)成员函数的标识是
“@@QAE”,
保护(
protected
)成员函数的标识是
“@@IAE”,
私有(
private
)成员函数的标识是
“@@AAE”
,如果函数声明使用了
const
关键字,则相应的标识应分别为
“@@QBE”
,
“@@IBE”
和
“@@ABE”
。如果参数类型是类实例的引用,则使用
“AAV1”
,对于
const
类型的引用,则使用
“ABV1”
。
11.
查看函数的名字修饰
有两种方式可以检查你的程序中的函数的名字修饰:使用编译输出列表或使用
Dumpbin
工具。使用
/FAc
,
/FAs
或
/FAcs
命令行参数可以让编译器输出函数或变量名字列表。使用
dumpbin.exe /SYMBOLS
命令也可以获得
obj
文件或
lib
文件中的函数或变量名字列表。此外,还可以使用
undname.exe
将修饰名转换为未修饰形式。
12.
_beginthread
需要
_cdecl
的线程函数地址,
_beginthreadex
和
_CreateThread
需要
_stdcall
的线程函数地址。
13.
#define CALLBACK __stdcall //
这就是传说中的回调函数
#define WINAPI __stdcall //
这就是传说中的
WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain
的入口就在这里
#define APIPRIVATE __stdcall
#define PASCAL __stdcall