__cdecl __stdcall各种调用约定详解

调用协定(Calling Conventions)详解

资料收集:heartfly

学习时间:2010-10-11

what are the calling conventions? When a function is called, the arguments are typically passed to it, and the return value is retrieved. A calling convention describes how the arguments are passed and values returned by functions. It also specifies how the function names are decorated. Is it really necessary to understand the calling conventions to write good C/C++ programs? Not at all. However, it may be helpful with debugging. Also, it is necessary for linking C/C++ with assembly code.

**********************************************************************************************
参考文章:http://www.codeproject.com/cpp/calling_conventions_demystified.asp

Calling Conventions Demystified
By Nemanja Trifunovic

Introduction
During the long, hard, but yet beautiful process of learning C++ programming for Windows, you have probably been curious about the strange specifiers that sometime appear in front of function declarations, like __cdecl, __stdcall, __fastcall, WINAPI, etc. After looking through MSDN, or some other reference, you probably found out that these specifiers specify the calling conventions for functions. In this article, I will try to explain different calling conventions used by Visual C++ (and probably other Windows C/C++ compilers). I emphasize that above mentioned specifiers are Microsoft-specific, and that you should not use them if you want to write portable code.

So, what are the calling conventions? When a function is called, the arguments are typically passed to it, and the return value is retrieved. A calling convention describes how the arguments are passed and values returned by functions. It also specifies how the function names are decorated. Is it really necessary to understand the calling conventions to write good C/C++ programs? Not at all. However, it may be helpful with debugging. Also, it is necessary for linking C/C++ with assembly code.

To understand this article, you will need to have some very basic knowledge of assembly programming.

No matter which calling convention is used, the following things will happen:

All arguments are widened to 4 bytes (on Win32, of course), and put into appropriate memory locations. These locations are typically on the stack, but may also be in registers; this is specified by calling conventions.
Program execution jumps to the address of the called function.
Inside the function, registers ESI, EDI, EBX, and EBP are saved on the stack. The part of code that performs these operations is called function prolog and usually is generated by the compiler.
The function-specific code is executed, and the return value is placed into the EAX register.
Registers ESI, EDI, EBX, and EBP are restored from the stack. The piece of code that does this is called function epilog, and as with the function prolog, in most cases the compiler generates it.
Arguments are removed from the stack. This operation is called stack cleanup and may be performed either inside the called function or by the caller, depending on the calling convention used.
As an example for the calling conventions (except for this), we are going to use a simple function:

int sumExample (int a, int b)
{
 return a + b;
}
The call to this function will look like this:

 int c = sum (2, 3);
For __cdecl, __stdcall, and __fastcall calling conventions, I compiled the example code as C (not C++). The function name decorations, mentioned later in the article, apply to the C decoration schema. C++ name decorations are beyond the scope of this article.

C calling convention (__cdecl)
This convention is the default for C/C++ programs (compiler option /Gd). If a project is set to use some other calling convention, we can still declare a function to use __cdecl:

int __cdecl sumExample (int a, int b);The main characteristics of __cdecl calling convention are:

Arguments are passed from right to left, and placed on the stack.
Stack cleanup is performed by the caller.
Function name is decorated by prefixing it with an underscore character '_' .
Now, take a look at an example of a __cdecl call:

; // push arguments to the stack, from right to left
push        3   
push        2   

; // call the function
call        _sumExample

; // cleanup the stack by adding the size of the arguments to ESP register
add         esp,8

; // copy the return value from EAX to a local variable (int c)
mov         dword ptr [c],eax
The called function is shown below:

; // function prolog
  push        ebp 
  mov         ebp,esp
  sub         esp,0C0h
  push        ebx 
  push        esi 
  push        edi 
  lea         edi,[ebp-0C0h]
  mov         ecx,30h
  mov         eax,0CCCCCCCCh
  rep stos    dword ptr [edi]
 
; // return a + b;
  mov         eax,dword ptr [a]
  add         eax,dword ptr [b]

; // function epilog
  pop         edi 
  pop         esi 
  pop         ebx 
  mov         esp,ebp
  pop         ebp 
  ret             
Standard calling convention (__stdcall)
This convention is usually used to call Win32 API functions. In fact, WINAPI is nothing but another name for __stdcall:

