維尼的蜂巢

RealTime??!! It’s amazing!!!!

__stdcall ,__cdecl的區別 六月 19, 2006

Filed under: VC++/C++/C — kevinlin @ 5:20 下午

什麼時候會用到這兩個東西呢?? 在作DLL時

其實除了這兩個還有幾個比較常見的 而這些稱為Calling Conventions (呼叫慣例)

因為不同的語言間有不同的傳遞參數的方法,而C/C++編譯器為了能夠使用由其他語言開發出來的函式庫加上了這些參數傳遞方式不同的方式就稱為呼叫慣例,而在C++Builder比較長看到的是__fastcall,不過我不用BCB所以不說明了

下面表格簡單的結論就是

__stdcall是為了兼容Windows,__stdcall呼叫慣例相當於16位元動態函式庫中經常使用的PASCAL呼叫慣例,

在Windows中, WINAPI就是一個定義為__stdcall的巨集,Windows.h支援此巨集,它可以將輸出函數翻譯成適當的呼叫慣例,VB、VFP等也採用這個慣例。

函數名修飾約定也有不同,對於C編譯器,__stdcall呼叫慣例在輸出函數名前加上一個底線的前綴,後面加上一個「@」符號和其參數的byte數字,格式為 _functionname@number。

__cdecl呼叫慣例僅在輸出函數名前加上一個下劃線前綴,格式為_functionname。

__cdecl

__stdcall

C和C++程式的預設呼叫慣例 為了使用這種呼叫慣例,需要明確的加上__stdcall(或WINAPI)。就是return-type __stdcall function-name[(argument-list)]
呼叫函數(Callee)return,由呼叫者(Caller)調整堆疊。

呼叫者
    // call function
    // adjust stack
被呼叫函數
    // do work
    // return
呼叫函數(Callee)return,由呼叫函數(Callee)調整堆疊。圖示:

呼叫者
    // call function
被呼叫函數
    // do work
    // adjust stack
    // return
因為每個呼叫的地方都需要生成一段調整堆疊的程式碼,所以最後生成的檔案會比較大。 因為調整堆疊的程式碼只存在在一個地方(被呼叫函數的程式碼中),所以最後生成的檔案比較小。
函數的參數個數可變(就像printf函數一樣),因為只有呼叫者才知道它傳給被呼叫函數幾個參數,才能在呼叫結束時適當地調整堆疊。 函數的參數個數不能是可變的。
對於定義在C語言寫的程式中的輸出函數,函數名會保持原樣,不會被修飾。

對於定義在C++語言寫的程式中的輸出函數,函數名會被修飾, MSDN說Underscore character (_) is prefixed to names.

可通過在前面加上extern 「C」以去除函數名修飾。也可通過.def文件去除函數名修飾。

不論是C語言寫的程式中的輸出函數還是C++C語言寫的程式中的輸出函數,函數名都會被修飾。

對於定義在CC語言寫的程式的輸出函數,An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list.

對於定義在C++C語言寫的程式的輸出函數,好像更複雜,和__cdecl的情況類似。

好像只能通過.def文件去除函數名修飾。

_beginthread需要__cdecl的執行緒函數地址 _beginthreadex和CreateThread需要__stdcall的執行緒函數地址
兩者的參數傳遞順序都是從右向左。

為了讓VB可以使用,需要用__stdcall呼叫慣例來定義C/C++函數。請參看Microsoft KB153586 文章:How To Call C Functions That Use the _cdecl Calling Convention

當你LoadLibrary一個DLL文件後, 把GetProcAddress取得的函數地址傳給上面三個執行緒生成函數時,請務必確認實際定義在DLL文件的輸出函數符合呼叫慣例要求。否則,編譯成Release版後執行,可能會破壞堆疊,而且程式的行為會變的不可預測。

VC中的相關編譯開關:/Gd /Gr /Gz。另外,VC6中新增加的 /GZ 編譯開關可以幫你檢查堆疊問題。

在VC7.1及以前的編譯器中,預設的編譯選項是/Gd,如果沒有指定呼叫慣例,那麼編譯器就使用 __cdecl。

而在VS2005中,預設的編譯選項變成了/Gz,但是對非可變參數的函數,使用__stdcall調用約定。

下面做個小實驗

定義四個函式分別使用__fastcall 、__stdcall 與__cdecl 與不指定四種呼叫慣例:

  1. #define DLLEXP extern “C" __declspec(dllexport)
  2. DLLEXP int MyFunc_Default(char *c,int X)
  3. {
  4. return X;
  5. }
  6. DLLEXP int __fastcall MyFunc_Fast(char *c,int X)
  7. {
  8. return X;
  9. }
  10. DLLEXP int __stdcall MyFunc_Std(char *c,int X)
  11. {
  12. return X;
  13. }
  14. DLLEXP int __cdecl MyFunc_Cdecl(char *c,int X)
  15. {
  16. return X;
  17. }

編譯完成後使用tdump與impdef來觀察這些函式的輸出結果

經過簡單的實驗可以將一些的差異列出,並且可找出編譯器預設的呼叫慣例:

呼 叫 慣 例 原 始 函 式 Borland C++Builder Microsoft Visual C++
__cdecl MyFunc_cdcel _MyFunc_cdcel MyFunc_cdcel
__stdcall MyFunc_std MyFunc_std _MyFunc_std@8
__fastcall MyFunc_fast @MyFunc_fast @MyFunc_fast@8
MyFunc_default _MyFunc_default MyFunc_default
預 設 呼 叫 慣 例 __cdecl __cdecl

這實驗的目的就是比較不同呼叫慣例時編譯器不同會有什麼差異,才能讓BCB++跟VC++互相使用對方的DLL

 

One Response to “__stdcall ,__cdecl的區別”

  1. […] 六月 17, 2008 · 歸檔於 VC++/C++/C 前面寫了這篇,感覺很亂,再來深入探討一次 […]


發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 變更 )

Twitter picture

You are commenting using your Twitter account. Log Out / 變更 )

Facebook照片

You are commenting using your Facebook account. Log Out / 變更 )

Google+ photo

You are commenting using your Google+ account. Log Out / 變更 )

連結到 %s