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++中使用,参数压栈顺序与前两者相反。返回时的清栈方式与_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