論調用約定

在第一篇技術博客"動態鏈接庫簡介"中說到了調用約定對函數名改編的影響,當時沒有詳細說明,這裏跟大家一起討論一下調用約定


常見的調用約定

  • 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個方面的內容

  1. 函數參數的壓棧順序

  2. 由調用者還是被調用者把參數彈出棧

  3. 產生函數修飾名的方法(調用約定和編譯方式共同決定)

 

_stdcall

  1. standard call,標準調用約定,就是WINAPI的調用約定,Pascal程序的缺省調用方式,通常用於windowsAPIDelphi調用約定,每一個WINDOWSAPI函數都是__stdcall類型的

windows API都是函數參數個數固定。所以由函數自身清理堆棧由於函數體本身知道傳進來的參數個數,因此被調用的函數可以在返回前用一條ret n指令直接清理傳遞參數的堆棧。

  1. 2.      函數採用從右到左的壓棧方式

  2. 3.      被調函數(函數自己)在退出時清空堆棧

  3. 函數修飾名隨調用約定和編譯種類(CC++)的不同而變化
    C
    編譯方式
    在函數名前面加上下劃線前綴,在函數名後加上"@"和參數的字節數
    格式爲:_functionname@number



wKioL1QCsdfBudNIAAIQFLL181g764.jpg

wKiom1QCsMDB6T1uAACu5VB7jqE705.jpg

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

示例:

wKioL1QCsdeBpsICAAHzIXAJJoQ074.jpg

導出到DLL,查看

wKioL1QCsdfCoNZ-AAD-bnwXcZk696.jpg

  1. __stdcall可以寫成_stdcall

在跨(開發)平臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。

那麼爲什麼還需要_cdecl呢?當我們遇到這樣的函數如fprintf()它的參數是可變的,不定長的,被調用函數自己事先無法知道參數的長度,事後的清除工作也無法正常的進行,因此,這種情況我們只能使用_cdecl。到這裏我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。


_cdecl

  1. 1.      也叫做C調用約定(是C Declaration的縮寫(declaration,聲明))

      _cdecl:CC++程序的缺省調用約定(也是VC默認的方式)

      __cdecl:C/C++MFC程序默認使用的調用約定
         由於C/C++中函數可以是可變參數的,所以由調用函數清理堆棧

  1. 2.      函數參數按照從右到左的順序入棧

  2. 3.      由調用函數把參數彈出棧以清理堆棧(實現可變參數的函數只能使用該調用約定)

  3. 4.      函數修飾名隨調用約定和編譯種類(CC++)的不同而變化


C
編譯方式

輸出函數名不變。

VS編譯的時候,還是原函數名

wKiom1QCsMDwPgX7AAIGNZcnrIw684.jpg

wKioL1QCsdjjQ5FbAAC3VTD9u_A761.jpg

C++編譯方式
__cdecl
調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@YG”變爲“@@YA”

wKiom1QCsMHShW3AAAHye2JF4hw093.jpg

導出函數到DLL文件,查看

wKioL1QCsdjA2JWpAADN4DZCdGA325.jpg

  1.     由於每一個使用__cdecl約定的函數都要包含清理堆棧的代碼,所以產生的可執行文件大小會比較大

  2.    當遇到可變參數的時候,使用_cdecl

      程序中沒有涉及可變參數,最好使用_stdcall關鍵字

_fastcall

用於對性能要求非常高的場合。__fastcall約定將函數的從左邊開始的兩個大小不大於4個字節(DWORD)的參數分別放在ECXEDX寄存器,其餘的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的堆棧。__fastcall可以寫成_fastcall

 

C編譯方式

__fastcall調用約定在輸出函數名前加上一個“@”符號,後面也是一個“@”符號和其參數的字節數,格式爲@functionname@number

wKiom1QCsMGwVlrQAAHWOfSOvXs128.jpg

導出函數到DLL文件

wKiom1QCsMGSxV7OAADNgUWdhTY957.jpg

C++編譯時

__fastcall調用約定:
規則同上面的_stdcall調用約定,只是參數表的開始標識由上面的“@@YG”變爲“@@YI”

wKioL1QCsdnw3JBqAAHZunEZKBk722.jpg

導出函數到DLL文件

wKiom1QCsMGA55s6AADCu8A7jaE382.jpg

thiscall

thiscall僅僅應用於"C++"成員函數this指針存放於CX寄存器,參數從右到左壓。thiscall不是關鍵詞,因此不能被程序員指定。

naked call

採用1-4的調用約定時,如果必要的話,進入函數時編譯器會產生代碼來保存ESIEDIEBXEBP寄存器,退出函數時則產生代碼恢復這些寄存器的內容。

naked call不產生這樣的代碼。naked call不是類型修飾符,故必須和_declspec共同使用。


到這裏,關於第一篇博客"動態鏈接庫簡介"裏的全部內容都寫完了,由於當時筆記整理的比較亂,各位博友如果發現有不足和錯誤的地方,希望能提出寶貴的意見和建議



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