_cdecl和__stdcall都是函数调用规范(还有一个__fastcall),规定了参数出入栈的 顺序和方法,如果只用VC编程的话可以不用关心,但是要在C++和Pascal等其他语言通信的时候就要注意了,只有用相同的方法才能够调用成功.另外, 像printf这样接受可变个数参数的函数只有用cdecl才能够实现.   
 
__declspec主要是用于说明DLL的引出函数的,在某些情况下用__declspec(dllexport)在DLL中生命引出函数,比用传统的
DEF文件方便一些.在普通程序中也可以用__declspec(dllimport)说明函数是位于另一个DLL中的导出函数.

int   WINAPI   MessageBoxA(HWND,LPCSTR,LPSTR,UINT);   
  而WINAPI实际上就是__stdcall.   
 
大多数API都采用__stdcall调用规范,这是因为几乎所有的语言都支持__stdcall调用.相比之下,__cdecl只有在C语言中才能用.
但是__cdecl调用有一个特点,就是能够实现可变参数的函数调用,比如printf,这用__stdcall调用是不可能的.   
  __fastcall这种调用规范比较少见,但是在Borland   C++   Builder中比较多的采用了这种调用方式.   
 
如果有共享代码的需要,比如写DLL,推荐的方法是用__stdcall调用,因为这样适用范围最广.如果是C++语言写的代码供Delphi这样的语言
调用就必须声明为__stdcall,因为Pascal不支持cdecl调用(或许Delphi的最新版本能够支持也说不定,这个我不太清楚).在其他一
些地方,比如写COM组件,几乎都用的是stdcall调用.在VC或Delphi或C++Builder里面都可以从项目设置中更改默认的函数调用规
范,当然你也可以在函数声明的时候加入__stdcall,__cdecl,__fastcall关键字来明确的指示本函数用哪种调用规范.
  
  __declspec一般都是用来声明DLL中的导出函数.这个关键字也有一些其他的用法,不过非常罕见.

DLL中调用约定和名称修饰
调用约定(Calling Convention)是指在程序设计语言中为了实现函数调用而建立的一种协议。这种协议规定了该语言的函数中的参数传送方式、参数是否可变和由谁来处理堆栈等问题。不同的语言定义了不同的调用约定。
 
在C++中,为了允许操作符重载和函数重载,C++编译器往往按照某种规则改写每一个入口点的符号名,以便
允许同一个名字(具有不同的参数类型或者是不同的作用域)有多个用法,而不会打破现有的基于C的链接器。这项技术通常被称为名称改编(Name
Mangling)或者名称修饰(Name Decoration)。许多C++编译器厂商选择了自己的名称修饰方案。
 
因此,为了使其它语言编写的模块(如Visual Basic应用程序、Pascal或Fortran的应用程序等)可以调用C/C++编写的DLL的函数,必须使用正确的调用约定来导出函数,并且不要让编译器对要导出的函数进行任何名称修饰。
1.调用约定(Calling Convention)
调用约定用来处理决定函数参数传送时入栈和出栈的顺序(由调用者还是被调用者把参数弹出栈),以及编译器用来识别函数名称的名称修饰约定等问题。在Microsoft VC++ 6.0中定义了下面几种调用约定,我们将结合汇编语言来一一分析它们:
1、__cdecl
__cdecl是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关键字
来手工指定。采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能使用该
调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。
 
下面将通过一个具体实例来分析__cdecl约定:
 
在VC++中新建一个Win32 Console工程,命名为cdecl。其代码如下:
 
int __cdecl Add(int a, int b);         //函数声明
 
void main()
{
       Add(1,2);                                   //函数调用
}
 
int __cdecl Add(int a, int b)          //函数实现
{
       return (a + b);
}
 
函数调用处反汇编代码如下:
 
;Add(1,2);
push                     2                                        ;参数从右到左入栈,先压入2
push        1                                         ;压入1
call          @ILT+0(Add) (00401005)    ;调用函数实现
add           esp,8                                   ;由函数调用清栈
2、__stdcall
__stdcall调用约定用于调用Win32
API函数。采用__stdcal约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函数体本身知
道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。
 
还是那个例子,将__cdecl约定换成__stdcall:
 
int __stdcall Add(int a, int b)
{
return (a + b);
}
 
函数调用处反汇编代码:
      
       ; Add(1,2);
push                     2                                               ;参数从右到左入栈,先压入2
push        1                                                ;压入1
call          @ILT+10(Add) (0040100f)          ;调用函数实现
 
函数实现部分的反汇编代码:
 
;int __stdcall Add(int a, int b)
push                     ebp
mov          ebp,esp
sub                esp,40h
push               ebx
push               esi
push               edi
lea           edi,[ebp-40h]
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
pop          ebp
ret           8                 ;清栈
3、__fastcall
__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个
大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。
__fastcall可以写成_fastcall。
 
依旧是相类似的例子,此时函数调用约定为__fastcall,函数参数个数增加2个:
 
int __fastcall Add(int a, double b, int c, int d)
{
return (a + b + c + d);
}
 
