__stdcall
这些天我碰到很多东西。
MSDN并未非常清楚地解释其真正含义,何时以及为何使用它(如果有的话)。
如果有人提供解释,最好提供一个或两个示例,我将不胜感激。
Answers:
C / C ++中的所有函数都有特定的调用约定。调用约定的重点是确定如何在调用者和被调用者之间传递数据,以及负责执行诸如清除调用堆栈之类的操作的人员。
Windows上最流行的呼叫约定是
__stdcall
,以相反的顺序(从右到左)将参数压入堆栈__cdecl
,以相反的顺序(从右到左)将参数压入堆栈__clrcall
,按顺序(从左到右)将参数加载到CLR表达式堆栈上。__fastcall
,存储在寄存器中,然后压入堆栈__thiscall
,推入堆栈;该指针存储在ECX中将此说明符添加到函数声明中,实际上告诉编译器您希望该特定函数具有此特定调用约定。
调用约定记录在这里
从这里开始,Raymond Chen还对各种调用约定的历史做了很长的系列讨论(共五部分)。
传统上,C函数调用是通过调用方将一些参数推入堆栈,调用该函数,然后弹出堆栈以清理那些推入的参数来进行的。
/* example of __cdecl */
push arg1
push arg2
push arg3
call function
add sp,12 // effectively "pop; pop; pop"
注意:如上所示,默认约定称为__cdecl。
另一个最受欢迎的约定是__stdcall。在其中,参数再次由调用方推入,但堆栈由被调用方清除。它是Win32 API函数的标准约定(由中的WINAPI宏定义),有时也称为“ Pascal”调用约定。
/* example of __stdcall */
push arg1
push arg2
push arg3
call function // no stack cleanup - callee does this
这看起来像是次要的技术细节,但是如果在调用方和被调用方之间如何管理堆栈方面存在分歧,则堆栈将以无法恢复的方式被破坏。由于__stdcall确实进行了堆栈清理,因此仅在一个位置找到执行此任务的(很小)代码,而不是像在__cdecl中那样在每个调用方中都重复该代码。尽管大小影响仅在大型程序中可见,但这使代码非常小。
像stfcall这样的可变参数函数几乎不可能用__stdcall正确实现,因为只有调用者才真正知道为清除它们而传递了多少个参数。被调用方可以做出一些很好的猜测(例如,通过查看格式字符串),但是堆栈清理必须由函数的实际逻辑而不是调用约定机制本身来确定。因此,只有__cdecl支持可变参数函数,以便调用者可以执行清理。
链接器符号名称修饰:如上面的项目要点所述,使用“错误的”约定调用函数可能是灾难性的,因此Microsoft提供了一种避免这种情况发生的机制。尽管人们可能不知道原因是什么,但它可能会令人发疯。他们选择通过使用额外的字符(通常称为“装饰”)将调用约定编码为低级函数名称来解决此问题,并且链接程序将这些名称视为不相关的名称。默认的调用约定是__cdecl,但是可以使用/ G显式地请求每个调用约定。编译器的参数。
__cdecl(cl / Gd ...)
此类型的所有函数名称都带有下划线前缀,并且参数的数量并不重要,因为调用者负责堆栈设置和堆栈清除。调用者和被调用者可能会对实际传递的参数数量感到困惑,但至少可以正确维护堆栈规则。
__stdcall(cl / Gz ...)
这些函数名称以下划线作为前缀,并在@后面加上传递的参数的字节数。通过这种机制,不可能以“错误”类型甚至错误数量的参数来调用函数。
__fastcall(cl / Gr ...)
这些函数名称以@符号开头,并以@parameter计数作为后缀,就像__stdcall一样。
例子:
Declaration -----------------------> decorated name
void __cdecl foo(void); -----------------------> _foo
void __cdecl foo(int a); -----------------------> _foo
void __cdecl foo(int a, int b); -----------------------> _foo
void __stdcall foo(void); -----------------------> _foo@0
void __stdcall foo(int a); -----------------------> _foo@4
void __stdcall foo(int a, int b); -----------------------> _foo@8
void __fastcall foo(void); -----------------------> @foo@0
void __fastcall foo(int a); -----------------------> @foo@4
void __fastcall foo(int a, int b); -----------------------> @foo@8
__stdcall是一种调用约定:一种确定如何将参数传递给函数的方法(在堆栈上或在寄存器中),以及由谁负责在函数返回后进行清理(调用方或被调用方)。
Raymond Chen撰写了有关主要x86调用约定的博客,并且还有一篇不错的CodeProject文章。
在大多数情况下,您不必担心它们。唯一应该采用的情况是,如果调用的库函数使用的不是默认值,则编译器将生成错误的代码,并且程序可能会崩溃。
这是WinAPI函数需要正确调用的调用约定。调用约定是关于如何将参数传递到函数以及如何从函数传递返回值的一组规则。
如果调用者和被调用的代码使用不同的约定,则会遇到未定义的行为(例如看起来很奇怪的崩溃)。
默认情况下,C ++编译器不使用__stdcall,它们使用其他约定。因此,为了从C ++调用WinAPI函数,您需要指定它们使用__stdcall-这通常在Windoes SDK头文件中完成,并且在声明函数指针时也要这样做。