__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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章