函数调用部分的汇编代码:
 
;Add(1, 2, 3, 4);
push                     4                          ;后两个参数从右到左入栈,先压入4
mov          edx,3                    ;将int类型的3放入edx
push         40000000h            ;压入double类型的2
push         0
mov          ecx,1                    ;将int类型的1放入ecx
call          @ILT+0(Add) (00401005)                ;调用函数实现
 
函数实现部分的反汇编代码:
             
; int __fastcall Add(int a, double b, int c, int d)
push                     ebp
mov        ebp,esp
sub          esp,48h
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
mov        dword ptr [ebp-4],ecx
;return (a + b + c + d);
fild           dword ptr [ebp-4]
fadd          qword ptr [ebp+8]
fiadd         dword ptr [ebp-8]
fiadd         dword ptr [ebp+10h]
call          __ftol (004011b8)
pop          edi
pop          esi
pop          ebx
mov          esp,ebp
pop          ebp
ret           0Ch                              ;清栈
 
关键字__cdecl、__stdcall和__fastcall可以直接加在要输出的函数前,也可以在编
译环境的Setting...->C/C++->Code
Generation项选择。它们对应的命令行参数分别为/Gd、/Gz和/Gr。缺省状态为/Gd,即__cdecl。当加在输出函数前的关键字与编译
环境中的选择不同时,直接加在输出函数前的关键字有效。
 
4、thiscall
thiscall调用约定是C++中的非静态类成员函数的默认调用约定。thiscall只能被编译器使
用,没有相应的关键字,因此不能被程序员指定。采用thiscall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,只
是另外通过ECX寄存器传送一个额外的参数:this指针。
 
这次的例子中将定义一个类,并在类中定义一个成员函数,代码如下:
 
class CSum
     {
public:
int Add(int a, int b)
{
return (a + b);
}
};
 
void main()
{    
       CSum sum;
       sum.Add(1, 2);
}
 
函数调用部分汇编代码:
 
;CSum  sum;
     ;sum.Add(1, 2);
     push                     2                                 ;参数从右到左入栈,先压入2
     push               1                                 ;压入1
     lea           ecx,[ebp-4]                   ;ecx存放了this指针
call          @ILT+5(CSum::Add) (0040100a)        ;调用函数实现
 
函数实现部分汇编代码:
 
;int Add(int a, int b)
       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                                 ;清栈
5、naked属性
采用上面所述的四种调用约定的函数在进入函数时,编译器会产生代码来保存ESI、EDI、EBX、EBP寄
存器中的值,退出函数时则产生代码恢复这些寄存器的内容。对于定义了naked属性的函数,编译器不会自动产生这样的代码,需要你手工使用内嵌汇编来控制
函数实现中的堆栈管理。由于naked属性并不是类型修饰符,故必须和__declspec共同使用。下面的这段代码定义了一个使用了naked属性的函
数及其实现:
 
__declspec ( naked ) func()
{
int i;
     int j;
      
_asm
{
push              ebp
              mov      ebp, esp
              sub            esp, __LOCAL_SIZE
}
      
    _asm
       {
mov        esp, ebp
              pop         ebp
              ret
       }
}
 
naked属性与本节关系不大,具体请参考MSDN。
6、WINAPI
还有一个值得一提的是WINAPI宏,它可以被翻译成适当的调用约定以供函数使用。该宏定义于windef.h之中。下面是在windef.h中的部分内容:
 
#define CDECL             _cdecl
#define WINAPI           CDECL
#define CALLBACK       __stdcall
#define WINAPI        __stdcall
#define APIENTRY      WINAPI
 
       由此可见,WINAPI、CALLBACK、APIENTRY
等宏的作用。
2.名称修饰(Name Decoration)
C或C++函数在内部(编译和链接)通过修饰名(Decoration
Name)识别。函数的修饰名是编译器在编译函数定义或者原型时生成的字符串。编译器在创建.obj文件时对函数名称进行修饰。有些情况下使用函数的修饰
名是必要的,如在模块定义文件里头指定输出C++重载函数、构造函数、析构函数,又如在汇编代码里调用C或C++函数等。
 
在VC++中,函数修饰名由编译类型(C或C++)、函数名、类名、调用约定、返回类型、参数等多种因素共同决定。下面分C编译、C++编译(非类成员函数)和C++类及其成员函数编译三种情况说明:
1、C编译时函数名称修饰
当函数使用__cdecl调用约定时,编译器仅在原函数名前加上一个下划线前缀,格式为_functionname。例如:函数int __cdecl Add(int a, int b),输出后为:_Add。
 
当函数使用__stdcall调用约定时,编译器在原函数名前加上一个下划线前缀,后面加上一个@符号和函数参数的字节数,格式为_functionname@number。例如:函数int __stdcall Add(int a, int b),输出后为:_Add@8
 
当函数是用__fastcall调用约定时,编译器在原函数名前加上一个@符号,后面是加一个@符号和函数
参数的字节数,格式为@functionname@number。例如:函数int __fastcall Add(int a, int
b),输出后为:@Add@8。
 
    以上改变均不会改变原函数名中的字符大小写。

