(譯)win32asm教程-12-完結 (轉)

worldblog發表於2007-12-12
(譯)win32asm教程-12-完結 (轉)[@more@]

 這個是本系列教程的最後一篇了。下面我可能會貼出關於用寫一個遊戲的例項。仍然是面向基礎,面向初學者的。在此,也要感謝原作者:james的大力支援,無償提供本文的中文譯權。

-譯者taowen2002

13.0 中的視窗:namespace prefix = o ns = "urn:schemas--com::office" />

在本章中,我們將建立一個有視窗的

12.1視窗

你可能已經猜到了Windows之所以稱為Windows的原因了。在Windows中,有兩種程式:GUI程式和控制檯程式。控制檯的程式看上去就像Dos程式,它們在一個似-dos的視窗中執行。你使用的大多數程式是GUI(圖形介面)程式,它們有一個用於和使用者互動的圖形介面。這是由建立視窗來完成的。幾乎你在Windows中看見的每一件東西都是視窗。首先,你建立一個父視窗,然後是像編輯框,靜態(文字標籤-譯者注),按鈕等的自視窗(控制元件)。

13.2視窗類

每一個視窗都有名字。你為你的父視窗定義你自有的類。對於控制元件,你可以使用Windows的標準類名(例如,“Edit”,“Static”,“Button”)

13.3結構

你程式中的視窗類是用“RegisterClassEx“註冊的。(RegisterClassEx是RegisterClass的擴充套件版本,後者已經不太使用了)這個函式的宣告是:

ATOM RegisterClassEx(

CONST WNDLCASSEX *lpwcx//有類資料的結構之地址

);

lpwcx:指向WNDCLASSEX結構。在把它傳遞給函式之前,你必須用適當的類屬性填寫結構。

唯一的引數是指向結構的指標。先來看看一些結構的基本知識:

一個結構是一些變數(資料)的集合。它用STRUCT定義:

SOMESTRUCTURE STRUCT
d1 dd ?
dword2 dd ?
some_word dw ?
abyte ?
anotherbyte db ?
SOMESTRUCTURE ENDS

(結構名不一定要大寫)

你可以用問號把你的變數定義在未初始化data部分。現在你可以根據定義建立一個結構:

Initialized

Initializedstructure SOMESTRUCTURE <100,200,10,'A',90h>

Uninitialized

UnInitializedstructure SOMESTRUCTURE <>

在第一個例子中,建立了一個新的結構(用初始化了的結構儲存它的offset),而且結構的每一個元素用初始化數值填寫了。第二個例子只是告訴masm為結構名分配,而且每個資料元素用0初始化。在建立了結構之後,你可以在程式碼中使用它:

mov eax, Initializedstructure.some_word
; eax
現在是 10
inc UnInitializedstructure.dword1
;
結構的dword1步增

這是結構怎樣存在記憶體中的:

記憶體地址

內容

offset of Initializedstructure

100 (dword, 4 bytes)

offset of Initializedstructure + 4

200 (dword, 4 bytes)

offset of Initializedstructure + 8

10 (word, 2 bytes)

offset of Initializedstructure + 10

65 or 'A' (1 byte)

offset of Initializedstructure + 11

90h (1 byte)

12.3 WNDCLASSEX

現在已經瞭解了足夠多的結構知識,讓我們處理RegisterClassEx吧。在《程式設計師參考》中,你可以查詢WNDCLASSEX結構的定義。

