DLL中調(diào)用約定和名稱(chēng)修飾(一)收藏新一篇: DLL中調(diào)用約定和名稱(chēng)修飾(三) | 舊一篇: DLL中調(diào)用約定和名稱(chēng)修飾(二)DLL中調(diào)用約定和名稱(chēng)修飾(一)調(diào)用約定(Calling Convention)是指在程序設(shè)計(jì)語(yǔ)言中為了實(shí)現(xiàn)函數(shù)調(diào)用而建立的一種協(xié)議。這種協(xié)議規(guī)定了該語(yǔ)言的函數(shù)中的參數(shù)傳送方式、參數(shù)是否可變和由誰(shuí)來(lái)處理堆棧等問(wèn)題。不同的語(yǔ)言定義了不同的調(diào)用約定。 在C++中,為了允許操作符重載和函數(shù)重載,C++編譯器往往按照某種規(guī)則改寫(xiě)每一個(gè)入口點(diǎn)的符號(hào)名,以便允許同一個(gè)名字(具有不同的參數(shù)類(lèi)型或者是不同的作用域)有多個(gè)用法,而不會(huì)打破現(xiàn)有的基于C的鏈接器。這項(xiàng)技術(shù)通常被稱(chēng)為名稱(chēng)改編(Name Mangling)或者名稱(chēng)修飾(Name Decoration)。許多C++編譯器廠商選擇了自己的名稱(chēng)修飾方案。 因此,為了使其它語(yǔ)言編寫(xiě)的模塊(如Visual Basic應(yīng)用程序、Pascal或Fortran的應(yīng)用程序等)可以調(diào)用C/C++編寫(xiě)的DLL的函數(shù),必須使用正確的調(diào)用約定來(lái)導(dǎo)出函數(shù),并且不要讓編譯器對(duì)要導(dǎo)出的函數(shù)進(jìn)行任何名稱(chēng)修飾。 1.調(diào)用約定(Calling Convention)調(diào)用約定用來(lái)處理決定函數(shù)參數(shù)傳送時(shí)入棧和出棧的順序(由調(diào)用者還是被調(diào)用者把參數(shù)彈出棧),以及編譯器用來(lái)識(shí)別函數(shù)名稱(chēng)的名稱(chēng)修飾約定等問(wèn)題。在Microsoft VC++ 6.0中定義了下面幾種調(diào)用約定,我們將結(jié)合匯編語(yǔ)言來(lái)一一分析它們: 1、__cdecl __cdecl是C/C++和MFC程序默認(rèn)使用的調(diào)用約定,也可以在函數(shù)聲明時(shí)加上__cdecl關(guān)鍵字來(lái)手工指定。采用__cdecl約定時(shí),函數(shù)參數(shù)按照從右到左的順序入棧,并且由調(diào)用函數(shù)者把參數(shù)彈出棧以清理堆棧。因此,實(shí)現(xiàn)可變參數(shù)的函數(shù)只能使用該調(diào)用約定。由于每一個(gè)使用__cdecl約定的函數(shù)都要包含清理堆棧的代碼,所以產(chǎn)生的可執(zhí)行文件大小會(huì)比較大。__cdecl可以寫(xiě)成_cdecl。 下面將通過(guò)一個(gè)具體實(shí)例來(lái)分析__cdecl約定: 在VC++中新建一個(gè)Win32 Console工程,命名為cdecl。其代碼如下: int __cdecl Add(int a, int b); //函數(shù)聲明 void main() { Add(1,2); //函數(shù)調(diào)用 } int __cdecl Add(int a, int b) //函數(shù)實(shí)現(xiàn) { return (a + b); } 函數(shù)調(diào)用處反匯編代碼如下: ;Add(1,2); push 2 ;參數(shù)從右到左入棧,先壓入2 push 1 ;壓入1 call @ILT+0(Add) (00401005) ;調(diào)用函數(shù)實(shí)現(xiàn) add esp,8 ;由函數(shù)調(diào)用清棧 2、__stdcall __stdcall調(diào)用約定用于調(diào)用Win32 API函數(shù)。采用__stdcal約定時(shí),函數(shù)參數(shù)按照從右到左的順序入棧,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的棧,函數(shù)參數(shù)個(gè)數(shù)固定。由于函數(shù)體本身知道傳進(jìn)來(lái)的參數(shù)個(gè)數(shù),因此被調(diào)用的函數(shù)可以在返回前用一條ret n指令直接清理傳遞參數(shù)的堆棧。__stdcall可以寫(xiě)成_stdcall。 還是那個(gè)例子,將__cdecl約定換成__stdcall: int __stdcall Add(int a, int b) { return (a + b); } 函數(shù)調(diào)用處反匯編代碼:
; Add(1,2); push 2 ;參數(shù)從右到左入棧,先壓入2 push 1 ;壓入1 call @ILT+10(Add) ( 函數(shù)實(shí)現(xiàn)部分的反匯編代碼: ;int __stdcall Add(int a, int b) push ebp mov ebp,esp sub esp,40h push ebx push esi push edi lea edi,[ebp-40h] mov ecx,10h mov eax,0CCCCCCCCh rep stos dword ptr [edi] ;return (a + b); mov eax,dword ptr [ebp+8] add eax,dword ptr [ebp+0Ch] pop edi pop esi pop ebx mov esp,ebp pop ebp ret 8 ;清棧 3、__fastcall __fastcall約定用于對(duì)性能要求非常高的場(chǎng)合。__fastcall約定將函數(shù)的從左邊開(kāi)始的兩個(gè)大小不大于4個(gè)字節(jié)(DWORD)的參數(shù)分別放在ECX和EDX寄存器,其余的參數(shù)仍舊自右向左壓棧傳送,被調(diào)用的函數(shù)在返回前清理傳送參數(shù)的堆棧。__fastcall可以寫(xiě)成_fastcall。 依舊是相類(lèi)似的例子,此時(shí)函數(shù)調(diào)用約定為__fastcall,函數(shù)參數(shù)個(gè)數(shù)增加2個(gè): int __fastcall Add(int a, double b, int c, int d) { return (a + b + c + d); } 函數(shù)調(diào)用部分的匯編代碼: ;Add(1, 2, 3, 4); push 4 ;后兩個(gè)參數(shù)從右到左入棧,先壓入4 mov edx,3 ;將int類(lèi)型的3放入edx push 40000000h ;壓入double類(lèi)型的2 push 0 mov ecx,1 ;將int類(lèi)型的1放入ecx call @ILT+0(Add) (00401005) ;調(diào)用函數(shù)實(shí)現(xiàn) 函數(shù)實(shí)現(xiàn)部分的反匯編代碼:
; int __fastcall Add(int a, double b, int c, int d) push ebp mov ebp,esp sub esp,48h push ebx push esi push edi push ecx lea edi,[ebp-48h] mov ecx,12h mov eax,0CCCCCCCCh rep stos dword ptr [edi] pop ecx mov dword ptr [ebp-8],edx mov dword ptr [ebp-4],ecx ;return (a + b + c + d); fild dword ptr [ebp-4] fadd qword ptr [ebp+8] fiadd dword ptr [ebp-8] fiadd dword ptr [ebp+10h] call __ftol (004011b8) pop edi pop esi pop ebx mov esp,ebp pop ebp ret 0Ch ;清棧 關(guān)鍵字__cdecl、__stdcall和__fastcall可以直接加在要輸出的函數(shù)前,也可以在編譯環(huán)境的Setting...->C/C++->Code Generation項(xiàng)選擇。它們對(duì)應(yīng)的命令行參數(shù)分別為/Gd、/Gz和/Gr。缺省狀態(tài)為/Gd,即__cdecl。當(dāng)加在輸出函數(shù)前的關(guān)鍵字與編譯環(huán)境中的選擇不同時(shí),直接加在輸出函數(shù)前的關(guān)鍵字有效。
|
|