青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

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 閱讀(3040) 評論(0)  編輯 收藏 引用 所屬分類: Win32匯編程語言序設計

導航

<2010年8月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
2930311234

統計

留言簿(6)

隨筆分類(109)

隨筆檔案(105)

Blogers

Game

Life

NodeJs

Python

Useful Webs

大牛

搜索

積分與排名

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            亚洲美女精品一区| 久久久蜜桃精品| 亚洲深夜激情| 亚洲精品久久久久久久久久久久久| 国产婷婷精品| 国产亚洲人成a一在线v站| 国产精品免费网站在线观看| 国产女人精品视频| 亚洲丰满在线| 亚洲欧美一级二级三级| 久久精品视频免费观看| 欧美成人免费在线| 国产精品theporn88| 激情视频一区二区| 在线一区免费观看| 久久综合激情| 亚洲欧美日韩中文播放| 美女国产一区| 国内精品久久久久影院色| 亚洲老司机av| 欧美二区乱c少妇| 一区二区三区欧美视频| 中日韩高清电影网| 久热精品视频在线免费观看| 亚洲第一精品久久忘忧草社区| 久久精品最新地址| 国产精品视频一二三| 国产精品女人毛片| 国产日韩在线播放| 一区二区三区欧美激情| 久久成年人视频| 亚洲精品一区二区三区蜜桃久| 亚洲国产成人久久| 美女啪啪无遮挡免费久久网站| 狠狠爱成人网| 欧美~级网站不卡| 久久香蕉国产线看观看av| 国产乱码精品一区二区三区不卡| 欧美在线一区二区| 欧美激情一区二区三区高清视频| 午夜视频在线观看一区二区三区| 国产精品综合久久久| 激情成人av| 狠色狠色综合久久| 久久精品国亚洲| 欧美日韩1区2区3区| 欧美大胆成人| 久久久精品日韩欧美| 模特精品在线| 欧美日韩国产成人在线观看| 国产亚洲欧美在线| 尤物99国产成人精品视频| 亚洲国产综合91精品麻豆| 亚洲影院在线| 亚洲视频电影在线| 欧美大片va欧美在线播放| 日韩视频久久| 久久久999国产| 欧美成人精品三级在线观看| 国产精品久久久久av| 极品裸体白嫩激情啪啪国产精品| 国产精品久久久久久久7电影| 欧美日韩一区在线观看| 国产亚洲精品v| 亚洲天堂av电影| 欧美日韩你懂的| 在线亚洲激情| 一区二区三区成人| 欧美国产欧美亚洲国产日韩mv天天看完整| 鲁大师影院一区二区三区| 制服丝袜亚洲播放| 亚洲视频 欧洲视频| 亚洲图片在区色| 久久av资源网| 亚洲精选视频免费看| 亚洲午夜性刺激影院| 欧美色欧美亚洲高清在线视频| 亚洲第一在线综合在线| 亚洲人精品午夜| 久久综合给合久久狠狠色| 亚洲国产精品成人va在线观看| 久久久久久一区二区| 亚洲精品欧美| 国产亚洲欧美日韩精品| 亚洲欧美网站| 一区二区三区精品国产| 欧美日本精品| 久久久99国产精品免费| 欧美日韩一区二区视频在线| 另类综合日韩欧美亚洲| 国产日韩欧美中文| 亚洲日本中文字幕| 欧美先锋影音| 女女同性精品视频| 国产亚洲精品久久久久婷婷瑜伽| 欧美伊人久久大香线蕉综合69| 久久久久久久波多野高潮日日 | 久久在线播放| 这里只有精品视频| 精品福利av| 国产欧美精品日韩区二区麻豆天美| 久久日韩粉嫩一区二区三区| 亚洲小说区图片区| 99re在线精品| 亚洲精品国产精品乱码不99按摩 | 欧美激情视频网站| 亚洲一区二区伦理| 亚洲精品视频中文字幕| 黑人巨大精品欧美一区二区小视频| 欧美日韩国产bt| 久久久久久久久久久久久久一区| 欧美一区二区三区免费观看| 六月丁香综合| 久久成人在线| 国产欧美日韩视频一区二区三区| 噜噜噜91成人网| 韩国v欧美v日本v亚洲v| 久久综合色婷婷| 欧美亚洲成人网| 亚洲毛片一区二区| 国产精品99久久久久久久女警| 亚洲精品视频中文字幕| 欧美77777| 99精品视频免费在线观看| 亚洲国产小视频| 久久综合色8888| 久久久久青草大香线综合精品| 欧美不卡一卡二卡免费版| 麻豆免费精品视频| 影音先锋久久久| 久久久久国产精品人| 在线亚洲欧美专区二区| 开心色5月久久精品| 亚洲精品一区在线观看| 亚洲日韩欧美视频| 久久久久国产精品一区| 亚洲福利视频网站| 久久深夜福利免费观看| 一区二区三区视频观看| 国产亚洲一级高清| 亚洲国产国产亚洲一二三| 99国产精品视频免费观看一公开| 亚洲性av在线| 欧美激情中文不卡| 亚洲一区在线观看视频| 久久精品在线观看| 国产精品乱人伦中文| 亚洲电影天堂av| 久久夜色精品| 先锋影音久久| 国产欧美一区二区精品忘忧草| 91久久精品国产91久久性色tv| 午夜视频在线观看一区二区| 99精品国产在热久久婷婷| 免费不卡亚洲欧美| 亚洲黄色av一区| 亚洲第一在线综合网站| 久久一综合视频| 亚洲高清视频的网址| 你懂的国产精品永久在线| 亚洲另类自拍| 91久久国产综合久久91精品网站| 亚洲一区在线直播| 国产日韩一区二区三区在线| 午夜精品亚洲| 欧美一区二区三区免费观看| 国产偷自视频区视频一区二区| 久久精品一区二区三区中文字幕| 亚洲综合精品自拍| 在线色欧美三级视频| 亚洲精品国精品久久99热| 欧美午夜三级| 伊人精品成人久久综合软件| 亚洲少妇自拍| 亚洲欧美在线看| 91久久精品美女| 亚洲综合成人在线| 一区二区在线观看视频| 亚洲乱码国产乱码精品精98午夜| 国产精品久久久久久久久果冻传媒 | 欧美日本高清| 老色鬼精品视频在线观看播放 | 亚洲高清久久网| 欧美日韩亚洲一区二区三区| 久热国产精品| 国产无遮挡一区二区三区毛片日本| 欧美国产综合视频| 国内精品久久久久久影视8 | 午夜视频在线观看一区| 欧美激情视频网站| 老司机aⅴ在线精品导航| 国产精品久久久久久久第一福利| 91久久久国产精品| 亚洲品质自拍| 欧美成人久久| 一区二区欧美精品| 国产色视频一区| 亚洲美洲欧洲综合国产一区| 亚洲日本欧美日韩高观看| 国产中文一区二区|