#define WINAPI __stdcallWe can explicitly declare a function to use the __stdcall convention:

int __stdcall sumExample (int a, int b);Also, we can use the compiler option /Gz to specify __stdcall for all functions not explicitly declared with some other calling convention.

The main characteristics of __stdcall calling convention are:

Arguments are passed from right to left, and placed on the stack.
Stack cleanup is performed by the called function.
Function name is decorated by prepending an underscore character and appending a '@' character and the number of bytes of stack space required.
The example follows:

; // push arguments to the stack, from right to left
  push        3   
  push        2   
 
; // call the function
  call        _sumExample@8

; // copy the return value from EAX to a local variable (int c) 
  mov         dword ptr [c],eax
The function code is shown below:

; // function prolog goes here (the same code as in the __cdecl example)

; // return a + b;
  mov         eax,dword ptr [a]
  add         eax,dword ptr [b]

; // function epilog goes here (the same code as in the __cdecl example)

; // cleanup the stack and return
  ret         8
Because the stack is cleaned by the called function, the __stdcall calling convention creates smaller executables than __cdecl, in which the code for stack cleanup must be generated for each function call. On the other hand, functions with the variable number of arguments (like printf()) must use __cdecl, because only the caller knows the number of arguments in each function call; therefore only the caller can perform the stack cleanup.

Fast calling convention (__fastcall)
Fast calling convention indicates that the arguments should be placed in registers, rather than on the stack, whenever possible. This reduces the cost of a function call, because operations with registers are faster than with the stack.

We can explicitly declare a function to use the __fastcall convention as shown:

int __fastcall sumExample (int a, int b);We can also use the compiler option /Gr to specify __fastcall for all functions not explicitly declared with some other calling convention.

The main characteristics of __fastcall calling convention are:

The first two function arguments that require 32 bits or less are placed into registers ECX and EDX. The rest of them are pushed on the stack from right to left.
Arguments are popped from the stack by the called function.
Function name is decorated by by prepending a '@' character and appending a '@' and the number of bytes (decimal) of space required by the arguments.
Note: Microsoft have reserved the right to change the registers for passing the arguments in future compiler versions.

Here goes an example:

; // put the arguments in the registers EDX and ECX
  mov         edx,3
  mov         ecx,2
 
; // call the function
  call        @fastcallSum@8
 
; // copy the return value from EAX to a local variable (int c) 
  mov         dword ptr [c],eax
Function code:

; // function prolog

  push        ebp 
  mov         ebp,esp
  sub         esp,0D8h
  push        ebx 
  push        esi 
  push        edi 
  push        ecx 
  lea         edi,[ebp-0D8h]
  mov         ecx,36h
  mov         eax,0CCCCCCCCh
  rep stos    dword ptr [edi]
  pop         ecx 
  mov         dword ptr [ebp-14h],edx
  mov         dword ptr [ebp-8],ecx
; // return a + b;
  mov         eax,dword ptr [a]
  add         eax,dword ptr [b]
;// function epilog 
  pop         edi 
  pop         esi 
  pop         ebx 
  mov         esp,ebp
  pop         ebp 
  ret             
How fast is this calling convention, comparing to __cdecl and __stdcall? Find out for yourselves. Set the compiler option /Gr, and compare the execution time. I didn't find __fastcall to be any faster than other calling conventons, but you may come to different conclusions.

Thiscall
Thiscall is the default calling convention for calling member functions of C++ classes (except for those with a variable number of arguments).

The main characteristics of thiscall calling convention are:

Arguments are passed from right to left, and placed on the stack. this is placed in ECX.
Stack cleanup is performed by the called function.
The example for this calling convention had to be a little different. First, the code is compiled as C++, and not C. Second, we have a struct with a member function, instead of a global function.

struct CSum
{
 int sum ( int a, int b) {return a+b;}
};
The assembly code for the function call looks like this:

  push        3   
  push        2   
  lea         ecx,[sumObj]
  call        ?sum@CSum@@QAEHHH@Z   ; CSum::sum
  mov         dword ptr [s4],eax
