__stdcall的含义和用法是什么?
这几天我碰到了很多__stdcall
。
而在我看来,MSDN并没有很清楚地解释它的真正含义,
何时以及为什么要使用它,如果是的话。
如果有人提供解释,我将不胜感激,最好有一两个例子。
谢谢
C / C ++中的所有函数都有一个特定的调用约定。 调用约定的要点是确定调用者和被调用者之间的数据传递方式,以及谁负责清理调用堆栈等操作。
Windows上最stream行的调用约定是
- STDCALL
- CDECL
- clrcall
- FASTCALL
- thiscall
将该说明符添加到函数声明中本质上告诉编译器,您希望这个特定的函数具有这种特定的调用约定。
调用约定logging在这里
陈百强也从这里开始了各种召集会议历史的长篇大论(5部分)。
传统上,调用者将一些参数推入堆栈,调用函数,然后popup堆栈以清理那些推入的参数,从而进行C函数调用。
/* example of __cdecl */ push arg1 push arg2 push arg3 call function add sp,12 // effectively "pop; pop; pop"
注:上面显示的默认约定称为__cdecl。
另一个最stream行的约定是__stdcall。 在这个参数中,主叫方再次推送参数,但被叫方清除了堆栈。 它是Win32 API函数的标准约定(由WINAPImacros定义),有时也称为“Pascal”调用约定。
/* example of __stdcall */ push arg1 push arg2 push arg3 call function // no stack cleanup - callee does this
这看起来像是一个小技术细节,但是如果在调用者和被调用者之间如何pipe理堆栈存在分歧,堆栈将以不可能被恢复的方式被销毁。 由于__stdcall没有进行堆栈清理,因此执行此任务的(非常小的)代码只能在一个地方find,而不是像在__cdecl中那样在每个调用者中都被重复。 这使得代码稍微小一些,尽pipe只有在大型程序中才能看到大小的影响。
像printf()这样的variables函数几乎不可能通过__stdcall来获得正确的值,因为只有调用者真正知道有多less个参数被传递才能清除它们。 被调用者可以做出一些很好的猜测(比如通过查看一个格式string),但是堆栈清理必须由函数的实际逻辑决定,而不是调用约定机制本身。 因此,只有__cdecl支持可变参数,以便调用者可以进行清理。
链接器符号名称装饰:正如上面提到的,使用“错误”约定调用函数可能是灾难性的,所以微软有一个机制来避免这种情况的发生。 它运作良好,但如果不知道原因是什么,它会令人发狂。 他们select通过将调用约定编码为具有额外字符(通常称为“装饰”)的低级函数名称来解决这个问题,并且这些字符被链接器视为不相关的名称。 默认的调用约定是__cdecl,但是每个都可以用/ G来显式地请求。 参数给编译器。
__cdecl(cl / Gd …)
所有这种types的函数名都以下划线作为前缀,参数的数目并不重要,因为调用者负责堆栈设置和堆栈清理。 调用者和被调用者可能对实际传递的参数数目感到困惑,但至less堆栈规则是正确维护的。
__stdcall(cl / Gz …)
这些函数名称以下划线作为前缀,并附加@加上传递参数的字节数。 通过这种机制,不可能调用具有“错误”types的函数,或者甚至具有错误的参数数量。
__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文章 。
大多数情况下,你不应该担心他们。 唯一的情况是你应该调用一个库函数,而不是使用默认值 – 否则编译器会产生错误的代码,你的程序可能会崩溃。
不幸的是,什么时候使用它,什么时候使用它没有简单的答案。
__stdcall表示函数的参数从第一个到最后一个被压入堆栈。 这与__cdecl(这意味着参数从最后一个被推到第一个)以及__fastcall(将前四个(我认为)参数放在寄存器中,而其余的则放在堆栈上)相反。
你只需要知道被调用者的期望,或者如果你正在编写一个库,你的调用者可能期望什么,并确保你logging你select的约定。
它指定了一个函数的调用约定。 调用约定是一系列规则如何将parameter passing给函数:按顺序,每个地址或每个副本,谁来清理参数(调用者或被调用者)等
__stdcall表示调用约定(有关详细信息,请参阅此PDF )。 这意味着它指定了函数参数如何从堆栈中被推送和popup,以及谁负责。
__stdcall只是几个调用约定中的一个,并在整个WINAPI中使用。 如果您提供函数指针作为其中一些函数的callback函数,则必须使用它。 一般来说,除了上面提到的情况(提供对第三方代码的callback)之外,您不需要在代码中指定任何特定的调用约定,而只需使用编译器的默认值即可。
这是WinAPI函数需要正确调用的调用约定。 调用约定是关于如何将parameter passing给函数以及如何从函数传递返回值的一组规则。
如果调用者和被调用的代码使用不同的约定,你会遇到未定义的行为(就像这样一个奇怪的崩溃 )。
C ++编译器默认不使用__stdcall – 他们使用其他约定。 所以为了从C ++调用WinAPI函数,你需要指定他们使用__stdcall – 这通常是在Windoes SDK头文件中完成的,你也可以在声明函数指针的时候这样做。
简单地说,当你调用函数,它被装入堆栈/注册。 __stdcall是一个约定/方式(正确的参数第一,然后左参数…),__decl是另一个惯例是用来加载函数堆栈或寄存器。
如果使用它们,则指示计算机在链接过程中使用该特定方式来加载/卸载函数,因此不会出现不匹配/崩溃。
否则函数调用者和函数调用者可能会使用不同的约定,导致程序崩溃。