教你看懂C++類庫函數定義之三---_stdcall

一切從一個C++ 類庫頭文件開始,現在在做一個C++的項目,期間用到一個開源的界面庫DUILib(類似MFC),這個東西還不錯能很容易的寫出漂亮的界面,比如QQ的界面,可以去下載下來研究研究,地址:http://code.google.com/p/duilib/

廢話不多說,我比較困擾的是UIWebBrowser.h這個頭文件,雖然是C++寫的,但裏面包含太多大學C++課本以外的東西,第一遍看下來跟看天書一樣,裏面有很多的不惑,接下來我們一個一個解開。

首先看一下這個函數定義:

virtual HERSULT STDMETHODCALLTYPE GetTypeInfoCount( __RPC__out UINT *pctinfo);

這一篇詳細介紹  __stdcall


上篇文章我們知道#define STDMETHODCALLTYPE __stdcall ,那__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 X,X表示參數佔用的字節數,CPU在ret之後自動彈出X個字節的堆棧空間。稱爲自動清棧。 (4). 函數在編譯的時候就必須確定參數個數,並且調用者必須嚴格的控制參數的生成,不能
多,不能少,否則返回後會出錯。總的來說,就是函數的參數個數不能是可變的。是從 _cdecl 修改而來, _stdcall 不支持可變參數,並且清棧由被調用者負責,其他的都一樣 (5). 因爲只需在被調用函數的地方生成一段調整堆棧的代碼,所以最後生成的文件較小。  
3. PASCAL 是Pascal語言的函數調用方式,也可以在C/C++中使用,參數壓棧順序與前兩者相反。返回時的清棧方式忘記了。。。  
4. _fastcall 是編譯器指定的快速調用方式。由於大多數的函數參數個數很少,使用堆棧傳
遞比較費時。因此_fastcall通常規定將前兩個(或若干個)參數由寄存器傳遞,其餘參數還是通過堆棧傳遞。不同編譯器編譯的程序規定的寄存器不同。返回方式和_stdcall相當。  
5. _thiscall 是爲了解決類成員調用中this指針傳遞而規定的。_thiscall要求把this指針放
在特定寄存器中,該寄存器由編譯器決定。VC使用ecx,Borland的C++編譯器使用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程序或其他工具能夠匹配到正確的函數名字,就必須爲重載函數後一些特殊函數(如構造函數和析構函數)指定名字修飾。另一種需要指定函數名修飾的情況是在彙編程序中調用C或C++函數。 (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 long(DWORD) 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 

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