zoukankan      html  css  js  c++  java
  • __declspec,__cdecl,__stdcall区别和作用

    _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中的导出函数.这个关键字也有一些其他的用法,不过非常罕见.

    转载自  http://www.cppblog.com/dingding/archive/2008/10/21/64627.html
    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。
     
        以上改变均不会改变原函数名中的字符大小写。
    转载自http://blog.csdn.net/bird67/archive/2009/03/24/4019044.aspx
  • 相关阅读:
    ini_set /ini_get函数功能-----PHP
    【转】那个什么都懂的家伙
    word 2007为不同页插入不同页眉页脚
    August 26th 2017 Week 34th Saturday
    【2017-11-08】Linux与openCV:opencv版本查看及库文件位置等
    August 25th 2017 Week 34th Friday
    August 24th 2017 Week 34th Thursday
    August 23rd 2017 Week 34th Wednesday
    August 22nd 2017 Week 34th Tuesday
    August 21st 2017 Week 34th Monday
  • 原文地址:https://www.cnblogs.com/haoyuanyuan/p/3794712.html
Copyright © 2011-2022 走看看