The function itself is given below:

  push        ebp 
  mov         ebp,esp
  sub         esp,0CCh
  push        ebx 
  push        esi 
  push        edi 
  push        ecx 
  lea         edi,[ebp-0CCh]
  mov         ecx,33h
  mov         eax,0CCCCCCCCh
  rep stos    dword ptr [edi]
  pop         ecx 
  mov         dword ptr [ebp-8],ecx
  mov         eax,dword ptr [a]
  add         eax,dword ptr [b]
  pop         edi 
  pop         esi 
  pop         ebx 
  mov         esp,ebp
  pop         ebp 
  ret         8   
Now, what happens if we have a member function with a variable number of arguments? In that case, __cdecl is used, and this is pushed onto the stack last.

Conclusion
To cut a long story short, we'll outline the main differences between the calling conventions:

__cdecl is the default calling convention for C and C++ programs. The advantage of this calling convetion is that it allows functions with a variable number of arguments to be used. The disadvantage is that it creates larger executables.
__stdcall is used to call Win32 API functions. It does not allow functions to have a variable number of arguments.
__fastcall attempts to put arguments in registers, rather than on the stack, thus making function calls faster.
Thiscall calling convention is the default calling convention used by C++ member functions that do not use variable arguments.
In most cases, this is all you'll ever need to know about the calling conventions.

**********************************************************************************************
参考文章:http://it.icxo.com/htmlnews/2004/07/26/274735.htm

通过汇编看调用协定
《世界商业评论》ICXO.COM ( 日期:2004-07-26 15:50)

  调用函数的时候,有各种不同的调用约定。它们规定了参数的传送方式、参数是否可变,由谁来处理堆栈等。常用的调用约定有两种:C语言调用约定和Pascal语言调用约定。
   可以在工程设置中设定自定义函数的调用规则,也可以在函数声明和定义的时候在函数名前加关键词或API宏定义(如_cdecl、__stdcall、__fastcall、WINAPI、APIENTRY等)明确表示函数的调用协定。
    下面将分别说明各种调用协定的用法和意义,并附以相应的汇编代码分析。

1、C语言调用约定

   采用C语言编程的时候,默认使用C/C++语言调用约定。也可以手工指定,这需要在函数声明时加上__cdecl关键字。采用本约定时,参数从右到左入栈,个数可变。由于函数体不能预先知道传进来的参数个数,因此采用本约定时必须由调用函数者负责堆栈清理。由于参数可变,此约定比较灵活,但是性能比较低。生成的代码中函数名有一个_(下划线)做前缀。
   举例:
   int __cdecl Add(int a, int b)
   {
      return (a + b);
   }
  
   函数调用:
   Add(1, 2);

   push        2
   push        1
   call        @Add         ;其实还有编译器用于定位函数的表达式这里把它省略了
   add         esp,8        ;清栈

   函数体:

   push        ebp
   mov         ebp,esp
   sub         esp,40h       ;函数使用的栈默认为40H(16*4),增加一个变量加4bytesWIN32下栈的粒度为4bytes。     
   push        ebx
   push        esi
   push        edi

   lea        edi,[ebp-40h]   ;初始化用于该函数的栈空间为0XCCCCCCCC
   mov         ecx,10h
   mov         eax,0CCCCCCCCh
   rep stos    dword ptr [edi]

   return (a + b);

   mov         eax,dword ptr [ebp+8]     
   add         eax,dword ptr [ebp+0Ch]

   pop         edi
   pop         esi
   pop         ebx
   mov         esp,ebp         ;如果在此函数中对ESP进行操作,则会有add esp, 40h cmp esp,ebp call chkesp, 检查弹出的ESP指针是否和EBP相同,若不同则调用chkesp抛出异常  
   pop         ebp

   ret

下图指出了该函数的栈的使用情况:

                                


