2009-08-20 153 views
48

这几天我遇到过很多__stdcall
在我看来,MSDN并没有很清楚地解释它的真正含义,
什么时候以及为什么要使用它,如果有的话。__stdcall的含义和用法是什么?

如果有人提供解释,我会很感激,最好有一个或两个例子。

感谢

回答

45

C/C++中的所有函数有一个特定的调用约定。调用约定的要点是确定调用者和被调用者之间的数据传递方式,以及谁负责清理调用堆栈等操作。

在Windows上最流行的调用约定

  • STDCALL
  • CDECL
  • clrcall
  • FASTCALL
  • thiscall

添加此说明符函数声明本质上是告诉编译器,你希望这个特定的函数有这个特定的调用约定。

调用约定在这里

记录雷蒙德陈也做了不同的调用约定(5份),从这里开始,历史悠久系列。

1

这是WinAPI的功能需要妥善称为调用约定。调用约定是关于如何将参数传递给函数以及如何从函数传递返回值的一组规则。

如果调用者和被调用代码使用不同的约定,则会遇到未定义的行为(如such a strange-looking crash)。

C++编译器默认不使用__stdcall - 它们使用其他约定。所以为了从C++调用WinAPI函数,你需要指定他们使用__stdcall--这通常是在Windoes SDK头文件中完成的,你也可以在声明函数指针时进行。

3

它指定了函数的调用约定。一个调用约定是一组规则,它是如何将参数传递给一个函数的:按哪个顺序,每个地址或每个副本,谁来清理参数(调用者或被调用者)等。

5

不幸的是,对于何时使用它,什么时候不是没有简单的答案。

__stdcall表示函数的参数从第一个到最后一个被压入堆栈。这与__cdecl相反,这意味着参数从最后一个推到第一个,而__fastcall将前四个(我认为)参数放在寄存器中,剩下的放在堆栈中。

你只需要知道被调用者的期望,或者如果你正在编写一个库,你的调用者可能期望什么,并确保你记录你选择的约定。

+2

'__stdcall'和'__cdecl'只负责不同的收益(和装饰)后清理。两个参数的传递是相同的(从右到左)。你所描述的是Pascal调用约定。 – a3f 2015-05-28 06:26:40

6

__stdcall是一种调用约定:一种确定参数如何传递给函数(在堆栈或寄存器中)以及谁负责在函数返回后清除(调用者或被调用者)的方法。

Raymond Chen写了一个blog about the major x86 calling conventions,还有一个不错的CodeProject article

大多数情况下,您不必担心它们。唯一的情况是,如果您调用的库函数使用的不是默认值 - 否则编译器会生成错误的代码,您的程序可能会崩溃。

2

__stdcall表示调用约定(有关详细信息,请参阅this PDF)。这意味着它指定函数参数如何从堆栈中被推送和弹出,以及谁负责。

__stdcall只是几个调用约定中的一个,并在整个WINAPI中使用。如果您提供函数指针作为其中一些函数的回调函数,则必须使用它。一般来说,除了上面提到的情况(为第三方代码提供回调)之外,您不需要在代码中指定任何特定的调用约定,而只需使用编译器的默认值即可。

1

简单地说,当你调用函数,它被装入堆栈/寄存器。 __stdcall是一个约定/方式(正确的参数第一,然后左参数...),__decl是另一个约定,用于加载堆栈或寄存器上的函数。

如果您使用它们,则指示计算机在链接过程中使用该特定方式加载/卸载该函数,因此不会出现不匹配/崩溃。

否则函数调用者和函数调用者可能会使用不同的约定导致程序崩溃。

33

传统上,调用者将一些参数推入堆栈,调用函数,然后弹出堆栈以清理那些推入的参数,从而进行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中那样复制。这使得代码稍微小一些,尽管只有大型程序才能看到尺寸的影响。

如printf参数可变型函数()几乎是不可能得到正确的使用__stdcall,因为只有真正来电者知道有多少争论,以清除它们获得通过。被叫方可以(通过查看格式字符串说,)提出一些很好的猜测,但堆栈清理必须由函数,而不是调用约定机制本身的实际逻辑来决定。因此,只有__cdecl支持可变参数函数,以便调用者可以进行清理。

链接器符号名称修饰: 正如上面的要点所述,使用“错误”约定调用函数可能是灾难性的,因此Microsoft有一种机制可以避免发生这种情况。它运作良好,但如果不知道原因是什么,它会令人发狂。 他们选择通过编码调用约定与额外字符(这通常被称为“装饰”)的低级别的功能名称来解决这个问题,而这些都被视为链接器无关的名字。默认调用约定__cdecl,但每个人都可以明确地与/ G请求?参数给编译器。

__cdecl(CL /钆...)

所有此类型的函数名前面有下划线,而且由于呼叫者负责堆栈设置和堆栈清理参数的数量并不重要。这是可能的主叫方和被叫方是超过实际传递参数的数量感到困惑,但至少堆栈规则正确维护。

__stdcall(CL/GZ ...)

这些函数名的前缀为下划线并用@所附加的传递的参数的字节数。通过这种机制,它不是可调用的“错误”类型的函数,甚至数错误的参数。

__fastcall(CL /石墨...)

这些函数名开始的@符号和后缀与@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);   -----------------------> [email protected] 

void __stdcall foo(int a);   -----------------------> [email protected] 

void __stdcall foo(int a, int b); -----------------------> [email protected] 

void __fastcall foo(void);   -----------------------> @[email protected] 

void __fastcall foo(int a);  -----------------------> @[email protected] 

void __fastcall foo(int a, int b); -----------------------> @[email protected] 
1

__stdcall是用于该函数的调用约定。这会告诉编译器适用于设置堆栈,推送参数和获取返回值的规则。还有一些其他的调用约定的像__cdecl__thiscall__fastcall__naked

__stdcall是Win32系统调用的标准调用约定。

更多细节可以在Wikipedia找到。