在第一篇技術博客"動態鏈接庫簡介"中說到了調用約定對函數名改編的影響,當時沒有詳細說明,這裏跟大家一起討論一下調用約定
常見的調用約定
stdcall
cdecl
fastcall
thiscall
naked call
VC中的定義
#defineCALLBACK __stdcall
#defineWINAPI __stdcall
#defineWINAPIV __cdecl
#defineAPIENTRY WINAPI
#defineAPIPRIVATE __stdcall
#definePASCAL __stdcall
#definecdecl _cdecl
#ifndefCDECL
#defineCDECL _cdecl
#endif
調用約定包含了3個方面的內容
函數參數的壓棧順序
由調用者還是被調用者把參數彈出棧
產生函數修飾名的方法(由調用約定和編譯方式共同決定)
_stdcall
standard call,標準調用約定,就是WINAPI的調用約定,Pascal程序的缺省調用方式,通常用於windowsAPI中,Delphi調用約定,每一個WINDOWSAPI函數都是__stdcall類型的
windows API都是函數參數個數固定。所以由函數自身清理堆棧由於函數體本身知道傳進來的參數個數,因此被調用的函數可以在返回前用一條ret n指令直接清理傳遞參數的堆棧。
2. 函數採用從右到左的壓棧方式
3. 被調函數(函數自己)在退出時清空堆棧
函數修飾名隨調用約定和編譯種類(C或C++)的不同而變化
C編譯方式
在函數名前面加上下劃線前綴,在函數名後加上"@"和參數的字節數。
格式爲:_functionname@number
C++編譯方式
__stdcall調用約定:
1、以“?”標識函數名的開始,後跟函數名;
2、函數名後面以“@@YG”標識參數表的開始,後跟參數表;
3、參數表以代號表示:
X--void ,
D--char,
E--unsignedchar,
F--short,
H--int,
I--unsignedint,
J--long,
K--unsignedlong,
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”
示例:
導出到DLL,查看
__stdcall可以寫成_stdcall
在跨(開發)平臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。
那麼爲什麼還需要_cdecl呢?當我們遇到這樣的函數如fprintf()它的參數是可變的,不定長的,被調用函數自己事先無法知道參數的長度,事後的清除工作也無法正常的進行,因此,這種情況我們只能使用_cdecl。到這裏我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。
_cdecl
1. 也叫做C調用約定(是C Declaration的縮寫(declaration,聲明))
_cdecl:C和C++程序的缺省調用約定(也是VC默認的方式)
__cdecl:C/C++和MFC程序默認使用的調用約定
由於C/C++中函數可以是可變參數的,所以由調用函數清理堆棧
2. 函數參數按照從右到左的順序入棧
3. 由調用函數把參數彈出棧以清理堆棧(實現可變參數的函數只能使用該調用約定)
4. 函數修飾名隨調用約定和編譯種類(C或C++)的不同而變化
C 編譯方式
輸出函數名不變。
VS編譯的時候,還是原函數名
C++編譯方式
__cdecl調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@YG”變爲“@@YA”。
導出函數到DLL文件,查看
由於每一個使用__cdecl約定的函數都要包含清理堆棧的代碼,所以產生的可執行文件大小會比較大
當遇到可變參數的時候,使用_cdecl
程序中沒有涉及可變參數,最好使用_stdcall關鍵字
_fastcall
用於對性能要求非常高的場合。__fastcall約定將函數的從左邊開始的兩個大小不大於4個字節(DWORD)的參數分別放在ECX和EDX寄存器,其餘的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的堆棧。__fastcall可以寫成_fastcall
C編譯方式
__fastcall調用約定在輸出函數名前加上一個“@”符號,後面也是一個“@”符號和其參數的字節數,格式爲@functionname@number。
導出函數到DLL文件
C++編譯時
__fastcall調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@YG”變爲“@@YI”。
導出函數到DLL文件
thiscall
thiscall僅僅應用於"C++"成員函數。this指針存放於CX寄存器,參數從右到左壓。thiscall不是關鍵詞,因此不能被程序員指定。
naked call
採用1-4的調用約定時,如果必要的話,進入函數時編譯器會產生代碼來保存ESI,EDI,EBX,EBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容。
naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。
到這裏,關於第一篇博客"動態鏈接庫簡介"裏的全部內容都寫完了,由於當時筆記整理的比較亂,各位博友如果發現有不足和錯誤的地方,希望能提出寶貴的意見和建議