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