__cdecl __fastcall與 __stdcall
呼叫約定:
__cdecl __fastcall與 __stdcall,三者都是呼叫約定(Calling convention),它決定以下內容:1)函數參數的壓堆疊順序,2)由呼叫者還是被呼叫者把參數即現堆疊,3)以及產生函數修飾名的方法。
1、__stdcall呼叫約定:函數的參數自右向左通過堆疊傳遞,被呼叫的函數在傳回前清理傳輸參數的記憶體堆疊,
2、_cdecl是C和C++程式的預設呼叫方式。每一個呼叫它的函數都包含清空堆疊的程式碼,所以產生的可執行檔案大小會比呼叫_stdcall函數的大。函數釆用從右到左的壓堆疊方式。注意:對于可變參數的成員函數,始終使用__cdecl的轉換方式。
3、__fastcall呼叫約定:它是通過暫存器來傳輸參數的(實際上,它用ECX和EDX傳輸前兩個雙字(DWORD)或更小的參數,剩餘的參數仍舊自右向左壓堆疊傳輸,被呼叫的函數在傳回前清理傳輸參數的記憶體堆疊)。
4、thiscall僅僅應用于"C++"成員函數。this指標存放于CX暫存器,參數從右到左壓。thiscall不是關鍵詞,因此無法被式設計師指定。
5、naked call釆用1-4的呼叫約定時,如果必要的話,進入函數時編譯器會產生程式碼來儲存ESI,EDI,EBX,EBP暫存器,結束函數時則產生程式碼回復這些暫存器的內容。naked call不產生這樣的程式碼。naked call不是類別修飾符,故必須和_declspec共同使用。
呼叫約定可以通過工程設定:Setting...\C/C++ \Code Generation項進行選取,預設狀態為__cdecl。
名字修飾約定:
1、修飾名(Decoration name):"C"或者"C++"函數在內部(編譯和鏈結)通過修飾名識別
2、C編譯時函數名修飾約定規則:
__stdcall呼叫約定在匯出函數名前加上一個底線前置,后面加上一個"@"象徵式和其參數的位元組數,格式為_functionname@number,例如:function(int a, int b),其修飾名為:_function@8
__cdecl呼叫約定僅在匯出函數名前加上一個底線前置,格式為_functionname。
__fastcall呼叫約定在匯出函數名前加上一個"@"象徵式,后面也是一個"@"象徵式和其參數的位元組數,格式為@functionname@number。
3、C++編譯時函數名修飾約定規則:
__stdcall呼叫約定:
1)、以"?"識別函數名的開始,后跟函數名;
2)、函數名后面以"@@YG"識別參數表的開始,后跟參數表;
3)、參數表以代號表示:
X--void ,
D--char,
E--unsigned char,
F--short,
H--int,
I--unsigned int,
J--long,
K--unsigned long,
M--float,
N--double,
_N--bool,
PA--表示指標,后面的代號表明指標類別,如果相同類別的指標連續出現,以"0"代替,一個"0"代表一次重複;
4)、參數表的第一項為該函數的傳回值類別,其后依次為參數的資料類別,指標識別在其所指資料類別前;
5)、參數表后以"@Z"識別整個名字的結束,如果該函數無參數,則以"Z"識別結束。
其格式為"?functionname@@YG*****@Z"或"?functionname@@YG*XZ",例如
int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”
void Test2() -----“?Test2@@YGXXZ”
__cdecl呼叫約定:
規則同上面的_stdcall呼叫約定,只是參數表的開始識別由上面的"@@YG"變為"@@YA"。
__fastcall呼叫約定:
規則同上面的_stdcall呼叫約定,只是參數表的開始識別由上面的"@@YG"變為"@@YI"。
VC++對函數的省缺聲明是"__cedcl",將只能被C/C++呼叫.
注意:
1、_beginthread需要__cdecl的執行緒函數位址,_beginthreadex和CreateThread需要__stdcall的執行緒函數位址。
2、一般WIN32的函數都是__stdcall。而且在Windef.h中有如下的定義:
#define CALLBACK __stdcall
#define WINAPI __stdcall
3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl*FunPointer)(int a, int b);
修飾符的書寫順序如上。
4、extern "C"的作用:如果Add(int a, int b)是在c語言編譯器編譯,而在c++檔案使用,則需要在c++檔案中聲明:extern "C" Add(int a, int b),因為c編譯器和c++編譯器對函數名的解譯不一樣(c++編譯器解譯函數名的時候要考慮函數參數,這樣是了方便函數重載,而在c語言中不存在函數重載的問題),使用extern "C",實質就是告訴c++編譯器,該函數是c程式庫裡面的函數。如果不使用extern "C"則會出現鏈結錯誤。
一般象如下使用:
#ifdef _cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C extern
#endif
#ifdef _cplusplus
extern "C"{
#endif
EXTERN_C int func(int a, int b);
#ifdef _cplusplus
}
#endif
5、MFC提供了一些巨集,可以使用AFX_EXT_CLASS來代替__declspec(DLLexport),并修飾類名,從而導出類,AFX_API_EXPORT來修飾函數,AFX_DATA_EXPORT來修飾變數
AFX_CLASS_IMPORT:__declspec(DLLexport)
AFX_API_IMPORT:__declspec(DLLexport)
AFX_DATA_IMPORT:__declspec(DLLexport)
AFX_CLASS_EXPORT:__declspec(DLLexport)
AFX_API_EXPORT:__declspec(DLLexport)
AFX_DATA_EXPORT:__declspec(DLLexport)
AFX_EXT_CLASS:#ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
6、DLLMain負責起始化(Initialization)和結束(Termination)工作,每當一個新的程式或者該程式的新的執行緒存取DLL時,或者存取DLL的每一個程式或者執行緒不再使用DLL或者結束時,都會呼叫DLLMain。但是,使用TerminateProcess或TerminateThread結束程式或者執行緒,不會呼叫DLLMain。
7、一個DLL在記憶體中只有一個實例
DLL程式和呼叫其匯出函數的程式的關係:
1)、DLL與程式、執行緒之間的關係
DLL模組被對映到呼叫它的程式的虛擬位址空間。
DLL使用的記憶體從呼叫程式的虛擬位址空間配置,只能被該程式的執行緒所存取。
DLL的控點可以被呼叫程式使用;呼叫程式的控點可以被DLL使用。
DLLDLL可以有自己的資料段,但沒有自己的堆疊,使用呼叫程式的堆疊,與呼叫它的應用程式相同的堆疊型態。
2)、關于共用資料段
DLL定義的全域變數可以被呼叫程式存取;DLL可以存取呼叫程式的全域資料。使用同一DLL的每一個程式都有自己的DLL全域變數實例。如果多個執行緒并發存取同一變數,則需要使用同步機制;對一個DLL的變數,如果希望每個使用DLL的執行緒都有自己的值,則應該使用執行緒局部儲存(TLS,Thread Local Strorage)。
原文位址 http://tb.blog.csdn.net/TrackBack.aspx?PostId=706590