• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            luqingfei@C++

            為中華之崛起而崛起!
            兼聽則明,偏聽則暗。

            Win32匯編--分析窗口程序

             

            分析窗口程序

             

            了解了消息驅動體系的工作流程以后,讓我們來分析如何用Win32匯編實現這一切。

             

            模塊和句柄

            模塊的概念

            一個模塊代表的是一個運行中的exe文件或dll文件,用來代表這個文件中所有的代碼和資源,磁盤上的文件不是模塊,裝入內存后運行時就叫做模塊。

             

            一個應用程序調用其他DLL中的API時,這些DLL文件被裝入內存,就產生了不同的模塊,為了區分地址空間中的不同模塊,每個模塊都有一個唯一的模塊句柄來標識。

             

            很多API函數中都要用到程序的模塊句柄,以便利用程序中的各種資源,所以在程序中一開始就先取得模塊句柄并存放到一個全局變量中可以省去很多的麻煩,在Win32中,模塊句柄在數值上等于程序在內存中裝入的起始地址。

             

            取模塊句柄使用的API函數是GetModuleHandle,它的使用方法是:

                   invoke GetModuleHandle, lpModuleName

            lpModuleName參數是一個指向含有模塊名稱字符串的指針,可以用這個函數取得程序地址空間中各個模塊的句柄,例如,如果想得到User32.dll的句柄以便使用其中包含的圖標資源,那么可以如下使用:

                   szUserDll        db    ‘User32.dll’,0

                   invoke           GetModuleHandle, addr szUserDll

                   .if                  eax

                                        mov hUserDllHandle,eax

                   .endif

            如果使用參數NULL,調用GetModuleHandle,那么得到的是調用者本模塊的句柄,如下所示:

                   invoke            GetModuleHandl,NULL

                   mov               hInstance,eax

                   可以注意到,把返回的句柄放到了hInstance變量里而并不是放在hModule中,為什么是hInstance呢?Instance是“實例”,它的概念來自于Win16Win16中不同運行程序的地址空間并不是完全隔離的,一個可執行文件運行后形成“模塊”,多次加載同一個可執行文件時,這個“模塊”是公用的,為了區分多次加載的“拷貝”,就把每個“拷貝”叫做實例,每個實例均用不同的“實例句柄”(hInstance)值來標識它們。

             

            但在Win32中,程序運行時是隔離的,每個實例都使用自己私有的4GB空間,都認為自己是唯一的,不存在一個模塊的多個實例的問題,實際上在Win32中,實例句柄就是模塊句柄,但很多API原型中用到模塊句柄的時候使用的名稱還是沿用hInstance,所以我們還是把變量名稱取為hInstance

             

            C++語言的編程中,hInstance通過WinMain由系統傳入,WinMain的原型是:

                   WinMain(hInstance, hPrevInstance, lpszCmdParam, nCmdShow)

            程序不用自己去獲得hInstance,但在Win32匯編中必須自己獲取,如果不了解hModule就是hInstance的話,就無法得知如何得到hInstance,因為并沒有一個類似于GetInstanceHandle之類的API函數。

             

             

            句柄是什么

            隨著分析的深入,句柄(handle)一詞也出現得頻繁了起來,“句柄”是什么呢?句柄只是一個數值而已,它的值對程序來說是沒有意義的,它只是Windows用來表示各種資源的編號而已,所以只有Windows才知道怎么使用它來引用各種資源。

             

            舉例說明,屏幕上已經有10窗口,Windows把它們從110編號,應用程序又建立了一個窗口,現在Windows把它編號為11,然后把11當做窗口句柄返回給應用程序,應用程序并不知道11代表的是什么,但在操作窗口的時候,把11當做句柄傳給WindowsWindows自然可以根據這個數值查出是哪個窗口。當該窗口關閉的時候,11這個編號作廢。第二次運行的時候,如果屏幕上現有5個窗口,那么現在句柄可能就是6了,所以,應用程序并不用關心句柄的具體數值是多少。打個比方,可以把句柄當做是商場中寄放書包時營業員給的紙條,紙條上的標記用戶并不知道是什么意思,但把它交還給營業員的時候,她自然會找到正確的書包。

             

            Windows中幾乎所有的東西都是用句柄來標識的,文件句柄、窗口句柄、線程句柄和模塊句柄等,同樣道理,不必關心它們的值究竟是多少,拿來用就是了!

             

             

            創建窗口

            在創建窗口之前,先要談到“類”。“類”的概念讀者都不陌生,主要是為了把一組物體的相同屬性歸納整理起來封裝在一起,以便重復使用,在“類”已定義的屬性基礎上加上其他個性化的屬性,就形成了各式各樣的個體。

             

            Windows中創建窗口同樣使用這樣的層次結構。首先定義一個窗口類,然后在窗口類的基礎上添加其他的屬性建立窗口。不同一步到位的辦法是因為很多窗口的基本屬性和行為都是一樣的,如按鈕、文本輸入框和選擇框等,對這些東西Windows都預定義了對應的類,使用時直接使用對應的類名建立窗口就可以了。只有用戶自定義的窗口才需要先定義自己的類,再建立窗口。這樣可以節省資源。

             

            注冊窗口類

            建立窗口類的方法是在系統中注冊,注冊窗口類的API函數是RegisterClassEx,最后的Ex是擴展的意思,因為它是Win16RegisterClass的擴展。一個窗口類定義了窗口的一些主要屬性,如:圖標、光標、背景色、菜單和負責處理該窗口所屬消息的函數。這些屬性并不是分成多個參數傳遞過去的,而是定義在一個WNDCLASSEX結構中,再把結構的地址當參數一次性傳遞給RegisterClassExWNDCLASSEXWNDCLASS結構的擴展。

             

            WNDCLASSEX的結構定義為:

                   WNDCLASSEX     STRUCT

                          CbSize                  DWORD               ?             ;結構的字節數

                          Style                     DWORD               ?             ;類風格

                          LpfnWndProc        DWORD               ?             ;窗口過程的地址

                          CbClsExtra            DWORD               ?

                          CbWndExtra          DWORD               ?

                          HInstance              DWORD               ?             ;所屬的實例句柄

                          HIcon                   DWORD               ?             ;窗口圖標

                          HCursor                DWORD               ?             ;窗口光標

                          HbrBackground      DWORD               ?             ;背景色

                          LpszMenuName     DWORD               ?             ;窗口菜單

                          LpszClassName      DWORD               ?             ;類名字符串的地址

                          HIconSm               DWORD               ?             ;上圖標

                   WNDCLASSEX    ENDS

             

             

            Win32匯編源程序中,注冊窗口類的代碼如下:

                   local        @stWndClass:WNDCLASSEX            ;定義一個WNDCLASSEX結構

             

                   invoke     RtlZeroMemory, addr @stWndClass, sizeof @stWndClass

                   invoke     LoadCursor, 0, IDC_ARROW

                   mov        @stWndClass.hCursor, eax

                   push              hInstance

                   pop         @stWndClass.hInstance

                   mov        @stWndClass.cbSize, sizeof WNDCLASSEX

                   mov        @stWndClass.style, CS_HREDRAW or CS_VREDRAW

                   mov        @stWndClass.lpfnWndProc, offset _ProcWinMain

                   mov        @stWndClass.hbrBackground, COLOR_WINDOW + 1

                   mov        @stWndClass.lpszClassName, offset szClassName

                   invoke     RegisterClassEx, addr @stWndClass

            程序定義了一個WNDCLASSEX結構的變量@stWndClass,用RtlZeroMemory將它填為全零,再填寫結構的各個字段,這樣,沒有賦值的部分就保持為0,結構各字段的含義如下:

            hIcon      圖標句柄,指定顯示在窗口標題欄左上角的圖標。Windows已經預定課外作業了一些圖標,同樣,程序也可以使用在資源文件中定義的圖標,這些圖標的句柄可以用LoadIcon函數獲得。例子程序沒有用到圖標,所以Windows給窗口顯示了一個默認的圖標。

             

            hCursor   光標句柄,指定了鼠標在窗口中光標形狀。同樣,Windows也預定義了一些光標,可以用LoadCursor獲取它們的句柄,IDC_ARROWWindows預定義的箭頭光標,如果想使用自定義的光標,也可以自己在資源文件中定義。

             

            lpszMenuName      指定窗口上顯示的默認菜單,它指向一個字符串,描述資源文件中菜單的名稱,如果資源文件中菜單是用數值定義的,那么這里使用菜單資源的數值。窗口中的菜單也可以在建立窗口函數CreateWindowsEx的參數中指定。如果在兩個地方都沒有指定,那么建立的窗口上就沒有菜單。

             

            hInstance              指定要注冊的窗口類屬于哪個模塊,模塊句柄在程序開始的地方已經用GetModuleHandle函數獲得。

             

            cbSize            指定WNDCLASSEX結構的長度,用sizeof偽操作符來獲取。很多Win32 API參數中的結構都有cbSize字段,它主要是用來區分結構的版本,當以后新增了一個字段時,cbSize就相應增大,如果調用的時候cbSize還是老的長度,表示運行的是基于舊結構的程序,這樣可以防止使用無效的字段。

             

            style        窗口風格。CS_HREDRAWCS_VREDRAW表示窗口的寬度或高度改變時是否重畫窗口。比較重要的是CS_DBLCLKS風格,指定了它,Windows才會把在窗口中快速兩次單擊鼠標的行為翻譯為雙擊消息WM_LBUTTONDBLCLK發給窗口過程。

             

            hbrBackground      窗口客戶區的背景色。前面的hbr表示它是一個刷子(Brush)的句柄,刷子一詞形象地表示了填充一個區域的著色模式。Windows預定義了一些刷子,如BLACK_BRUSHWHITE_BRUSH等,可以用下列語句來得到它們的句柄:

                   invoke     GetStockObject, WHITE_BRUSH

            但在這里也可以使用顏色值,Windows已經預定義了一些顏色值,分別對應窗口各部分的顏色,如COLOR_BACKGROUNDCOLOR_HIGHLIGHTCOLOR_MENUCOLOR_WINDOWS等,使用顏色值的時候,Windows規定必須在顏色值上加1,所以在程序中的指令是:

                   mov        @stWndClass.hbrBackground, COLOR_WINDOWS + 1

             

            lpszClassName指定程序員要建立的類命名,以便以后用這個名稱來引用它。這個字段是一個字符串指針,在程序里,它指向MyClass字符串中。

             

            cbWndExtracbClsExtra分別是在Windows內部保存的窗口結構和類結構中給程序員預留的空間大小,用來存放自定義的數據,它們的單位是字節。不使用自定義數據的話,這兩個字段就是0

             

            lpfnWndProc         是最重要的參數,它指定了基于這個類建立的窗口的窗口過程地址。通過這個參數,Windows就知道了在DispatchMessage函數中把窗口消息發到哪里去,一個窗口過程可以為多個窗口服務,只要這些窗口是基于同一個窗口類建立的。Windows中不同應用程序中的按鈕和文本框的行為都是一樣的,就是因為它們是基于相同的Windows預定義類建立的,它們背后的窗口過程其實是同一段代碼。

             

            結構中的style表示窗口的風格,Windows已經有一些預定義的值,它們是以CS_(Class Style的縮寫)開始的標識符,如下所示:

            CS_VREDRAW                    00000001H

            CS_HREDRAW                    00000002H

            CS_KEYCVTWINDOWS      00000004H

            CS_DBLCLKS                     00000008H

            CS_OWNDC                       00000020H

            CS_CLASSDC                     00000040H

             

            可以看到,這些預定義值實際上在使用不重復的數據位,所以可以組合起來使用,同時使用不同的預定義值并不會引起混淆。

             

            注意:對于不同二進制位組合的計算,“加”和“或”的結果是一樣的,在FirstWindows程序中用CS_HREDRAW or CS_VREDRAW來代表兩個組合,若用CS_HREDRAW + CS_VREDRAW也并沒有什么不同,但強烈建議使用or,因為如果不小心指定了兩個同樣的風格時:CS_HREDRAW or CS_VREDRAW or CS_VREDRAW和原來的數值是一樣的,而CS_HREDRAW + CS_VREDRAW + CS_VREDRAW就不對了,因為1 or 1 = 1,而1 + 1就等于2了。

             

             

            建立窗口

            接下來的步驟是在已經注冊的窗口類的基礎上建立窗口,使用“類”的原因是定義窗口的“共性”,建立窗口時肯定還要指定窗口的很多“個性化”的參數,如WNDCLASSEX結構中沒有定義的外觀、標題、位置、大小和邊框類型等屬性,這些屬性是在建立窗口時才指定的。

             

            和注冊窗口類時用一個結構傳遞所有參數不同,建立窗口時所有的屬性都是用單個參數的方式傳遞的,建立窗口的函數是CreateWindowEx(注意不要寫成CreateWindowsEx),同樣,它是Win16中的CreateWindow函數的擴展,主要表現在多了一個dwExStyle(擴展風格)參數,原因是Win32Win16中多了很多種窗口風格,原來的一個風格參數已經不夠用了。

            CreateWindowEx函數的使用方法是:

                   invoke     CreateWindowEx, dwExStyle, lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam

            12個參數很好理解:

            lpClassName          建立窗口使用的類名字符串指針,在FirstWindow程序中指向MyClass字符串,表示使用MyClass類建立窗口,這正是我們自己注冊的類,這樣一來,這個窗口就有“MyClass”的所有屬性,并且消息將被發到“MyClass”中指定的窗口過程中去,當然,這里也可以是Window預定義的類名。

             

            lpWindowName      指向表示窗口名稱的字符串,該名稱會顯示在標題欄上。如果該參數空白,則標題欄上什么都沒有。

             

            hMenu    窗口上要出現的菜單的句柄。在注冊窗口類的時候也定義了一個菜單,那是窗口的默認菜單,意思是如果這里沒有定義菜單(用參數NULL)而注冊窗口類時定義了菜單,則使用窗口類中定義的菜單;如果這里指定了菜單句柄,則不管窗口類中有沒有定義都將使用這里定義的菜單;兩個地方都沒有定義菜單句柄,則窗口上沒有菜單。另外,當建立的窗口是子窗口時(dwStyle中指定了WS_CHILD),這個參數是另一個含義,這時hMenu參數指定的是子窗口的ID號,這樣可以節省一個參數的位置,因為反正子窗口不會有菜單。

             

            hpParam         這是一個指針,指向一個欲傳給窗口的參數,這個參數在WM_CREATE消息中可以被獲取,一般情況下用不到這個字段。

             

            hInstance              模塊句柄,和注冊窗口類時一樣,指定了窗口所屬的程序模塊。

             

            hWndParent           窗口所屬的父窗口,對于普通窗口(相對于子窗口),這里的“父子”關系只是從屬關系,主要用來在父窗口銷毀時一同將其“子”窗口銷毀,并不會把窗口位置限制在父窗口的客戶區范圍內,但如果要建立的是真正的子窗口(dwStyle中指定了WS_CHILD的時候),這時窗口位置會被限制在父窗口的客戶區范圍內,同時窗口的坐標(xy)也是以父窗口的左上角為基準的。

             

            xy       指定窗口左上角位置,單位是像素。默認時可指定為CW_USEDEFAULT,這樣Windows會自動為窗口指定最合適的位置,當建立子窗口時,位置是以父窗口的左上角為基準的,否則,以屏幕左上角為基準。

             

            nWidthhHeight           窗口的寬度和高度,也就是窗口的大小,同樣是以像素為單位的。默認時可指定為CW_USEDEFAULT,這樣Windows會自動為窗口指定最合適的大小。

             

            窗口的兩個參數dwStyledwExStyle決定了窗口的外形和行為,dwStyle是從Win16開始就有的屬性,下表列出了一些常見的dwStyle定義,它們是一些以WSWindows Style的縮寫)為開頭的預定義值。

            預定義值

            16進制值

            含義

            WS_OVERLAPPED

            00000000h

            普通的重疊式窗口

            WS_POPUP

            80000000h

            彈出式窗口(沒有標題欄)

            WS_CHILD

            40000000h

            子窗口

            WS_MINIMIZE

            20000000h

            初始狀態是最小化的

            WS_VISIBLE

            10000000h

            初始狀態是可見的

            WS_DISABLED

            08000000h

            初始狀態是被禁止的

            WS_MAXIMIZE

            01000000h

            初始狀態是最大化的

            WS_BORDER

            00800000h

            單線條邊框

            WS_DLGFRAME

            00400000h

            對話框類型的邊框

            WS_VSCROLL

            00200000h

            帶垂直滾動條

            WS_HSCROLL

            00100000h

            帶水平滾動條

            WS_SYSMENU

            00080000h

            帶系統菜單(即帶標題欄左上角的圖標)

            WS_THICKFRAME

            00040000h

            可以拖動調整大小的邊框

             

            為了容易理解,Windows也為一些定義取了一些別名,同時,由于窗口的風格往往是幾種風格的組合,所以Windows也預定義了一些組合值,如下表所示:

            預定義值

            等效值

            WS_CHILDWINDOW

            WS_CHILD

            WS_TILED

            WS_OVERLAPPED

            WS_ICONIC

            WS_MINIMIZE

            WS_SIZEBOX

            WS_THICKFRAME

            WS_OVERLAPPEDWINDOW

            WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_THICKFRAME or WS_MINIMIZEBOX or WS_MAXINIZEBOX

            WS_TILEDWINDOW

            WS_OVERLAPPEDWINDOW

            WS_POPUPWINDOW

            WS_POPUP or WS_BORDER or WS_SYSMENU

             

            dwExStyleWin32中擴展的,它們是一些以WS_EX_開頭的預定義值,主要定義了一些特殊的風格,如下表所示:

            預定義值

            16進制值

            含義

            WS_EX_POPMOST

            00000008h

            總在頂層的窗口

            WS_EX_ACCEPTFILES

            00000010h

            允許窗口進行鼠標拖放操作

            WS_EX_TOOLWINDOW

            00000080h

            工具窗口(很窄的標題欄)

            WS_EX_WINDOWEDGE

            00000100h

            立體體的邊框

            WS_EX_CLIENTEDGE

            00000200h

            客戶區立體邊框

            WS_EX_OVERLAPPDWINDOW

             

            WS_EX_WINDOWEDGE or WS_EX_CLIENTEDGE

            WS_EX_PALETTEWINDOW

             

            WS_EX_POPMOST

             

            用預定義的組合值WS_EX_PALETTEWINDOW可以很方便地構成浮在其他窗口前面的工具欄。

             

            建立窗口的相關代碼如下:

                   invoke            CreateWindowEx, WS_EX_CLIENTEDGE, \

                                        offset szClassName, offset szCaptionMain, \

                                        WS_OVERLAPPPEDWINDOW, \

                                        100, 100, 600, 400, \

                                        NULL, NULL, hInstance, NULL

                   mov               hWinMain,eax

                   invoke            ShowWindow, hWinMain, SW_SHWONORMAL

                   invoke            UpdateWindow, hWinMain

             

            建立窗口以后,eax中傳回來的是窗口句柄,要把它保存起來,這時候,窗口雖已建立,但還沒有在屏幕上顯示出來,要用ShowWindow把它顯示出來,ShowWindow也可以用在另的地方,主要用來控制窗口的顯示狀態(顯示或隱藏),大小控制(最大化、最小化或原始大小)和是否激活(當前窗口還是背后的窗口),它用窗口句柄第一個參數,第二個參數則是顯示的方式。顯示方式有如下的預定義值:

            預定義值

            等效值

            SW_HIDE

            隱藏窗口,大小不變,激活狀態不變

            SW_MAXIMIZE

            最大化窗口,顯示狀態不變,激活狀態不變

            SW_MINIMIZE

            最小化窗口,顯示狀態不變,激活狀態不變

            SW_RESTORE

            從最大化或最小化恢復正常大小,顯示狀態不變,激活狀態不變

            SW_SHOW

            顯示并激活窗口,大小狀態不變

            SW_SHOWMAXIMIZED

            顯示并激活窗口,以最大化顯示

            SW_SHOWMINIMIZED

            顯示并激活窗口,以最小化顯示

            SW_SHOWMINOACTIVE

            顯示窗口并最小化,激活狀態不變

            SW_SHOWNA

            顯示窗口,大小狀態不變,激活狀態不變

            SW_SHOWNOACTIVATE

            顯示并從最大化或最小化恢復正常大小,激活狀態不變

            SW_SHOWNORMAL

            顯示并激活窗口,恢復正常大小(初始化時用這個參數)

             

            窗口顯示以后,用UpdateWindow繪制客戶區,它實際上就是向窗口發送了一條WM_PAINT消息。到此為止,一個頂層窗口就正常建立并顯示了。

             

            CreateWindowEx也可以用來建立子窗口,Windows中有很多預定義的子窗口類,如按鈕和文本框的類名分別是ButtonEdit。要建立一個按鈕,只要把lpClassName指向Button字符串就可以了。例如:

                                               .data

                   szButton                db           ‘button’,0

                   szButtonText          db           ‘&OK’,0

             

                          invoke     CreateWindowEx, NULL, \

                                        offset szButton, offset szButtonText, \

                                        WS_CHILD or WS_VISIBLE, \   

                                        10, 10, 65, 22, \

                                        hWnd, 1, hInstance, NULL

             

            FirstWindow的源程序中加入按鈕類的定義字符串“szButton”和按鈕文字字符串“szButtonText”,然后在窗口過程的WM_CREATE消息中加入建立按鈕的代碼,執行一下,窗口中就出現了一個按鈕。建立按鈕的時候,lpWindowName參數就是按鈕上的文字,風格則一定要指定WS_CHILD,建立的按鈕才會在我們的主窗口上,WS_VISIBLE也要同時指定,否則按鈕不會顯示出來,hMenu參數在這里用做表示子窗口ID,將它設置為1,在建立多個子窗口的時候,ID應該有所區別。

             

             

            消息循環

            消息循環的一般形式:

                   .while      TRUE

                                 invoke     GetMessage, addr @stMsg, NULL, 0, 0

                                 .break     .if eax == 0

                                 invoke     TranslateMessage, addr @stMsg

                                 invoke     DispatchMessage, addr @stMsg

                   .endw

             

            消息循環中的幾個函數要用到一個MSG結構,用來做消息傳遞:

                   MSG       STRUCT

                          Hwnd      DWORD        ?

                          Message DWORD        ?

                          WParam DWORD        ?

                          LParam   DWORD        ?

                          Time       DWORD        ?

                          Pt           POINT           <>

                   MSG       ENDS

            各個字段的含義是:

            hwnd      消息要發向的窗口句柄。

            message 消息標識符,在頭文件中以WM_開頭的預定義值(意思為Windows Message)。

            wParam 消息的參數之一。

            lParam    消息的參數之二。

            time        消息放入消息隊列的時間。

            pt           這是一個POINT的數據結構,表示消息放入消息隊列時的鼠標坐標。

            這個結構定義了消息的所有屬性,GetMessage函數就是從消息隊列中取出這樣一條消息來的:

                          invoke     GetMessage, lpMsg, hWnd, wMsgPilterMin, wMsgFilterMax

            函數的lpMsg指向一個MSG結構,函數會在這里返回取到的消息,hWnd參數指定要獲取哪個窗口的消息,例子中指定為NULL表示獲取的是所有本程序所屬窗口的消息wMsgFilterMinwMsgFilterMax0表示獲取所有編號的消息。

             

            GetMessage函數從消息隊列里取得消息,填寫好MSG結構并返回,如果獲取的消息是WM_QUIT消息,那么eax中的返回值是0,否則eax返回非零值,所以用.break .if eax == 0來檢查返回值,如果消息隊列中有WM_QUIT則退出消息循環。

             

            TranslateMessageMSG結構傳給Windows進行一些鍵盤消息的轉換,當有鍵盤按下和放開時,Windows產生WM_KEYDOWNWM_KEYUPWM_SYSKEYDOWNWM_SYSKEYUP消息,但這些消息的參數中包含的是按鍵的掃描碼,轉換成常用的ASCII碼要經過查表,很不方便,TranslateMessage遇到鍵盤消息則將掃描碼轉換成ASCII碼并在消息隊列中插入WM_CHARWM_SYSCHAR消息,參數就是轉換好的ASCII碼,如此一來,要處理鍵盤消息的話只要處理WM_CHAR消息就好了。遇到別的消息則TranslateMessage不做處理。

             

            最后,由DispatchMessage將消息發送到窗口對應的窗口過程去處理。窗口過程返回后DispatchMessage函數才返回,然后開始新一輪消息循環。

             

             

            其它形式的消息循環:

            GetMessage函數是程序空閑的時候主動將控制權交還給Windows的一種方式Windows是一個搶占式的多任務系統,任務之間每20ms切換一次,試想一下,如果窗口程序在主窗口中采用死循環等待,消息由Windows直接發送到窗口過程,那么程序會是下列這種樣子:

                   invoke     CreateWindow,

                   invoke     ShowWindow,

                   invoke     UpdateWindow,

                   .while      dwQuitFlag == 0           ;要退出時在窗口過程中設置dwQuitFlag

                   .endw

                   invoke     ExitProcess,

             

            但這樣一來,即使程序在空閑狀態,輪到自己的20ms時間片的時候,CPU時間就會全部消耗在.while循環中,使用GetMessage的時候,輪到應用程序時間片的時候,如果消息隊列里還沒有消息,那么程序還是停留在GetMessage內部,這時就可以由Windows當家作主沒收這20ms的時間片,如此保證了CPU資源的合理應用。

             

            如果應用程序想把所有時間充分用回來,消息隊列里沒有消息的時候不讓GetMessageWindows內部等待,拱手交出屬于自己的CPU時間,那么消息循環可以是下列這種樣子:

                   .while      TRUE

                          invoke     PeekMessage, addr @stMsg, NULL, 0, 0, PM)REMOVE

                          .if           eax

                                        .break     .if @stMsg.message == WM_QUIT

                                        invoke     TranslateMessage, addr @stMsg

                                        invoke     DispatchMessage, addr @stMsg

                          .else

                                        <做其他工作>

                          .endif

                   .endw

            PeekMessage是一個類似于GetMessage的函數,區別在于當消息隊列里有消息的時候,PeekMessage取回消息,并在eax中返回非零值,沒有消息的時候它會直接返回,并在eax中返回零。所以在返回非零值的時候,程序檢查消息是否是WM_QUIT,是則結束消息循環,不是則用標準流程處理消息;返回零的時候,表示是空閑時間,程序就可以做其他工作了,但插入做其他工作的代碼執行時間不能過長,以不越過10ms為好,否則會影響正常的消息處理,使窗口的反應看起來很遲鈍。如果必須處理很長時間的工作,那么應該將它分成很多小部分處理,以便有足夠的頻率來用PeekMessage來檢查消息。PeekMessage的前面4個參數和GetMessage是相同的,增加了最后一個參數,PM_REMOVE表示取回消息的同時從消息隊列里刪除,否則用PM_NOREMOVE

             

             

            窗口過程

            窗口過程是給Windows回調用的,它必須遵循規定的格式。對窗口過程的子程序名并沒有規定,對Windows來說,窗口過程的地址才是唯一需要的,例子程序中的子程序名是_ProcWinMain,讀者可以改用任何名稱。窗口過程子程序的參數格式為:

                   WindowProc   proc        hwnd, uMsg, wParam, lParam

            第一個參數是窗口句柄,一個窗口過程可能為多個基于同一個窗口類的窗口服務,所以Windows回調的時候必須指出要操作的窗口,否則窗口過程不知道要去處理哪個窗口,FirstWindow程序只建立了一個窗口,所以每次傳遞過來的hwnd和用CreateWindowEx函數返回的窗口句柄是一樣的;

            第二個參數是消息標識,后面兩個參數是消息的兩個參數。這4個參數和消息循環中MSG結構中的前4個字段是一樣的。

             

            窗口過程的結構

            窗口過程一般有如下結構:

            WindowsProc        proc        uses ebx edi esi, hWnd, uMsg, wParam, lParam

             

                                        mov        eax,uMsg

                                        .if           eax == WM_XXX

                                                      <處理WM_XXX消息>

                                        .elseif      eax == WM_YYY

                                                      <處理WM_YYY消息>

                                        .elseif      eax == WM_CLOSE

                                                      invoke     DestroyWindow, hWinMain

                                                      invoke     PostQuitMessage, NULL

                                        .else

                                                      invoke     DefWindowProc, hWnd, uMsg, wParam, lParam

                                                      ret

                                        .endif

             

                                        mov        eax,eax

                                        ret

            WindowProc          endp

             

            該過程主要是對uMsg參數中的消息編號構成一個分支結構,對于需要處理的消息分別處理。不感興趣的消息則交給DefWindowProc來處理。

             

            要注意的是窗口過程中要注意保存ebxediesiebp寄存器,高級語言中不用自己操心這一點,匯編中就要注意了,Windows內部將這4個寄存器當指針使用,如果返回時改變了它們的值,程序會馬上崩潰。proc后面的uses偽操作在子程序進入和退出時自動按插上pushpop寄存器指令,來保護這些寄存器的值。其實不僅是在窗口過程中是這樣,所有由應用程序提供給Windows的回調函數都必須遵循這個規定,如定時器回調函數等,所有Win32 API也遵循這個規定,所以調用API后,ebxediesiebp寄存器的值總是不會被改變的,但ecxedx的值就不一定了。

             

            uMsg參數指定的消息有一定的范圍,Windows標準窗口中已經預定義的值在0~03ffh之間(1024個),用戶可以自定義一些消息,通過SendMessage等函數傳給窗口過程做自定義的處理工作,這時可以使用的值是從0400h開始的,WM_USER就定義為00000400h,當程序員定義多個用戶消息的時候,一般使用WM_USER+1WM_USER+2,之類的定義方法。

             

            wParamlParam參數是消息所附帶的參數,它隨消息的不同而不同,對于不同的消息,它們的含義必須分別從手冊中查明:如WM_MOUSEMOVE消息中,wParam是標志,lParam是鼠標位置;而在WM_GETTEXT消息中,wParam是要獲取的字符數,lParam是緩沖地址;而對于WM_COPY消息來說,它不需要額外的信息,所以兩個參數都沒有定義。

             

            處理了不同的消息,必須返回規定的值給Windows,返回值也需要分別從手冊中查明,比如處理WM_CREATE消息的時候,如果返回0表示成功;如果程序無法初始化,如申請內存失敗,那么可以返回-1Windows就不會繼續窗口的創建過程。一些消息的返回值則沒有定義,但大部分的消息處理以后都以返回0表示成功,所以程序中把默認的返回語句放在最后,將eax清零后返回,如果在處理某個消息的時候需要返回不同的值,可以以分支中將eax賦值后直接用ret指令返回。對于DefWindowProc的返回值,我們不對它進行干涉,所以直接將eax不做修改地用ret返回。

             

            WM_CLOSE消息是按下了窗口右上角的“關閉”按鈕后收到的,程序可以在這里處理和關閉窗口相關的事情,一般是相關資源的釋放工作,如釋放內存、保存工作和提示用戶是否保存工作等,如記事本程序在未保存的時候單擊“關閉”按鈕,會有提示框提示是否先保存文件,單擊“取消”按鈕的話,記事本不會關閉,這個步驟就是在WM_CLOSE消息處理中完成的。如果處理WM_CLOSE消息時直接返回,那么窗口不會關閉,因為這個消息只是Windows通知窗口用戶剛才單擊了“關閉”按鈕而已,窗口采用什么樣的行為是窗口的事。當窗口決定關閉的時候,需要程序自己調用DestroyWindow來摧毀窗口,并用PostQuitMessage向消息循環發送WM_QUIT消息來退出消息循環。調用PostQuitMessage時的參數是退出碼,就是GetMessage收到的WM_QUITMSG結構wParam字段中的東西,在這里使用NULL

             

            PostQuitMessage是初學者容易遺漏的函數,如果沒有這條語句,外觀上窗口是從屏幕上消失了,但主程序中的消息循環卻沒有收到WM_QUIT,結果還在那里打轉。常有人調試的時候丟了這條語句,結果再一次編譯的時候就收到錯誤:LINK fatal error LNK1104:cannot open file “xxx.exe”,表示exe文件現在不可寫。

             

            Windows為什么不在窗口摧毀的時候自動發送一個WM_QUIT消息,而必須由用戶程序自己通過PostQuitMessage函數發送呢?其實很好理解:因為屏幕上可能不止一個窗口,Windows無法確定哪個窗口的關閉代表著程序結束。試想一下,用戶打開了一個輸入參數的小窗口,單擊“確定”按鈕后關閉并回到主窗口,Windows卻不分三七二十一自動發送了一個WM_QUIT,程序就會莫名其妙地退出了。

             

             

            收到消息的順序

            窗口過程收到消息是有一定順序的,收到第一條消息并不是從消息循環開始以后,而是在CreateWindowEx中就開始了,顯示和刷新窗口的函數ShowWindowUpdateWindow也向窗口過程發送消息,這一點并不奇怪,因為WindowsCreateWindowEx前調用RegisterClassEx的時候就已經得到窗口過程的地址了。并且在建立窗口的過程中需要窗口過程的配合。

            下面分別列出調用CreateWindowExShowWindow的時候窗口過程收到的消息。

             

            調用CreateWindowEx時窗口過程收到的消息

            消息發生

            說明

            WM_GETMINMAXINFO

            獲取窗口大小,以便初始化

            WM_NCCREATE

            非客戶區開始建立

            WM_NCCALCSIZE

            計算客戶區大小

            WM_CREATE

            窗口建立

             

            調用ShowWindow時窗口過程收到的消息

            消息發生

            說明

            WM_SHOWWINDOW

            顯示窗口

            WM_WINDOWPOSCHANGING

            窗口位置準備改變

            WM_ACTIVATEAPP

            窗口準備激活

            WM_NCACTIVATE

            激活狀態改變

            WM_GETTEXT

            取窗口名稱(顯示標題欄用)

            WM_ACTIVATE

            窗口準備激活

            WM_SETFOCUS

            窗口獲得焦點

            WM_NCPAINT

            需要繪畫窗口邊框

            WM_ERASEBKGND

            需要擦除背景

            WM_WINDOWPOSCHANGED

            窗口益已經改變

            WM_SIZE

            窗口大小已經改變

            WM_MOVE

            窗口位置已經移動

             

            然后程序執行UpdateWindow,這個函數向窗口過程發送一條WM_PAINT消息,接著,主程序開始進入消息循環,Windows根據各種因素給窗口過程發送相應的消息,一直到調用DestroyWindow為止。

            調用DestroyWindow時窗口過程收到的消息

            消息發生

            說明

            WM_NCACTIVATE

            窗口激活狀態改變

            WM_ACTIVATE

            窗口準備非激活

            WM_ACTIVATEAPP

            窗口準備非激活

            WM_KILLFOCUS

            失去焦點

            WM_DESTROY

            窗口即將被摧毀

            WM_NCDESTROY

            窗口的非客戶區及所有子窗口已經被摧毀

             

            在所有這些階段的消息中,大部分的消息都不需要程序自己關心,Windows只是盡義務通知窗口過程而已,窗口過程轉手就交給DefWindowProc去處理了。程序需要關心的消息有下面這些,可以根據需要選擇使用:

            WM_CREATE 放置窗口初始化代碼,如建立各種子窗口(狀態欄和工具欄等)。

            WM_SIZE             放置位置安排的代碼,因為建立的子窗口可能需要隨窗口大小的改變而移動位置。

            WM_PAINT          如果需要自己繪制客戶區,則在這里安排代碼。

            WM_CLOSE         向用戶確認是否退出,如果退出則摧毀窗口并發送WM_QUIT消息。

            WM_DESTROY    窗口摧毀,在這里放置釋放資源等掃尾代碼。

             

            在例子程序中,我們處理了WM_PAINT消息來繪制客戶區,功能就是在窗口中的中間寫上一行字:Win32 Assembly, Simple and powerful! 過程是先通過BeginPaint獲取窗口客戶區的“設備環境”句柄,然后通過GetClientRect獲取客戶區的大小,最后通過DrawText函數將字符串按照取得的屏幕大小居中寫到“設備環境”中,也就是窗口上。如果不需要顯示這個字符串,則連WM_PAINT消息也不用處理。

             

             

            消息的默認處理:DefWindowProc

            Windows預定義的消息范圍是0~03ffh,共1024個消息,查看一下頭文件Windows.inc,可以發現實際已定義的消息數目有幾百個,這些消息中的大部分對于窗口的運行來說都是必需的,如果窗口過程要處理每一種消息,那么窗口過程中的elseif語句就會綿延數千行,但是窗口的行為就是由處理這些消息的方法來表現的,不處理又不行,怎么辦呢?

             

            實際上大部分窗口的行為都是差不多的,這意味著如果要窗口過程處理全部的消息,不同窗口的窗口過程代碼應該是大同上異的,那么可以用一個模塊來以默認的方式處理消息,Win32中的DefWindowProc函數實現的就是這個功能。

             

            不要小看了這個DefWindowProc,正是它用默認的方式處理了幾百種消息,才使用戶能用區區百來行代碼寫出一個全功能的窗口。也正是所有的窗口都用DefWindowProc默認處理程序自己不處理的消息,才使它們的行為看上去大同小異,因為它們背后實際上是同一塊代碼在處理。

             

            在窗口過程的分支語句中,用戶處理所有需要個性化處理的消息,對于表現行為是默認行為的消息,則在else分支中用DefWindowProc來處理,對于Windows來說,它并不關心消息在窗口過程中是程序用自己的代碼處理的還是用DefWindowProc處理的,它只看eax中的返回值來了解處理結果,所以不管消息是誰處理的,都必須在eax中返回正確的值。DefWindowProc返回時eax中就是它對消息的處理結果,程序只要直接把eax傳回給Windows就行了,所以在例子程序中,DefWindowProc后面直接用一句ret指令返回。

             

            DefWindowProc中對一些消息的處理方法,如果和用戶期望的不同,就必須在窗口過程中自己處理。

             

            DefWindowProc對一些消息的默認處理方式

            消息

            DefWindowProc的處理方式

            WM_PAINT

            發送WM_ERASEBKGND消息來擦除背景

            WM_ERASEBKGND

            用窗口類結構中的hbrBackground刷子來繪畫窗口背景

            WM_CLOSE

            調用DestroyWindow來摧毀窗口

            WM_NCLBUTTONDBLCLK

            這是非客戶區(如標題欄)鼠標雙擊消息,DefWindowProc測試鼠標的位置,然后再采取相應的措施,如標題欄雙擊將最大化和恢復窗口

            WM_NCLBUTTONUP

            這非客戶區鼠標標題釋放消息,同樣,DefWindowProc測試鼠標的位置然后再采取相應的措施,如鼠標在“關閉”按鈕的位置釋放將導致發送WM_CLOSE消息

            WM_NCPAINT

            非客戶區繪制消息,DefWindowProc將繪制邊框和客戶區

             

            從這些默認的處理方法可以看出,想要一個窗口和別的窗口看起來不一樣,比如想要窗口看起來像蘋果機的窗口一樣,并且把關閉按鈕移到標題欄最左邊去,那么可以自己處理WM_NCPAINT消息,把非客戶區畫成蘋果機窗口的樣子,并把關閉按鈕畫到標題欄左邊去,并且自己處理WM_NCLBUTTONUP消息,當檢測到鼠標按下的位置在自己的關閉按鈕上的時候,則發送WM_CLOSE消息。對別的消息的處理思路也可以按這種方法類推。

             

            另外,可以發現DefWindowProcWM_CLOSE的默認處理是調用DestroyWindow摧毀窗口,DestroyWindow會引發一個WM_DESTROY消息,WM_CLOSEWM_DESTROY的不同之處是:WM_CLOSE代表用戶有關閉的意向,窗口過程有權不“服從”,但收到WM_DESTROY的時候窗口已經在關閉過程中了,不管窗口過程愿不愿意,窗口的關閉已經是不可挽回的事了。

             

            對于這兩個消息,窗口過程必須處理其中的一個,因為必須有個地方發送WM_QUIT消息來結束消息循環,例子程序中處理WM_CLOSE消息,在其中用DestroyWindow摧毀窗口,再調用PostQuitMessage結束消息循環;程序也可以不處理WM_CLOSE消息,讓DefWindowProc以默認處理的方式摧毀窗口,但這時候必須處理WM_DESTROY消息,在其中調用PostQuitMessage發送WM_QUIT以結束消息循環。

             

             

            posted on 2010-08-17 11:49 luqingfei 閱讀(2975) 評論(0)  編輯 收藏 引用 所屬分類: Win32匯編程語言序設計

            導航

            <2009年3月>
            22232425262728
            1234567
            891011121314
            15161718192021
            22232425262728
            2930311234

            統計

            留言簿(6)

            隨筆分類(109)

            隨筆檔案(105)

            Blogers

            Game

            Life

            NodeJs

            Python

            Useful Webs

            大牛

            搜索

            積分與排名

            最新評論

            閱讀排行榜

            評論排行榜

            波多野结衣AV无码久久一区| 丰满少妇人妻久久久久久4| 久久99精品国产99久久| 久久精品国产亚洲AV影院| 青青草国产97免久久费观看| 亚洲成人精品久久| 久久成人国产精品二三区| 99精品久久精品| 国产99久久久久久免费看| 国产真实乱对白精彩久久| 久久久久99精品成人片三人毛片| 26uuu久久五月天| 久久国产一片免费观看| 亚洲日韩欧美一区久久久久我 | 91秦先生久久久久久久| 亚洲国产成人久久综合碰碰动漫3d| 国产精品视频久久| 91精品免费久久久久久久久| 久久久精品久久久久久 | 国产精品9999久久久久| 精品久久一区二区三区| 久久亚洲电影| 人妻少妇久久中文字幕一区二区| 国产精品18久久久久久vr| 久久99国产一区二区三区| 久久人人青草97香蕉| 国产Av激情久久无码天堂| 久久精品中文字幕一区| 性色欲网站人妻丰满中文久久不卡 | 国内精品久久国产大陆| 久久九九免费高清视频 | WWW婷婷AV久久久影片| 精品视频久久久久| 久久亚洲精品无码AV红樱桃| 国产免费久久久久久无码| 伊人色综合久久天天人手人婷| 9191精品国产免费久久| 亚洲精品国产美女久久久| 精品久久久久久久中文字幕| 久久久久AV综合网成人| 色播久久人人爽人人爽人人片aV|