最近在看《windows程序设计》一书,在书中看到使用windows函数的时候,要在函数前面加上WINAPI这样一个关键字。WINAPI 是在WINDEF.H中定义的,其定义如下
#define WINAPI __stdcall
其中__stdcall是一种函数调用的约定。
首先,这里要清楚的第一个问题是:什么是函数调用的约定?
通过在网上搜索,好搜百科给出了如下的定义:
函数调用约定,是指当一个函数被调用时,函数的参数会传递给被调用的函数和返回值会被返回给调用函数。函数的调用约定就是描述参数是怎么传递和由谁平衡堆栈的,当然还有返回值。
其中常用的函数约定有三种类型,分别是:
__pascal,
__stdcall,
__cdecl,
他们之间的区别如下表所示:
参数传递顺序 | 谁负责清理参数占用的堆栈 | |
__pascal | 从左到有 | 调用者 |
__stdcall | 从右到左 | 被调函数 |
__cdecl | 从右到左 | 调用者 |
调用函数的代码和被调用函数必须采用相同的函数调用约定,程序才能正常运行。在Windows上,__cdecl是c/c++程序的缺省函数调用约定。
在有的CPU上,编译器会用寄存器传递参数,函数使用的堆栈由被调用函数分配和释放。这种调用约定在行为上和__cdecl有一个共同点:实参和形参数目不符不会导致堆栈错误。
不过,即使用寄存器传递参数,编译器在进入函数时,还是会将寄存器里的参数存入堆栈指定的位置。参数和局部变量一样应该在堆栈中有一席之地。参数可以被理解为由调用函数指定的局部变量。
其中,VC默认使用__cdecl。所以如果需要使用__stdcall,可采用两种方法:
(1)可以在函数名前手工添加,只对单一函数有效
(2)直接修改工程属性(C/C++ > Advanced > Calling Convention)来一次性配置所有的函数
__cdecl可实现变长参数列表
__stdcall产生的代码更小
__cdecl的运行速度更快,这和内联函数有点类似,代码越多当然运行的越快
__cdecl主调用函数进行参数压栈并且恢复堆栈
__stdcall主调用函数进行参数压栈,被调函数恢复堆栈
所以如果使用__cdecl的函数多次调用同一函数,就产生多分恢复码
一份恢复码只能将一种长度的参数表出栈,所以要对不同长度的参数表堆栈恢复,必须要有多分恢复码,所以变长参数列表必须由主调函数恢复堆栈
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个'@'符号和其参数的字节数,格式为:_functionname@number
__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为:_functionname
__stdcall通常用于DLL的创建(以支持多语言的调用),此外Win32API函数皆用__stdcall,所以Win32程序中的自定义函数也最好使用__stdcall
__cdecl非DLL的Console程序