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