__stdcall詳解(下)

1.       _cdecl

(1). C Declaration的縮寫,表示C語言默認的函數調用方法,實際上也是C++的默認的函數調用方法。

(2). 所有參數從右到左依次入棧,這些參數由調用者清除,稱爲手動清棧。具體所示:調用方的函數調用->被調用函數的執行->被調用函數的結果返回->調用方清除調整堆棧

(3). 被調用函數無需要求調用者傳遞多少參數,調用者傳遞過多或者過少的參數,甚至完全不同的參數都不會產生編譯階段的錯誤。總的來說函數的參數個數可變的(就像printf函數一樣),因爲只有調用者才知道它傳給被調用函數幾個參數,才能在調用結束時適當地調整堆棧。

(4). 因爲每個調用的地方都需要生成一段調整堆棧的代碼,所以最後生成的文件較大。

 

 2.       _stdcall(CALLBACK/WINAPI)

(1). Standard Call的縮寫,要想函數按照此調用方式必須在函數名加入_stdcall,通常_ win32 api 應該是_stdcall調用規則。通過VC++編寫的DLL欲被其他語言編寫的程序調用,應將函數的調用方式聲明爲_stdcall 方式,WINAPI都採用這種方式。

(2).  所有參數從右到左依次入棧,如果是調用類成員的話,最後一個入棧的是this指針。具體所示:調用方的函數調用->被調用函數的執行-> 被調用方清除調整堆棧->被調用函數的結果返回

(3).  這些堆棧中的參數由被調用的函數在返回後清除,使用的指令是 retn XX表示參數佔用的字節數,CPUret之後自動彈出X個字節的堆棧空間。稱爲自動清棧。

(4).  函數在編譯的時候就必須確定參數個數,並且調用者必須嚴格的控制參數的生成,不能多,不能少,否則返回後會出錯。總的來說,就是函數的參數個數不能是可變的。是從 _cdecl 修改而來, _stdcall 不支持可變參數,並且清棧由被調用者負責,其他的都一樣

(5).  因爲只需在被調用函數的地方生成一段調整堆棧的代碼,所以最後生成的文件較小。

 

3  PASCAL Pascal語言的函數調用方式,也可以在C/C++中使用,參數壓棧順序與前兩者相反。返回時的清棧方式忘記了。。。

 

4.       _fastcall 是編譯器指定的快速調用方式。由於大多數的函數參數個數很少,使用堆棧傳遞比較費時。因此_fastcall通常規定將前兩個(或若干個)參數由寄存器傳遞,其餘參數還是通過堆棧傳遞。不同編譯器編譯的程序規定的寄存器不同。返回方式和_stdcall相當。

 

5.       _thiscall 是爲了解決類成員調用中this指針傳遞而規定的。_thiscall要求把this指針放在特定寄存器中,該寄存器由編譯器決定。VC使用ecxBorlandC++編譯器使用eax。返回方式和_stdcall相當。

 

6.       _fastcall  _thiscall涉及的寄存器由編譯器決定,因此不能用作跨編譯器的接口。所以Windows上的COM對象接口都定義爲_stdcall調用方式。

 

 7.       C中不加說明默認函數爲_cdecl方式(C中也只能用這種方式),C++也一樣,但是默認的調用方式可以在IDE環境中設置。

 

8.       帶有可變參數的函數必須且只能使用_cdecl方式,例如下面的函數:

      int printf(char * fmtStr, ...);

int scanf(char * fmtStr, ...);

9.       函數名修飾

(1). _cdecl :對於_cdecl而言,如果對於定義在C程序文件(編譯器會通過後綴名爲.C判斷)的輸出函數,函數名會保持原樣;對於定義在C++程序文件中的輸出函數,函數名會被修飾(10)。爲使函數名不被修飾,有兩種方法:A.可通過在前面加上extern “c”以去除函數名修飾;B. 可通過.def文件去除函數名修飾。

(2). _stdcall:無論是C程序文件中的輸出函數還是C++程序文件中的輸出函數,函數名都會被修飾。對於定義在C++程序文件中的輸出函數,好像更復雜,和_cdecl的情況類似。去除函數名修飾方法:只能通過.def文件去除函數名修飾。

10.  函數名修飾規則:

