so true

          心懷未來,開創未來!
          隨筆 - 160, 文章 - 0, 評論 - 40, 引用 - 0
          數據加載中……

          windows下的調用規范

          先把幾篇不錯的文章貼在這里:
          一個程序(進程,應用程序實例)運行過程占用內存包含:代碼區、全局區/靜態區、常量區、堆棧、堆。其中前四種的內存是在系統為進程分配的進程地址 空間中,而堆是系統的內存,用戶可以通過malloc\new等進行申請使用權。堆棧簡稱:棧,其實堆棧是用在函數上,所以也叫“函數堆棧”,當一個函數 被調用時,進程內核對象為其在進程的地址空間的堆棧部分進行分配一定的棧內存給該函數使用,函數堆棧用于:

          (1)在進入函數之前,保存“返回地址”和“環境變量”;返回地址是指該函數結束后,剛從哪里繼續執行下去。

          (2)在進入函數之后,保存實參或實參拷貝、局部變量。 

          下圖是一個函數堆棧例子:

          點擊查看原始尺寸

          函數原型:[連接規范] 函數類型 [調用約定] 函數名 參數列表 {......}

          調用約定: 調用約定是決定函數實參或實參拷貝進入和退出函數堆棧的方式以及函數堆棧釋放的方式,簡單講就是:實參或實參拷貝入棧、出棧、函數堆棧釋放的方式。在Win32下有四種:

          (1)__cdecl : 這個是c/c++默認的調用約定,實參是以參數列表從右依次向左入棧,出棧相反,函數堆棧是由調用方幫忙釋放。主要用在那些帶有可變參數的函數上。

          (2)__stdcall : 這個是WIN API的調用約定,其實COM接口等只要是申明定義接口都要顯示指定其調用約定為__stdcall。實參是以參數列表從右依次向左入棧,出棧相反,函數 堆棧是由被調用方自己釋放。但是若函數含有可變參數那么即使顯示指定了__stdcall,編譯器也會自動把其改變成__cdecl。

          (3)_thiscall : 這個是類的非靜態成員函數默認的調用約定,其不能用在含有可變參數的函數上,否則編譯會出錯,實參是以參數列表從右依次向左入棧,出棧相反,函數堆棧是由 被調用方自己釋放。但是注意,類非靜態成員函數內部都隱含有一個this指針,該指針不是存放在函數堆棧上,而是直接存放在CPU寄存器上。

          (4)__fastcall : 快速調用,這樣的函數,它們的實參并不是存放在函數堆棧上,而是直接存放在CPU寄存器上,所以不存在入棧、出棧、函數堆棧釋放。


          _stdcall 與 _cdecl 的區別
          幾乎我們寫的每一個WINDOWS API函數都是__stdcall類型的,首先,需要了解兩者之間的區別: WINDOWS的函數調用時需要用到棧(STACK,一種先入后出的存儲結構)。當函數調用完成后,棧需要清除,這里就是問題的關鍵,如何清除??如果我們的函數使用了_cdecl,那么棧的清除工作是由調用者。這樣帶來了一個棘手的問題,不同的編譯器產生棧的方式不盡相同,那么調用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的問題就解決了,函數自己解決清除工作。所以,在跨(開發)平臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。那么為什么還需要_cdecl呢?當我們遇到這樣的函數如 fprintf()它的參數是可變的,不定長的,被調用者事先無法知道參數的長度,事后的清除工作也無法正常的進行,因此,這種情況我們只能使用 _cdecl。到這里我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。

          Microsoft的幾種函數調用約定(calling convention)總結

            在DLL的調用中,要特別注意調用約定的不同,在windows中,有__cdecl,__stdcall,__fastcall,thiscall,naked等幾種調用約定。
          #include <stdio.h>
          int fun();
          int main(int argc, char *argv[])
          {
           
           printf("this is a local variable:%d",fun(2,3,1));
           return 0;
          }
          int fun(int a,int b,int c)
          {
           return a+b+c;
          }
          1:__cdecl
             __cdecl調用約定是C/C++的默認調用約定,因為是由調用者清理堆棧,所以可以實現變長參數。并且因為要求每個函數
          調用都包含清理堆棧的代碼,所以其生成的可執行文件要比用__stdcall生成的尺寸大,下面是這種調用約定的特征。
          (1):參數傳遞由右向左。
          (2):呼叫者清理堆棧。
          (3):C編譯時函數符號解析是在函數名前加一個_。
          (4):/Gd選項強迫整個文件按照該調用約定進行編譯。

          下面是windows下的反匯編代碼:
          ;在主函數中將參數3,2入棧,然后調用被解析后的函數名稱_fun,
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push 1
           push 3
           push 2
           call _fun
          ;將esp加12,進行堆棧清理
           add esp, 12
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push ebp
           mov ebp, esp
           mov eax, DWORD PTR _a$[ebp]
           add eax, DWORD PTR _b$[ebp]
           pop ebp
           ret 0
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
          2:__stdcall
          __stdcall是用來呼叫Win32 API函數的一種調用約定,由被調用者清理堆棧,所以不能將擁有變長參數的函數聲明為此種
          調用約定。
          (1):參數傳遞由右向左
          (2):被調用函數清理堆棧
          (3):C編譯時函數符號解析是在函數名前加_,函數名稱后面加@,其后加十進制表示的參數所占的字節,例如有:
               int func(int a,int b,int c),解析后為: _func@12
          (4): /Gz選項強迫整個文件按照該調用約定進行編譯。
          (5): 函數指針聲明方式:typedef BOOL (__stdcall *funcname_ptr)(int a,int b);
          下面是windows下的反匯編代碼:
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push 1
           push 3
           push 2
           call _fun@12
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push ebp
           mov ebp, esp
           mov eax, DWORD PTR _a$[ebp]
           add eax, DWORD PTR _b$[ebp]
           add eax, DWORD PTR _c$[ebp]
           pop ebp
           ret 12
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
          3:__fastdcall
            __fastcall指定函數參數通過寄存器來傳遞(只是約定,并不是一種保證)
          (1):前兩個DWORD或者字節數小于DWORD的參數通過ECX和EDX寄存器傳遞,其余參數從右向左進行傳遞
          (2):被調用者清理堆棧。
          (3):C編譯時函數符號解析是在函數名前后都加@,在末尾加十進制表示的參數所占的字節,例如:
               int func(int a,int b,int c),解析后為: _@func@12
          (4): /Gr選項強迫整個文件按照該調用約定進行編譯(main函數除外)。
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push 1
           mov edx, 3
           mov ecx, 2
           call @fun@12
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push ebp
           mov ebp, esp
           sub esp, 8
           mov DWORD PTR _b$[ebp], edx
           mov DWORD PTR _a$[ebp], ecx
           mov eax, DWORD PTR _a$[ebp]
           add eax, DWORD PTR _b$[ebp]
           add eax, DWORD PTR _c$[ebp]
           mov esp, ebp
           pop ebp
           ret 4
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
          4:thiscall:
            thiscall是調用C++類成員函數的默認調用約定
          (1):this指針被放置在ECX中
          (2):被呼叫者清理函數堆棧
          (3):因為thiscall不是關鍵字,所以不能被顯示應用于代碼中
          #include<iostream>
          using namespace std;
          class temp
          {
           public:
            void print(int,int);
          };
          void temp::print(int a,int b)
          {
           printf("the value is %d",a+b);
          }
          int main()
          {
           temp a;
           a.print(1,2);
           return 0;
          }
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push ebp
           mov ebp, esp
           push ecx     ;ecx中存放this指針的值
           mov DWORD PTR _this$[ebp], ecx
           mov eax, DWORD PTR _a$[ebp]
           add eax, DWORD PTR _b$[ebp]
           push eax
           push OFFSET FLAT:$SG8428
           call _printf
           add esp, 8
           mov esp, ebp
           pop ebp
           ret 8
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push 2
           push 1
           lea ecx, DWORD PTR _a$[ebp]
           call ?print@temp@@QAEXHH@Z   ; temp::print
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
          5:naked
             這種調用約定僅僅被用在VxD驅動程序中。
          (1):參數傳遞方式為從右向左
          (2):呼叫者清理被調用函數堆棧
          #include<stdio.h>
          int NakedCallFunction(int ,int);
          int main()
          {
           NakedCallFunction(2,3);
           return 0;
          }
          __declspec(naked) int NakedCallFunction(int a,int b)
          {
           __asm
           {
            ret
           }
          }
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
           push ebp
           mov ebp, esp
           push 3
           push 2
           call ?NakedCallFunction@@YAHHH@Z  ; NakedCallFunction
           add esp, 8
           xor eax, eax
           pop ebp
           ret 0
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
          ; Function compile flags: /Odt
          _a$ = 8       ; size = 4
          _b$ = 12      ; size = 4
          ?NakedCallFunction@@YAHHH@Z PROC NEAR   ; NakedCallFunction
           ret 0
          ?NakedCallFunction@@YAHHH@Z ENDP   ; NakedCallFunction
          ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
          6:還有幾種調用約定,例如:__pascal, __fortran, __syscall只是在相關開發工具中使用,并且不被Microsoft
          所支持。
          注:以上函數符號名解析都以C為標準,C++的解析規則非常復雜,這里有一個簡要的描述:
          http://www.microsoft.com/china/community/program/originalarticles/techdoc/dll.mspx



          posted on 2010-04-10 12:17 so true 閱讀(301) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 乌拉特后旗| 青州市| 宁夏| 平定县| 札达县| 棋牌| 河南省| 崇阳县| 翁源县| 东丰县| 尚志市| 阳高县| 安塞县| 锦屏县| 济阳县| 伊宁县| 习水县| 太仓市| 广河县| 辽阳市| 东源县| 永寿县| 奉节县| 万荣县| 漳平市| 彭州市| 元朗区| 桃园市| 榆社县| 游戏| 余姚市| 赣州市| 扶余县| 化德县| 乡宁县| 广西| 蒙自县| 崇义县| 抚顺县| 广南县| 鹿邑县|