2、Pascal语言调用约定

   大部分的Windows API都采用Pascal语言调用约定。采用C语言编程的时候,如果要采用这种调用约定,需要在函数声明的时候加上__stdcall关键字。windows.h头文件中也定义了一个WINAPI的宏,起同样的作用。采用本约定时,参数从右到左入栈,个数固定。因此,函数体本身就能知道传进来的参数个数,可以用一条ret n指令直接清理堆栈。牺牲灵活性换来的,是性能的提高。生成的代码中函数名一个_(下划线)做前缀、一个@和参数总字节数(十进制)作后缀(参数的个数乘以4)。WINAPI CALLBACK APIENTRY PASCAL 都是__stdcall的宏定义,而__pascal是一个废弃的关键词。
  举例:
  int __stdcall Add(int a, int b)
  {
     return (a + b);
  }
 
  函数的调用:
  push          2
  push          1
  call          _Add@8            ;这里使用了连接时使用的函数名
 
  函数体:
  ;和__cdecl一样,最后一行如下:
  ret           8                 ;清栈


3、This调用约定

   用于C++中的非静态类成员函数,它只能被编译器使用,没有相应的关键字。在Intel IA32架构下,此调用约定跟PASCAL语言调用约定相同,只是另外通过ECX寄存器传送一个额外的参数―this指针。在函数中清理栈,不能在C中使用,没有C的换名规则,所以说类的非静态成员函数不能作为回调函数。
   举例:
   struct CSum
   {
      int Add(int a, int b)
      {
          return (a + b);
      }
   };
   函数调用:
   CSum  sum;
   sum.Add(1, 2);

   push        2
   push        1
   lea         ecx,[ebp-4]              ;ecx存放了this指针
   call        ?Add@CSum@@QAEHHH@Z      ;当重复定义一个类成员函数时,可以在输出框中得到该名字
  
   函数体:
   push        ebp
   mov         ebp,esp
   sub         esp,44h                   ;多用了一个4bytes的空间用于存放this指针
   push        ebx
   push        esi
   push        edi
   push        ecx
   lea         edi,[ebp-44h]
   mov         ecx,11h
   mov         eax,0CCCCCCCCh
   rep stos    dword ptr [edi]
   pop         ecx
   mov         dword ptr [ebp-4],ecx
 
   return (a + b);

   mov         eax,dword ptr [ebp+8]
   add         eax,dword ptr [ebp+0Ch]

   pop         edi
   pop         esi
   pop         ebx
   mov         esp,ebp
   pop         ebp
   ret         8                            ;清栈


4、快速调用约定

   这种调用约定用于对性能要求非常高的场合,关键字是__fastcall。它要求将参数放在寄存器中,以提高速度。在Intel IA32架构下,此调用约定将函数最左边两个大小小于4个字节(DWORD的大小)的参数放在ECX和EDX寄存器,其余规定同Pascal调用约定。生成的代码中函数名有一个@做前缀和一个@加上参数

总字节数(十进制)作后缀。
   举例:
   int __fastcall Add(int a, int b, int c)
   {
       return (a + b + c);
   }
   函数的调用:
   Add(1, 2, 3);

   push            3
   mov             edx, 2
   mov             ecx, 1
   call            @Add@8                 ;这里使用了连接时使用的函数名

   函数体:
   push        ebp
   mov         ebp,esp
   sub         esp,48h                    ;多使用了两个变量在[ebp-4]、[ebp-8]
   push        ebx
   push        esi
   push        edi
   push        ecx
   lea         edi,[ebp-48h]
   mov         ecx,12h
   mov         eax,0CCCCCCCCh
   rep stos    dword ptr [edi]
   pop         ecx
   mov         dword ptr [ebp-8],edx        ;还是将edx,ecx的内容放在
   mov         dword ptr [ebp-4],ecx        ;栈中似乎并没有提高速度

   return (a + b + c);

   mov         eax,dword ptr [ebp-4]
   add         eax,dword ptr [ebp-8]
   add         eax,dword ptr [ebp+8]

   pop         edi
   pop         esi
   pop         ebx
   mov         esp,ebp
   pop         ebp
   ret         4                              ;清栈

5、裸调用约定

   当嵌入式汇编程序员需要手工控制调用约定的时候,使用这 种方法。它没有相应的关键字,但是有一个__declspec(naked)用来指定这种调用约定。它不能用于函数声明,只能用于函数定义,也就是说,它只能让编译器不要生成函数体中的堆栈管理代码,但是调用函数者依然需要前面的某种调用约定来生成调用函数的代码。此协定本人没用过,希望有高手补充。

**********************************************************************************************
参考文章:http://www.marcocantu.com/epascal/chinese/ch06proc.htm