__declspec,__cdecl,__stdcall区别和作用的更多相关文章

  1. __declspec,__cdecl,__stdcall都是什么意思?有什么作用?

    __cdecl和__stdcall都是函数调用规范(还有一个__fastcall),规定了参数出入栈的顺序和方法,如果只用VC编程的话可以不用关心,但是要在C++和Pascal等其他语言通信的时候就要 ...

  2. 函数调用方式--__thiscall调用方式和__cdecl,__stdcall有什么区别

    函数调用方式--__thiscall调用方式和__cdecl,__stdcall有什么区别 首先,__thiscall是关于类的一种调用方式,它与其他调用方式的最大区别是:    __thiscall ...

  3. 浅析匿名函数、lambda表达式、闭包(closure)区别与作用

    浅析匿名函数.lambda表达式.闭包(closure)区别与作用 所有的主流编程语言都对函数式编程有支持,比如c++11.python和java中有lambda表达式.lua和JavaScript中 ...

  4. maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令

    maven发布项目到私服-snapshot快照库和release发布库的区别和作用及maven常用命令 在日常的工作中由于各种原因,会出现这样一种情况,某些项目并没有打包至mvnrepository. ...

  5. 深入解析Oracle 10g中SGA_MAX_SIZE和SGA_TARGET参数的区别和作用

    原文链接:http://m.blog.csdn.net/blog/aaron8219/40037005 SGA_MAX_SIZE是从9i以来就有的作为设置SGA大小的一个参数,而SGA_TARGET则 ...

  6. jsp中的contentType与pageEncoding的区别和作用

    jsp中的contentType与pageEncoding的区别和作用   <%@ page contentType="text/html; charset=utf-8" p ...

  7. Python 实例方法、类方法、静态方法的区别与作用

    Python中至少有三种比较常见的方法类型,即实例方法,类方法.静态方法.它们是如何定义的呢?如何调用的呢?它们又有何区别和作用呢?且看下文. 首先,这三种方法都定义在类中.下面我先简单说一下怎么定义 ...

  8. [转] <context-param>与<init-param>的区别与作用

    看到一篇关于web.xm文件中标签的讲解,顺带还阐述了容器的工作流程,因此转载此,以供参考,原文地址:与的区别与作用 <context-param>的作用: web.xml的配置中< ...

  9. nginx和uwsgi的区别和作用

    Django+uwsgi+nginx nginx和uwsgi的区别和作用: 1, nginx是对外的服务器,外部浏览器通过url访问nginx, uwsgi是对内的服务器,主要用来处理动态请求. 2, ...

随机推荐

  1. 基于CentOS与VmwareStation10搭建Oracle11G RAC 64集群环境:4.安装Oracle RAC FAQ-4.1.系统界面报错Gnome

    1.错误信息:登录系统后,屏幕弹出几个错误对话框,无菜单.无按钮 GConf error: Failed to contact configuration server; some possible ...

  2. php生成 Arduino 12864 汉字内码

    $ch = "你"; $ch = iconv("UTF-8","GB2312",$ch); $xx= sprintf("%X&qu ...

  3. js闭包用法

    闭包 既保证了 内部函数的私有性 又可以向外公开 通过一个已有对象 向它注入属性 /** * 闭包 * 在函数中定义的函数,在外部使用 * 1.在函数内部定义的函数,在外部不能访问 */ functi ...

  4. delphi中通过CreateOleObject操控Word

    http://blog.csdn.net/csm2432/article/details/7692443

  5. collect my database for test KCF tracker tools

    Path Button used to set dir where avi file saves, set path set video size and start record write to ...

  6. 【转】asp.net发布到IIS中出现错误:处理程序“PageHandlerFactory-Integrated”在其模块列表中有一个错误模块“ManagedPipelineHandler”

    原文:http://blog.csdn.net/mazhaojuan/article/details/7660657 开发web项目时需要安装IIS,在安装好IIS的Windows7本上发布asp.n ...

  7. 【LeetCode】58 - Length of Last Word

    Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the l ...

  8. 跨平台音乐播放器qmmp(Cross-Platform Audio Player Qmmp)

    开源多媒体播放器(Audio-Player),简洁的界面,最看重它的是跨平台的特性. 开源 跨平台(Linux.Windows) 简洁 支持音乐格式(mp3/ogg......) 全局快捷键 播放中打 ...

  9. Linux下U盘的挂载和文件的拷贝

    把文件通过U盘拷贝到linux系统下插好U盘后,查看磁盘情况fdisk -l正常情况下有 Disk /dev/sda:2045 MB,2045247488 bytes47 heads,46 secto ...

  10. sql-labs 分享

    前段时间在网上发现了一个阿三同学托管在github上的sql注入入门科普项目,感觉挺不错,在此分享一下.虽然现在有很多工具比如sqlmap可以实现自动化的sql注入,但是个人感觉如果只知其然而不知其所 ...