一、概念
1)_stdcall調(diào)用
_stdcall是Pascal程序的缺省調(diào)用方式,參數(shù)采用從右到左的壓棧方式,由調(diào)用者完成壓棧操作,
被調(diào)函數(shù)自身在返回前清空堆棧。 WIN32 Api都采用_stdcall調(diào)用方式,這樣的宏定義說明了問題: #define WINAPI _stdcall
按C編譯方式,_stdcall調(diào)用約定在輸出函數(shù)名前面加下劃線,后面加“@”符號和參數(shù)的字節(jié)數(shù),形如_f
unctionname@number.
Therefore, the function declared as int func( int a, double b )
is decorated as follows: _func@12
.
按C++編譯方式,可參看(三)
2)_cdecl調(diào)用
_cdecl是C/C++的缺省調(diào)用方式,參數(shù)采用從右到左的壓棧方式,
由調(diào)用者完成壓棧操作
,傳送參數(shù)的內(nèi)存棧由調(diào)用者維護。
_cedcl約定的函數(shù)只能被C/C++調(diào)用,每一個調(diào)用它的函數(shù)都包含清空堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會比調(diào)用_stdcall函數(shù)的大。
按C編譯方式,_cdecl調(diào)用約定僅在輸出函數(shù)名前面加下劃線,形如_functionname。
按C++編譯方式,可參看(三)
二、區(qū)別
幾乎我們寫的每一個WINDOWS API函數(shù)都是__stdcall類型的,首先,需要了解兩者之間的區(qū)別:
WINDOWS的函數(shù)調(diào)用時需要用到棧(STACK,一種先入后出的存儲結(jié)構(gòu))。當(dāng)函數(shù)調(diào)用完成后,棧需要清除,這里就是問題的關(guān)鍵,如何清除?如果我
們的函數(shù)使用了_cdecl,那么棧的清除工作是由調(diào)用者,用COM的術(shù)語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產(chǎn)生棧的方式不
盡相同,那么調(diào)用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數(shù)自己解決清除工作。所以,在跨(開發(fā))
平臺的調(diào)用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現(xiàn))。那么為什么還需要_cdecl呢?當(dāng)我們遇到這樣的函數(shù)如
fprintf()它的參數(shù)是可變的,不定長的,被調(diào)用者事先無法知道參數(shù)的長度,事后的清除工作也無法正常的進行,因此,這種情況我們只能使用
_cdecl。到這里我們有一個結(jié)論,如果你的程序中沒有涉及可變參數(shù),最好使用__stdcall關(guān)鍵字。
三、編譯時函數(shù)名修飾約定規(guī)則
1)首先介紹一下函數(shù)修飾名,C”
或者“C++”函數(shù)在內(nèi)部(編譯和鏈接)通過修飾名識別。修飾名是編譯器在編譯函數(shù)定義或者原型時生成的字符串。有些情況下使用函數(shù)的修飾名是必要的,如
在模塊定義文件里頭指定輸出“C++”重載函數(shù)、構(gòu)造函數(shù)、析構(gòu)函數(shù),又如在匯編代碼里調(diào)用“C””或“C++”函數(shù)等。修飾名由函數(shù)名、類名、調(diào)用約定、返回類型、參數(shù)等共同決定。名字修飾約定隨調(diào)用約定和編譯種類(C或C++)的不同而變化。函數(shù)名修飾約定隨編譯種類和調(diào)用約定的不同而不同。下面詳細(xì)說明在C++編譯器中修飾名的約定方法,在C編譯器中的變化已經(jīng)在(一)中闡述。
2)C++編譯時函數(shù)名修飾約定規(guī)則
__stdcall調(diào)用約定:
1)、以"?"標(biāo)識函數(shù)名的開始,后跟函數(shù)名;
2)、函數(shù)名后面以"@@YG"標(biāo)識參數(shù)表的開始,后跟參數(shù)表;
3)、參數(shù)表以代號表示:
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--表示指針,后面的代號表明指針類型,如果相同類型的指針連續(xù)出現(xiàn),以"0"代替,一個"0"代表一次重復(fù);
4)、參數(shù)表的第一項為該函數(shù)的返回值類型,其后依次為參數(shù)的數(shù)據(jù)類型,指針標(biāo)識在其所指數(shù)據(jù)類型前;
5)、參數(shù)表后以"@Z"標(biāo)識整個名字的結(jié)束,如果該函數(shù)無參數(shù),則以"Z"標(biāo)識結(jié)束。
其格式為"
?functionname@@YG*****@Z"或"
?functionname@@YG*XZ",例如 int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z” void Test2()
-----“?Test2@@YGXXZ”
__cdecl調(diào)用約定:
規(guī)則同上面的_stdcall調(diào)用約定,只是參數(shù)表的開始標(biāo)識由上面的"@@YG"變?yōu)?@@YA"。
四、在C++編譯時,引入c庫里面的函數(shù)的處理
如果Add(int a, int b)是在c語言編譯器編譯,而在c++文件使用,由(三)知,因為c編譯器和c++編譯器對函數(shù)名的解釋不一樣(c++編譯器解釋函數(shù)名的時候要考慮函數(shù)參數(shù),這樣是了方便函數(shù)重載,而在c語言中不存在函數(shù)重載的問題),會出現(xiàn)鏈接錯誤。 哪么該如何處理呢?這是關(guān)鍵字extern就派上用場了。
而在c++文件使用c編譯器編譯過的函數(shù),則需要在c++文件中聲明:extern "C" Add(int a, int b),使用extern "C",實質(zhì)就是告訴c++編譯器,該函數(shù)是c庫里面的函數(shù)。請保持我的名稱,不要給我生成用于鏈接的函數(shù)修飾名。
另外順便說一下extern的另一個用途,extern可以置于變量或者函數(shù)前,以標(biāo)示變量或者函數(shù)的定義在別的文件中,提示編譯器遇到此變量和函數(shù)時在其他模塊中尋找其定義。#inlcude "***.***" 和 int f() 等價于 extern int f() ;即 extern 申明的函數(shù)前不需要加引用頭文件就能用。
參考文獻:
博客:
http://www.cnblogs.com/Winston/archive/2008/09/11/1289391.html