Delphi 调用协定

32位的Delphi 中增加了新的参数传递方法,称为fastcall:只要有可能,传递到CPU寄存器的参数能多达三个,使函数调用操作更快。这种快速调用协定(Delphi 3确省方式)可用register 关键字标示。

问题是这种快速调用协定与Windows不兼容,Win32 API 函数必须声明使用stdcall 调用协定。这种协定是Win16 API使用的原始Pascal 调用协定和C语言使用的cdecl 调用协定的混合体。

除非你要调用外部Windows函数或定义Windows 回调函数,否则你没有理由不用新增的快速调用协定。 在后面你会看到使用stdcall 协定的例子,在Delphi帮助文件的Calling conventions 主题下,你能找到有关Delphi调用协定的总结内容。


**********************************************************************************************
参考文章:dephi帮助文档 Calling conventions

When you declare a procedure or function, you can specify a calling convention using one of the directives register, pascal, cdecl, stdcall, and safecall. For example,

function MyFunction(X, Y: Real): Real; cdecl;
 ...

Calling conventions determine the order in which parameters are passed to the routine. They also affect the removal of parameters from the stack, the use of registers for passing parameters, and error and exception handling. The default calling convention is register.

The register and pascal conventions pass parameters from left to right; that is, the left most parameter is evaluated and passed first and the rightmost parameter is evaluated and passed last. The cdecl, stdcall, and safecall conventions pass parameters from right to left.
 For all conventions except cdecl, the procedure or function removes parameters from the stack upon returning. With the cdecl convention, the caller removes parameters from the stack when the call returns.

The register convention uses up to three CPU registers to pass parameters, while the other conventions pass all parameters on the stack.
 The safecall convention implements exception "firewalls." On Windows, this implements interprocess COM error notification.

The table below summarizes calling conventions.

Calling conventions
Directive Parameter order Clean-up Passes parameters in registers?
register Left-to-right Routine Yes
pascal Left-to-right Routine No
cdecl Right-to-left Caller No
stdcall Right-to-left Routine No
safecall Right-to-left Routine No
The default register convention is the most efficient, since it usually avoids creation of a stack frame. (Access methods for published properties must use register.) The cdecl convention is useful when you call functions from shared libraries written in C or C++, while stdcall and safecall are recommended, in general, for calls to external code. On Windows, the operating system APIs are stdcall and safecall. Other operating systems generally use cdecl. (Note that stdcall is more efficient than cdecl.)

The safecall convention must be used for declaring dual-interface methods. The pascal convention is maintained for backward compatibility. For more information on calling conventions, see Program control.
The directives near, far, and export refer to calling conventions in 16-bit Windows programming. They have no effect in 32-bit applications and are maintained for backward compatibility only.


**********************************************************************************************
参考文章:http://www.china-askpro.com/msg25/qa54.shtml
          http://bbs.chinaunix.net/forum/viewtopic.php?t=589615

   由于PASCAL同C++之间的区别较大,比如调用规则,以及数据类型,最简单的就是字符串类型,两者的实现都不一样.所以在做通用的C的DLL的时侯一定要注意下面两点.
1.函数的调用规则一定要用stdcall也就是windows api的调用规则,编译完成后可以使用工具depends观察所生成的dll函数,如果同定义的函数名一样,说明在这一点上已经通过
2.函数的类型和参数类型一定要用两者统一使用且无歧义的类型,比如int在32位平台上,你就用DWORD来传.更详细的请参照网上资料。

   你可以使用VC++编写DLL供Delphi调用,或者反过来。直接链接.obj是不行的,因为两者的格式不同。VC++和Delphi都支持Pascal和C语言两种调用协定。
 如果使用Pascal协定,VC++的函数定义前要加_stdcall修饰(stdcall是Windows的标准调用方式,可以在所有支持API调用的语言中使用),并且需要在.DEF文件中的EXPORTS端中加入函数名。参考QA000072 "VB和VC混合编程"。在Delphi定义时要在函数名后加stdcall修饰(参见帮助)。
如果使用C协定,VC++的函数定义前要加__declspec(dllexport)修饰,不需要再修改.DEF文件了。在Delphi定义时要在函数名后加cdecl修饰(参见帮助)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值