(1). 爲什麼要函數名修飾:

      函數名修飾就是編譯器在編譯期間創建的一個字符串,用來指明函數的定義和原型。LINK程序或其他工具有時需要指定函數的名字修飾來定位函數的正確位置。多少情況下程序員並不需要知道函數的名字修飾,LINK程序或其他工具會自動區分他們。當然,在某些情況下需要指定函數名修飾,例如在c++程序中,爲了讓LINK程序或其他工具能夠匹配到正確的函數名字,就必須爲重載函數後一些特殊函數(如構造函數和析構函數)指定名字修飾。另一種需要指定函數名修飾的情況是在彙編程序中調用CC++函數。

(2). C語言:

      對於_stdcall調用約定,編譯器和鏈接器會在輸出函數名前加上一個下劃線前綴,函數名後面加上一個“@”符號和其參數的字節數,例如_functionname@number_cdecl調用約定僅在輸出函數名前加上一個下劃線前綴,例如_functionname_fastcall調用約定在輸出函數名前加上一個 “@“符號,後面也是一個”@“符號和其參數的字節數,例如@functionname@number

(3). C++語言:

   C++的函數名修飾規則有些複雜,但是信息更充分,通過分析修飾名不僅能夠知道函數的調用方式,返回值類型,參數個數甚至參數類型。不管__cdecl__fastcall還是__stdcall調用方式,函數修飾都是以一個“?”開始,後面緊跟函數的名字,再後面是參數表的開始標識和按照參數類型代號拼出的參數表。對於__stdcall方式,參數表的開始標識是“@@YG”,對於__cdecl方式則是“@@YA”,對於__fastcall方式則是“@@YI”。參數表的拼寫代號如下所示:

X--void   

D--char   

E--unsigned char   

F--short   

H--int   

I--unsigned int   

J--long   

K--unsigned longDWORD

M--float   

N--double   

_N—bool

U—struct

....

指針的方式有些特別,用PA表示指針,用PB表示const類型的指針。後面的代號表明指針類型,如果相同類型的指針連續出現,以“0”代替,一個“0”代表一次重複。U表示結構類型,通常後跟結構體的類型名,用“@@”表示結構類型名的結束。函數的返回值不作特殊處理,它的描述方式和函數參數一樣,緊跟着參數表的開始標誌,也就是說,函數參數表的第一項實際上是表示函數的返回值類型。參數表後以“@Z”標識整個名字的結束,如果該函數無參數,則以“Z”標識結束。下面舉兩個例子,假如有以下函數聲明:

int Function1(char *var1,unsigned long);

其函數修飾名爲“?Function1@@YGHPADK@Z”,而對於函數聲明:

oid Function2();

其函數修飾名則爲“?Function2@@YGXXZ” 

對於C++的類成員函數(其調用方式是thiscall),函數的名字修飾與非成員的C++函數稍有不同,首先就是在函數名字和參數表之間插入以“@”字符引導的類名;其次是參數表的開始標識不同,公有(public)成員函數的標識是“@@QAE”,保護(protected)成員函數的標識是“@@IAE”,私有(private)成員函數的標識是“@@AAE”,如果函數聲明使用了const關鍵字,則相應的標識應分別爲“@@QBE”“@@IBE”“@@ABE”。如果參數類型是類實例的引用,則使用“AAV1”,對於const類型的引用,則使用“ABV1”

11.   查看函數的名字修飾

 有兩種方式可以檢查你的程序中的函數的名字修飾:使用編譯輸出列表或使用Dumpbin工具。使用/FAc/FAs/FAcs命令行參數可以讓編譯器輸出函數或變量名字列表。使用dumpbin.exe /SYMBOLS命令也可以獲得obj文件或lib文件中的函數或變量名字列表。此外,還可以使用 undname.exe 將修飾名轉換爲未修飾形式。

12.   _beginthread需要_cdecl的線程函數地址,_beginthreadex_CreateThread需要_stdcall的線程函數地址。

13.   #define CALLBACK __stdcall //這就是傳說中的回調函數
#define WINAPI __stdcall //這就是傳說中的WINAPI
#define WINAPIV __cdecl
#define APIENTRY WINAPI //DllMain的入口就在這裏
#define APIPRIVATE __stdcall
#define PASCAL __stdcall


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