標(biāo) 題: 【原創(chuàng)】Nasm-Win32匯編學(xué)習(xí)4-繪制文本
作 者: littlefire
時 間: 2009-11-11,20:35:59
鏈 接: http://bbs.pediy.com/showthread.php?t=101077
繪制文本
今天我們將學(xué)習(xí)如何在窗口的客戶區(qū)“繪制”字符串。我們還將學(xué)習(xí)關(guān)于“設(shè)備環(huán)境”的概念。
Windows中的文本是一個GUI(圖形用戶界面)對象。每一個字符實際上是由許多的像素點組成,這些點在有筆畫的地方顯示出來,這樣就會出現(xiàn)字符。這也是為什么我說“繪制”字符,而不是寫字符。通常你都是在你應(yīng)用程序的客戶區(qū)“繪制”字符串(盡管你也可以在客戶區(qū)外“繪制”)。Windows下的“繪制”字符串方法和Dos下的截然不同,在Dos下你可以把屏幕想象成85x25的一個平面,而Windows下由于屏幕上同時有幾個應(yīng)用程序的畫面,所以你必須嚴(yán)格遵從規(guī)范。Windows通過把每一個應(yīng)用程序限制在他的客戶區(qū)來做到這一點。當(dāng)然客戶區(qū)的大小是可變的,你隨時可以調(diào)整。
提示:客戶區(qū)是指我們窗體與用戶交互的部分。打個比方,比如我們Windows的notepad記事本程序的客戶區(qū)就是它的編輯框,非客戶區(qū)則是指它的標(biāo)題欄和菜單欄及滾動條。
在你在客戶區(qū)“繪制”字符串前,你必須從Windows那里得到你客戶區(qū)的大小,確實你無法像在Dos下那樣隨心所欲地在屏幕上任何地方“繪制”,繪制前你必須得到Windows的允許,然后Windows會告訴你客戶區(qū)的大小,字體,顏色和其它GUI對象的屬性。你可以用這些來在客戶區(qū)“繪制”。
什么是“設(shè)備環(huán)境”(DC)呢?它其實是由Windows內(nèi)部維護(hù)的一個數(shù)據(jù)結(jié)構(gòu)。一個“設(shè)備環(huán)境”和一個特定的設(shè)備相連。像打印機(jī)和顯示器。對于顯示器來說,“設(shè)備環(huán)境”和一個個特定的窗口相連。
“設(shè)備環(huán)境”中的有些屬性和繪圖有關(guān),像:顏色,字體等。你可以隨時改動那些缺省值,之所以保存缺省值是為了方便。你可以把“設(shè)備環(huán)境”想象成是Windows為你準(zhǔn)備的一個繪圖環(huán)境,而你可以隨時根據(jù)需要改變某些缺省屬性。
當(dāng)應(yīng)用程序需要繪制時,你必須得到一個“設(shè)備環(huán)境”的句柄。通常有幾種方法:
在WM_PAINT消息中使用call BeginPaint。
在其他消息中使用call GetDC。
call CreateDC建立自己的DC。
你必須牢記的是,在處理單個消息后你必須釋放“設(shè)備環(huán)境”句柄。不要在一個消息處理中獲得“設(shè)備環(huán)境”句柄,而在另一個消息處理中再釋放它。
我們在Windows發(fā)送WM_PAINT消息時處理繪制客戶區(qū),Windows不會保存客戶區(qū)的內(nèi)容,它用的方法是“重繪”機(jī)制(譬如當(dāng)客戶區(qū)剛被另一個應(yīng)用程序的客戶區(qū)覆蓋),Windows會把WM_PAINT消息放入該應(yīng)用程序的消息隊列。重繪窗口的客戶區(qū)是各個窗口自己的責(zé)任,你要做的是在窗口過程處理WM_PAINT的部分知道繪制什么和如何繪制。
你必須了解的另一個概念是“無效區(qū)域”。Windows把一個最小的需要重繪的正方形區(qū)域叫做“無效區(qū)域”。當(dāng)Windows發(fā)現(xiàn)了一個“無效區(qū)域”后,它就會向該應(yīng)用程序發(fā)送一個WM_PAINT消息,在WM_PAINT的處理過程中,窗口首先得到一個有關(guān)繪圖的結(jié)構(gòu)體,里面包括無效區(qū)的坐標(biāo)位置等。你可以通過調(diào)用BeginPaint讓“無效區(qū)”有效,如果你不處理WM_PAINT消息,至少要調(diào)用缺省的窗口處理函數(shù)DefWindowProc,或者調(diào)用ValidateRect讓“無效區(qū)”有效。否則你的應(yīng)用程序?qū)盏綗o窮無盡的WM_PAINT消息。
下面是響應(yīng)該消息的步驟:
1、取得“設(shè)備環(huán)境”句柄
2、繪制客戶區(qū)
3、釋放“設(shè)備環(huán)境”句柄
假如,我們程序的客戶區(qū)假設(shè)被另一個應(yīng)用程序的客戶區(qū)所覆蓋,那些此刻另一個應(yīng)用程序退出,相應(yīng)我們程序被覆蓋客戶區(qū)需要發(fā)生重繪,而被覆蓋的這部分叫做“無效區(qū)域”,這時候Windows就會檢測到我們需要發(fā)生重繪的無效區(qū)域,然后發(fā)送給我們程序的消息隊列中一個WM_PAINT消息。此刻我們就需要在我們的窗口過程中處理WM_PAINT消息,即使不處理也要調(diào)用缺省的DefWindowProc函數(shù)。BeginPaint函數(shù)默認(rèn)使無效區(qū)有效。那么BeginPaint返回了一個設(shè)備環(huán)境的句柄。因為我們Windows下的硬件設(shè)備都是以相關(guān)的驅(qū)動形式來和Windows通信的,那么我們的設(shè)備環(huán)境也就關(guān)聯(lián)了相應(yīng)的設(shè)備。例如我們的窗體對象關(guān)聯(lián)了相應(yīng)的顯示器設(shè)備,那么此時我們要想在窗體上顯示文本,我們必須通過設(shè)備驅(qū)動,因為Windows提供給了我們相應(yīng)的函數(shù)以及相應(yīng)的數(shù)據(jù)類型,我們只需要取得設(shè)備環(huán)境的句柄,然后通過相應(yīng)的函數(shù)來調(diào)用,此時我們不需要知道它是如何實現(xiàn)的,我們只需要遵守Windows的規(guī)則就可以了。下面我們通過一段代碼來深入分析一下。
%include '..\inc\nasmx.inc'
%include '..\inc\win32\windows.inc'
%include '..\inc\win32\kernel32.inc'
%include '..\inc\win32\user32.inc'
entry start
[section .bss]
hInstance: resd 1
hWnd: resd 1
lpCommandLine: resd 1
[section .data]
szTitle: db "My First Window", 0x0
szClass: db "FirstWindow", 0x0
szText: db "My First Window Text", 0x0
wc:
istruc WNDCLASSEX ;聲明結(jié)構(gòu)體
at WNDCLASSEX.cbSize, dd NULL
at WNDCLASSEX.style, dd NULL
at WNDCLASSEX.lpfnWndProc, dd NULL
at WNDCLASSEX.cbClsExtra, dd NULL
at WNDCLASSEX.cbWndExtra, dd NULL
at WNDCLASSEX.hInstance, dd NULL
at WNDCLASSEX.hIcon, dd NULL
at WNDCLASSEX.hCursor, dd NULL
at WNDCLASSEX.hbrBackground, dd NULL
at WNDCLASSEX.lpszMenuName, dd NULL
at WNDCLASSEX.lpszClassName, dd NULL
at WNDCLASSEX.hIconSm, dd NULL
iend
message:
istruc MSG
at MSG.hwnd, dd NULL
at MSG.message, dd NULL
at MSG.wParam, dd NULL
at MSG.lParam, dd NULL
at MSG.time, dd NULL
at MSG.pt, dd NULL
iend
ps:
istruc PAINTSTRUCT
iend
rect:
istruc RECT
at RECT.left, dd NULL
at RECT.top, dd NULL
at RECT.right, dd NULL
at RECT.bottom, dd NULL
iend
[section .code]
proc start
invoke GetModuleHandleA, dword NULL
mov [hInstance], eax
invoke GetCommandLineA
mov [lpCommandLine], eax
invoke WinMain, dword [hInstance], dword NULL, dword lpCommandLine, dword SW_SHOWNORMAL
invoke ExitProcess, dword NULL
ret
endproc
proc WinMain
hinst argd ; Current instance handle
hpinst argd ; Previous instance handle
cmdln argd ; Command line arguments
dwshow argd ; Display style
mov [wc + WNDCLASSEX.cbSize], dword WNDCLASSEX_size
mov [wc + WNDCLASSEX.style], dword CS_HREDRAW | CS_VREDRAW
invoke LoadIconA, dword NULL, dword IDI_APPLICATION
mov edx, eax
mov eax, dword argv(hinst)
mov ebx, dword szClass
mov ecx, dword WndProc
mov [wc + WNDCLASSEX.hInstance], eax
mov [wc + WNDCLASSEX.hbrBackground], dword COLOR_WINDOW + 1
mov [wc + WNDCLASSEX.lpszClassName], ebx
mov [wc + WNDCLASSEX.lpfnWndProc], ecx
mov [wc + WNDCLASSEX.hIcon], edx
mov [wc + WNDCLASSEX.hIconSm], edx
invoke RegisterClassExA, dword wc
invoke CreateWindowExA, dword NULL, dword szClass, dword szTitle, dword WS_OVERLAPPEDWINDOW + WS_VISIBLE, dword CW_USEDEFAULT, dword CW_USEDEFAULT, dword CW_USEDEFAULT, dword CW_USEDEFAULT, dword NULL, dword NULL, dword [wc + WNDCLASSEX.hInstance], dword NULL
mov [hWnd], eax
invoke ShowWindow, dword hWnd, dword argv(dwshow)
invoke UpdateWindow, dword hWnd
.WHILE:
invoke GetMessageA, dword message, dword NULL, dword NULL, dword NULL
cmp eax, dword 0
je .ENDW
invoke TranslateMessage, dword message
invoke DispatchMessageA, dword message
jmp .WHILE
.ENDW:
mov eax, dword [message + MSG.wParam]
ret
endproc
proc WndProc
hwnd argd ; Window handle
umsg argd ; Window message
wparam argd ; wParam
lparam argd ; lParam
locals ;locals/local/endlocals,聲明局部變量,使用var()宏獲得變量地址
local hdc, Dword
endlocals
if argv(umsg), ==, dword WM_DESTROY
invoke PostQuitMessage, dword NULL
elsif argv(umsg), ==, dword WM_PAINT
invoke BeginPaint, dword argv(hwnd), dword ps ;重繪指定的窗口
mov dword var(hdc), eax
invoke GetClientRect, dword argv(hwnd), dword rect ;獲取客戶區(qū)的坐標(biāo),放到RECT結(jié)構(gòu)體中
invoke DrawTextA, dword var(hdc), dword szText, dword -1, dword rect, dword DT_SINGLELINE | DT_CENTER | DT_VCENTER ;在指定窗口寫入格式化文本。
invoke EndPaint, dword argv(hwnd), dword ps ;指定窗口的繪制過程結(jié)束,這個函數(shù)在每次調(diào)用BeginPaint函數(shù)之后被請求。
else
invoke DefWindowProcA, dword argv(hwnd), dword argv(umsg), dword argv(wparam), dword argv(lparam)
endif
ret
endproc
今天的代碼跟我們前面寫的窗口顯示代碼,只有幾點不同的地方。
一、數(shù)據(jù)段中增加了兩個結(jié)構(gòu)體的聲明:PAINTSTRUCT和RECT
ps:
istruc PAINTSTRUCT
iend
rect:
istruc RECT
at RECT.left, dd NULL
at RECT.top, dd NULL
at RECT.right, dd NULL
at RECT.bottom, dd NULL
iend
二、窗口過程中聲明了局部變量hdc:
locals ;locals/local/endlocals,聲明局部變量,使用var()宏獲得變量地址
local hdc, Dword
endlocals
上面這兩點中聲明的變量,都是由處理WM_PAINT消息的GDI函數(shù)調(diào)用。locals/local/endlocals是nasmx提供給我們在函數(shù)中聲明局部變量的宏,locals是局部變量區(qū)的開頭,endlocals是局部變量區(qū)的結(jié)尾,中間用local聲明,“l(fā)ocal parm, 數(shù)據(jù)類型(byte,word,dword等)”。使用var(parm)宏來調(diào)用。
hdc用來存放調(diào)用BeginPaint返回的“設(shè)備環(huán)境”句柄。ps是一個PAINTSTRUCT數(shù)據(jù)類型的變量。它由Windows傳遞給BeginPaint,在結(jié)束繪制后再原封不動的傳遞給EndPaint。它的函數(shù)原型如下:
typedef struct tagPAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT;
PAINTSTRUCT 結(jié)構(gòu)體包含了用于繪制窗口客戶區(qū)的信息。
hdc是用于繪制的句柄,fErase如果為非零值則擦除背景,否則不擦除背景,rcPaint 通過制定左上角和右下角的坐標(biāo)確定一個要繪制的矩形范圍,該矩形單位相對于客戶區(qū)左上角,后面三個參數(shù)都是系統(tǒng)預(yù)留的,編程一般用不到。
rect是一個RECT結(jié)構(gòu)體類型的參數(shù),它的定義如下:
STRUC RECT
.left RESD 1
.top RESD 1
.right RESD 1
.bottom RESD 1
ENDSTRUC
left和top是客戶區(qū)正方形左上角的坐標(biāo)。right和buttom是正方形右下角的坐標(biāo)。
三、在窗口函數(shù)的消息判斷中增加了對WM_PAINT消息的處理
elsif argv(umsg), ==, dword WM_PAINT
invoke BeginPaint, dword argv(hwnd), dword ps ;重繪指定的窗口
mov dword var(hdc), eax
invoke GetClientRect, dword argv(hwnd), dword rect ;獲取客戶區(qū)的坐標(biāo),放到RECT結(jié)構(gòu)體中
invoke DrawTextA, dword var(hdc), dword szText, dword -1, dword rect, dword DT_SINGLELINE | DT_CENTER | DT_VCENTER ;在指定窗口寫入格式化文本。
invoke EndPaint, dword argv(hwnd), dword ps ;指定窗口的繪制過程結(jié)束,這個函數(shù)在每次調(diào)用BeginPaint函數(shù)之后被請求。
大家注意到了嗎?對hdc的引入是使用var(hdc)宏實現(xiàn)的。
在上面已經(jīng)講過,Windows只要檢測到我們有需要重繪的無效區(qū)域,就會發(fā)送WM_PAINT消息,那么我們必須處理WM_PAINT或通過缺省的DefWindowProc函數(shù)來處理。因為BeginPaint函數(shù)接受一個窗口句柄和未初始化的PAINTSTRUCT型參數(shù)后,會自動的將我們的無效區(qū)域設(shè)置為有效,所以此時我們一般調(diào)用BeginPaint來設(shè)置我們的無效區(qū)域有效,而且BeginPaint函數(shù)返回我們相應(yīng)的窗體對象的設(shè)備環(huán)境的句柄,我們此時獲得相應(yīng)設(shè)備環(huán)境句柄后,再調(diào)用GetClientRect獲得我們應(yīng)用程序窗體客戶區(qū)的大小。大小放在rect結(jié)構(gòu)體中。然后把它傳給DrawTextA。DrawText的原型如下:
int DrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
hdc:“設(shè)備環(huán)境”的句柄。
lpString:要顯示的文本串,該文本串要么以NULL結(jié)尾,要么在nCount中指出它的長度。
nCount:要輸出的文本的長度。若以NULL結(jié)尾,該參數(shù)必須是-1。
lpRect:指向要輸出文本串的正方形區(qū)域的指針,該正方形必須是一個裁剪區(qū),也就是說超過該區(qū)域的字符將不能顯示。
uFormat:指定如何顯示。我們可以用“|”把以下標(biāo)志“或”到一塊。
DT_SINGLELINE:是否單行顯示。
DT_CENTER:是否水平居中。
DT_VCENTER:是否垂直劇中。
四、在.data段中聲明了窗口顯示變量:
szText: db "My First Window Text", 0x0
結(jié)束繪制后,必須調(diào)用EndPaint釋放“設(shè)備環(huán)境”的句柄。好了,現(xiàn)在我們把“繪制”文本串的要點總結(jié)如下:
1、必須在開始和結(jié)束處分別調(diào)用BeginPaint和EndPaint;
2、在BeginPaint和EndPaint之間調(diào)用所有的繪制函數(shù);
3、如果在其它的消息處理中重新繪制客戶區(qū),你可以有兩種選擇;
(1)用GetDC和ReleaseDC代替BeginPaint和EndPaint;
(2)調(diào)用InvalidateRect或UpdateWindow讓客戶區(qū)無效,這將迫使Windows把WM_PAINT放入應(yīng)用程序消息隊列,從而使得客戶區(qū)重繪。
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請
點擊舉報。