typedef struct _WNDCLASSEX { // wc
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX;

解釋

cbSize

WNDCLASSEX結構體的大小。用於Windows的。你可以用SIZEOF得到它的大小:
mov wc.cbsize, SIZEOF WNDCLASSEX

style

為類指定一個樣式(如果視窗要有捲軸,加上重畫標誌。等等)

lpfnWndProc

指向Windows過程的指標(本章後面有更多內容)

cbClsExtra

在Windows類結構後本配多少額外記憶體。對我們不重要

cbWndExtra

在Windows例項後分配多少額外記憶體。對我們也不重要

hInstance

你程式的實力控制程式碼。你可以用GetMoudleHandle函式得到這個控制程式碼

hIcon

視窗圖示資源的控制程式碼

hCursor

視窗游標資源的控制程式碼

hbrBackground

用於填充背景的畫刷控制程式碼,或是標準刷子型別中的一個,如 COLOR_WINDOW, COLOR_BTNFACE , COLOR_BACKGROUND.

lpszMenuName

指向一個指定選單類名的零結尾字串

lpszClassName

指向一個指定視窗類名的零結尾字串

hIconSm

一個和視窗類關聯的小圖示控制程式碼

在你的Win32夾中建立一個名為firstWindow的資料夾並在這個資料夾中建立一個名為window.asm的新檔案,輸入一下內容:

.486
.model flat, stdcall
option casemap:none

includelib masm32libkernel32.lib
includelib masm32libuser32.lib
includelib masm32libgdi32.lib
include masm32includewindows.inc
include masm32includekernel32.inc
include masm32includeuser32.inc
include masm32includegdi32.inc

然後建立一個名為make.bat的.bat檔案。把這些文字貼上進去:

@echo off
ml /c /coff window.asm
link /subsystem:windows window.obj
pause>nul

從現在開始,為了節省空間,僅顯示小段的程式碼。你可以透過點來顯示教程此處的全部程式碼。完整的程式碼在新視窗中顯示。

譯者注:為了方便,我又把這些放回來了。

13.4註冊類

現在我們在名為WinMain的過程中註冊類。該過程中完成視窗的初始化。

把這些加入你的彙編檔案:

WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD

.data?

hInstance dd ?

.code

invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL

end start

這些程式碼透過GetModuleHandle得到模組控制程式碼,並把模組控制程式碼放入hInstance變數中。這個控制程式碼在Windows 中頻繁使用。然後它WinMain過程。這不是一個API函式,而是一個我們將要定義的過程。原型是:WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD,因而是一個帶4個引數的函式:

現在把這些程式碼放在end start:前

WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD

ret
WinMain endp

你根本就不需要用這個winmain過程,但這是一種十分普遍的處世化你的程式的方法。Visual C自動初始化這個函式的引數,但我們必須自己來做。現在不要管hPrevInst和CmdLine。集中注意在hInst和CmdShow上。Hinst是例項控制程式碼(=模組控制程式碼),CmdShow是定義視窗該如何顯示的標誌。(你可以在API參考關於ShowWindows部分發現更多)

在前面程式碼中的"invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL"用正確的例項控制程式碼和顯示標誌呼叫這個函式。現在我們可以在WinMain中寫我們的初始化程式碼了。

WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD

ret
WinMain endp

這有我們將在過程中要用的兩個區域性變數

.data

ClassName db "FirstWindowClass",0

.code

WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
; now set all the structure members of the WNDCLASSEX structure wc:
mov  wc.cbSize,SIZEOF WNDCLASSEX
mov  wc.style, CS_HREDRAW or CS_VREDRAW
mov  wc.lpfnWndProc, OFFSET WndProc
mov  wc.cbClsExtra,NULL
mov  wc.cbWndExtra,NULL
push hInst
pop  wc.hInstance
mov  wc.hbrBackground,COLOR_WINDOW
mov  wc.lpszMenuName,NULL
mov  wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov  wc.hIcon, eax
mov  wc.hIconSm, eax
invoke LoadCursor,NULL,IDC_ARROW
mov  wc.hCursor,eax
invoke RegisterClassEx, ADDR wc

ret
WinMain endp

讓我們來看看發生了什麼:

mov  wc.cbSize,SIZEOF WNDCLASSEX
mov  wc.style, CS_HREDRAW or CS_VREDRAW
mov  wc.lpfnWndProc, OFFSET WndProc
mov  wc.cbClsExtra,NULL
mov  wc.cbWndExtra,NULL

初始化了結構的大小(這是RegisterClassEx要求的)。設定類的樣式為”CS_HREDRAW or CS_VREDRAW”,然後設定了視窗過程的offset。你在後面會知道什麼是視窗過程,現在你僅需要記住你需要WndProc過程的地址。該地址可以透過“offset WndProc”獲得。Cb.ClsExtra和cb.WndExtra我們沒有使用因而設它們為Null。

Push hInst
Pop wc.hInstance

Wc.hInstance設為WinMain的hInst引數。為什麼我們不用:mov wc.hInstance, hInst?因為mov指令不允許從一個地址移到另一個地址。透過push/pop,值被壓入棧,然後又彈入目標中。

mov  wc.hbrBackground, COLOR_WINDOW
mov  wc.lpszMenuName, NULL
mov  wc.lpszClassName, OFFSET ClassName

類的背景色被設為COLOR_WINDOW,沒有定義選單(null)而且lpszClassName設為一個指向零結尾的類名字串:“FirstWindowClass”它應該是一個在你的程式中定義的唯一名字。

invoke LoadIcon, NULL, IDI_APPLICATION
mov  wc.hIcon, eax
mov  wc.hIconSm, eax

視窗需要一個圖示。但又因為我們要一個指向圖示的控制程式碼,我們使用LoadIcon來載入圖示並獲得控制程式碼。LoadIcon有兩個引數:hInstance和lpIconName。HInstance是包含圖示的可檔案的模組控制程式碼。LpIconName是一個指向圖示資源和圖示ID的字串的指標。如果你用NULL為hInstance,你可以從一些標準圖表中選這一個(這卻是是因為我們在這裡還沒有圖示資源)hIconSm是小圖示,你可以對它使用相同的控制程式碼。

invoke LoadCursor,NULL,IDC_ARROW
mov  wc.hCursor,eax

對游標也一樣。NULL作hInstance,並用一個標準游標型別:IDC_ARROW,標準Windows箭頭型游標。

invoke RegisterClassEx, ADDR wc

現在,最終用RegisterClassEx來註冊類,透過一個指向WNDCLASSEX結構的指標作引數。

13.5建立視窗

現在,你已經註冊了一個類,你可以使用它建立一個視窗:

HWND CreateWindowEx(

DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window ntifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);

DwExstyle和dwStyle是兩個決定視窗樣式的引數。

LpClassName 是一個指向你註冊了的類名的指標。

LpWindowName 是你視窗的名字(如果有的話,這將成為你視窗的標題)

X, Y, nWidth, nHeight 決定你視窗的位置和大小

HMenu 是選單視窗的控制程式碼(在後面討論,現在為空)

HInstance 是程式例項的控制程式碼

LpPara是你能在你的程式中使用的擴充套件值

.data

AppName "FirstWindow",0

.code

INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,
CW_USEDEFAULT,400,300,NULL,NULL,
hInst,NULL
mov hwnd, eax
invoke ShowWindow, hwnd, SW_SHOWNORMAL
invoke UpdateWindow, hwnd

(注意使彙編器讀下一行的時候好像還在同一行)

我們的程式碼將用我們剛剛註冊的類名建立一個新的視窗。標題是“FirstWindow”(程式名,AppName),樣式是WS_OVERLAPPEDWINDOW,這是一個建立有標題,選單,可縮放邊框和最大化/最小化按鈕的視窗樣式。CW_USERDEFAULT作為x和y的位置會使Windows為新視窗使用預設位置。視窗的(初始)大小是400×300象素。

函式的返回值是視窗控制程式碼,HWND。它儲存在區域性變數hwnd中。然後視窗用ShowWindow顯示。UpdateWindow確保視窗被畫出。

13.6訊息迴圈

視窗可以透過訊息和你的程式以及其他視窗通訊。無論何時,一條訊息被髮送給指定的視窗。它的視窗過程都要被呼叫。每個視窗都有一個訊息迴圈或訊息泵(pump)。這是一個無止盡的檢查是否給有你的視窗的訊息的迴圈。而且如果有,把訊息傳遞給dispatchMessage函式。這個函式會呼叫你的視窗過程。訊息迴圈和視窗過程是兩個完全不同的東西!!!

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
LOCAL msg:MSG ;<<

........

.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW

這是訊息迴圈看上去的樣子。.WHILE TRUE, .ENDW迴圈到eax為0之前都會繼續。如果它接到了WM_QUIT訊息,GetMessage返回0,這將關閉視窗因而程式應該在不論GetMessage返回0時退出。如果不是這樣(0),訊息被傳遞給TranslateMessage(這個函式把按鍵翻譯為訊息)而且訊息被Windows用DispatchMessage函式解包。訊息本身在一個訊息迴圈的組成部分MSG結構中(LOCAL msg: MSG被加入過程,增加了一個稱為msg的區域性訊息結構)你可以在你的所有程式中用這個訊息迴圈。

13.7視窗過程

訊息會被髮送往視窗過程。一個視窗過程看上去總是這樣:

WndProc PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD

.code

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==XXXX
.ELSEIF eax==XXXX
.ELSE
 invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp

視窗過程總是有4個引數

hWnd 包含視窗控制程式碼

uMsg 訊息

wParam 訊息的第一個引數(由訊息定義)

lParam 訊息的第二個引數(由訊息定義)

視窗不處理的訊息應該傳遞給DefWindowProc,它會處理這些。一個視窗過程的例子:

WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==WM_CREATE
 invoke MessageBox, NULL, ADDR AppName, ADDR AppName, NULL
.ELSEIF eax==WM_DESTROY
 invoke PostQuitMessage, NULL
.ELSE
 invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp

這段程式碼在視窗初始化時顯示程式名稱。也要注意我加入了WM_DESTROY訊息的處理。這條訊息在視窗將要關閉的時候傳送。程式要用PostQuitMessage作出反應。

現在看看最終的程式碼:

.486


.model flat, stdcall


option casemap:none


 


includelib masm32libkernel32.lib


includelib masm32libuser32.lib


includelib masm32libgdi32.lib


 


include masm32includewindows.inc


include masm32includekernel32.inc


include masm32includeuser32.inc


include masm32includegdi32.inc


WinMain PROTO STDCALL  :DWORD, :DWORD, :DWORD, :DWORD


WndProc PROTO STDCALL  :DWORD, :DWORD, :DWORD, :DWORD


.data?


hInstance  dd  ?


 


.data


ClassName  db  "FirstWindowClass",0


AppName  db  "FirstWindow",0


 


.code


start:


 


  invoke  GetModuleHandle, NULL


  mov  hInstance, eax


  invoke  WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL


  invoke  ExitProcess, NULL


 


WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD


  LOCAL wc:WNDCLASSEX


  LOCAL hwnd:DWORD


   LOCAL msg:MSG


  mov  wc.cbSize,SIZEOF WNDCLASSEX


  mov  wc.style,  CS_HREDRAW or CS_VREDRAW


  mov  wc.lpfnWndProc, OFFSET WndProc


  mov  wc.cbClsExtra,NULL


  mov  wc.cbWndExtra,NULL


  push  hInst


  pop  wc.hInstance


  mov  wc.hbrBackground,COLOR_WINDOW


  mov  wc.lpszMenuName,NULL


  mov  wc.lpszClassName,OFFSET ClassName


  invoke  LoadIcon,NULL,IDI_APPLICATION


  mov  wc.hIcon,  eax


  mov  wc.hIconSm, eax


  invoke  LoadCursor,NULL,IDC_ARROW


   mov  wc.hCursor,eax


  invoke  RegisterClassEx,  addr wc


 


 INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,


  WS_OVERLAPPEDWINDOW-WS_SIZEBOX-WS_MAXIMIZEBOX,CW_USEDEFAULT,


  CW_USEDEFAULT,400,300,NULL,NULL,


   hInst,NULL


  mov  hwnd,eax


  invoke ShowWindow, hwnd,SW_SHOWNORMAL


  invoke UpdateWindow, hwnd


  .WHILE TRUE


  invoke GetMessage, ADDR msg,NULL,0,0


  .BREAK .IF (!eax)


  invoke TranslateMessage, ADDR msg


   invoke DispatchMessage, ADDR msg


  .ENDW


  mov  eax,msg.wParam


  ret


WinMain endp


 


WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD


mov eax, uMsg


.IF eax==WM_CREATE


  invoke  MessageBox, NULL, ADDR AppName, ADDR AppName, NULL


.ELSEIF eax==WM_DESTROY


  invoke PostQuitMessage, NULL


.ELSE


  invoke DefWindowProc, hWnd, uMsg, wParam, lParam


.ENDIF


ret


WndProc endp


end start


 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991692/,如需轉載,請註明出處,否則將追究法律責任。

相關文章