Win32匯編教程之一
Win32匯編的環(huán)境和基礎(chǔ)
1.32位環(huán)境簡介
在Dos下編匯編程序,我們可以管理系統(tǒng)的所有資源,我們可以改動系統(tǒng)中所有的內(nèi)存,如自己改動內(nèi)存控制塊來分配內(nèi)存,自己修改中斷向量表來截獲中斷等,對其他操作也是如此,如我們對鍵盤端口直接操作就可以把鍵盤屏蔽掉,可以這樣來描述Dos系統(tǒng):系統(tǒng)只有一個特權(quán)級別,在編程上講,任何程序和操作系統(tǒng)都是同級的,所以在Dos下,一個編得不好的程序會影響其他所有的程序,如一個程序把鍵盤口中斷關(guān)掉了,所有程序就都不能從鍵盤獲得鍵入的數(shù)據(jù),直到任何一個程序重新打開鍵盤為止,一個程序陷入死循環(huán),也沒有其他程序可以把它終止掉。Dos下的編程思路是“單任務(wù)”的,你只要認(rèn)為你的程序會按照你的流程一步步的執(zhí)行下去,不必考慮先后問題(當(dāng)然程序可能會被中斷打斷,但你可以認(rèn)為它們會把環(huán)境恢復(fù),如果中斷程序沒有把環(huán)境恢復(fù),那是他們的錯)。
在內(nèi)存管理方式上,Dos匯編和Win32匯編也有很多的不同:Dos工作在實(shí)模式下,我們可以尋址1M的內(nèi)存,尋址時通過段寄存器來制定段的初始地址,每個段的大小為64K,超過1M的部分,就只能把他作為XMS使用,也就是說,只能用作數(shù)據(jù)存放使用而無法在其中執(zhí)行程序。
而Windows在保護(hù)模式下執(zhí)行,這里所有的資源對應(yīng)用程序來說都是被“保護(hù)”的:程序在執(zhí)行中有級別之分,只有操作系統(tǒng)工作在最高級--0級中,所有應(yīng)用程序都工作在3級中(Ring3), 在Ring3中,你無法直接訪問IO端口,無法訪問其他程序運(yùn)行的內(nèi)存,連向程序自己的代碼段寫入數(shù)據(jù)都是非法的,會在Windows的屏幕上冒出一個熟悉的藍(lán)屏幕來。只有對Ring0的程序來說,系統(tǒng)才是全開放的。
在內(nèi)存方面,Windows使用了處理器的分頁機(jī)制,使得對應(yīng)用程序來說,所有的內(nèi)存都是“平坦”的,你不必用一個段寄存器去指定段的地址,因?yàn)樵诒Wo(hù)模式下,段寄存器的含義是不同的(可以參見80386手冊方面的書籍),你可以直接指定一個32位的地址來尋址4GB的內(nèi)存。
在程序結(jié)構(gòu)方面,Windows程序也有很大的不同,它是“基于消息”的,你可以想象這樣一個常見的Windows窗口,上面有幾個按鈕,如果你用Dos編程的思路去考慮,你會發(fā)現(xiàn)實(shí)現(xiàn)它很困難:鼠標(biāo)移動到窗口邊緣時拖動會改變窗口大小,鼠標(biāo)點(diǎn)擊按鈕時再做要做的事,你會發(fā)現(xiàn),你的程序自開始執(zhí)行后就在等待,你不知道鼠標(biāo)先會點(diǎn)什么地方,實(shí)際上你是在等待所有可能的事情的發(fā)生。而在Dos下,你可以只顧自己先執(zhí)行,需要用戶輸入時,再停下來,你不輸入我就不再執(zhí)行,而且,我讓你輸入數(shù)據(jù)A你就不能輸入數(shù)據(jù)B。
好了,言歸正傳,因?yàn)橐陨鲜荳in32編程的基礎(chǔ),無論對Win32匯編還是VC++,它們都是一樣的,下面我們來看看有關(guān)Win32匯編的內(nèi)容。
2.Win32A***編譯器
Win32A***的編譯器最常用的有兩種:Borland公司的Tasm5.0和Microsoft的Masm6.11以上版本,兩種編譯器各有自己的優(yōu)缺點(diǎn),Tasm帶了一個不大不小的Import庫,而Masm沒有帶,但Masm在代碼的優(yōu)化上面好象比Tasm做得好,但它卻不帶Import庫。看來使用哪一種編譯器還是比較難選擇的,但Steve Hutchesson給了我們一個答案,他為Masm建立了一個很全的Import庫,基本上包括了Windows絕大部分的Api函數(shù),這些庫、include文件和其他工具還有Masm6.14版本一起做成了一個 Masm32編譯器 -- Masm32V5。這樣一來,我們用匯編編程就象用C一樣方便。
因?yàn)橛辛薓asm32V5,所以就我個人而言,我推薦使用Masm作為Win32A***的編譯工具,但Masm和Tasm的宏語法有很多的不同,我的這個教程是以Masm格式寫的。
3.Masm32的環(huán)境設(shè)置
在Win32編程中,由于Windows有很多的數(shù)據(jù)結(jié)構(gòu)和定義,這些都放在include文件中,還有連接時要用到Import庫(通俗的講就是Windows提供的DLL文件中的函數(shù)列表,也就是告訴程序到哪里去調(diào)用API函數(shù)),這些都放在include 和lib目錄中。我們在編譯時要指定以下的系統(tǒng)環(huán)境:
set include=\Masm32v5\Include
set lib=\Masmv5\lib
set path=\Masmv5\Bin
這樣編譯器就會到正確的路徑中去找 include 文件和 lib 文件。你可以自己在 autoexec.bat 文件中加上以上語句,為了產(chǎn)生Windows的PE格式的執(zhí)行文件,在編譯和連接中要指定相應(yīng)的參數(shù):
編譯: Ml /c /coff 文件名.asm
連接: Link /SUBSYSTEM:WINDOWS OBJ文件名.obj 資源文件名.res
為了不在每次編譯時都要打這么多的參數(shù),我們可以用 nmake 文件來代為執(zhí)行,nmake 是代碼維護(hù)程序,他會檢查 .asm .obj .exe .res 等文件的時間,如果你更新了源程序,他會自動執(zhí)行編譯程序或連接程序產(chǎn)生相應(yīng)的文件。你可以在文件名為 makefile 的文件中指定使用的編譯器和連接程序以及相應(yīng)的參數(shù),下面是一個 makefile 文件的例子:
NAME = Clock
OBJS = $(NAME).obj
RES = $(NAME).res
$(NAME).exe: $(OBJS) $(RES)
Link /DEBUG /SUBSYSTEM:WINDOWS $(OBJS) $(RES)
$(RES): $(NAME).rc
Rc $(NAME).rc
.asm.obj:
Ml /c /coff $(NAME).asm
文件告訴 nmake程序,程序名為 clock,產(chǎn)生 clock.exe 文件需要 clock.obj和 clock.res 文件,而產(chǎn)生 clock.res 文件需要 clock.rc 文件,產(chǎn)生 clock.obj 文件要用到 clock.asm 文件,至于是否需要執(zhí)行 ml, link 和 rc,程序會根據(jù)文件的時間自動判斷。
Win32匯教程二
Win32匯編程序的結(jié)構(gòu)和語法
Win32A***程序的結(jié)構(gòu)和語法
讓我們先來看看一個最簡單的Win32匯編程序:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
szCaption db 'Win32匯編例子',0
szText db 'Win32匯編,Simple and powerful!',0
.code
start:
invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK
invoke ExitProcess,NULL
end start
這就是一個能執(zhí)行的最簡單的Win32匯編程序,下面我簡單地介紹一下各部分的作用:
.386
這條語句和Dos下匯編是一樣的,是告訴編譯器我們要用到80386的指令集,因?yàn)?2位匯編程序要用到32位的寄存器如eax,ebx等,所以這一句是必須的,當(dāng)然,你也可以用.486,.586等,當(dāng)用到特權(quán)指令時,還可以用 .386p,.486p等等。
.model flat,stdcall
.model告訴編譯器程序的模式,編過Dos匯編的人可能知道在Dos程序的模式有tiny,small,...huge 等,它指定了程序內(nèi)存尋址模式,在huge等模式下,內(nèi)存尋址和子程序調(diào)用將用Far的格式,但在Win32匯編中,你只能使用一個模式即 flat 模式,因?yàn)閷in32程序來說,內(nèi)存是連續(xù)的一個4GB的段,無所謂小或大的模式。而stdcall 告訴編譯器參數(shù)的傳遞方式,在調(diào)用子程序時,參數(shù)是通過堆棧傳遞的,參數(shù)的傳遞方式有三種,stdcall,c 和 pascal,stdcall 指定了參數(shù)是從右到左壓入堆棧的,比如說對一個Windows API 如 MessageBox,在手冊中是如此定義的:
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
那么在匯編中我們就可以這樣調(diào)用它:
push uType
push lpCaption
push lpText
push hWnd
call MessageBox
大家要注意最右面的參數(shù)是最后一個進(jìn)堆棧的,當(dāng)然,我們不必這樣麻煩的調(diào)用一個 API,因?yàn)镸asm中的一個宏語句不但幫助我們完成了所有的壓棧操作,還幫我們檢查參數(shù)的個數(shù)是否正確,那就是 invoke 語句,我們可以把上面的語句換成 invoke MessageBox,hWnd,lpText,lpCaption,uType 就行了。如本程序中代入實(shí)際參數(shù)就成了 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK。
include 語句
include 語句包含了一些系統(tǒng)的定義和API函說明,其中所有的Windows 數(shù)據(jù)結(jié)構(gòu)定義和常量定義包含在 windows.inc 中,而其他 API函數(shù)的說明包含在 xxx.inc 中, 如查 Microsoft Win32 Programmer's Reference 知道 ExitProcess包含在kernel32.dll 中,那么我們就要在程序中包括 include kernel32.inc 和 includelib kernel32.lib語句,否則在編譯時會出現(xiàn) API 函數(shù)未定義的錯誤。而 MessageBox 在 user32.dll 中,那么我們就要在程序中包括 include user32.inc 和 includelib user32.lib語句
.data 或 .data?
指明了接下來是數(shù)據(jù)段,.data 定義了預(yù)定義的變量,.data?定義了未初始化的變量,兩者的不同之處是 .data? 定義的變量并不占用 .exe 文件的大小,而是在程序執(zhí)行時動態(tài)分配,所以開始是不指定初始值的數(shù)據(jù)可以放在 .data? 段中,如一個1K大小的緩沖區(qū),放在 .data?中,程序?qū)⒉粫黾右粋€字節(jié)。
.code
指明了接下來是代碼段,我們的所有代碼都放在這里。最后的一句 start 語句指定了程序開始執(zhí)行的語句。程序中的 ExitProcess 是一個標(biāo)準(zhǔn)的 Win32 API,對應(yīng) Dos匯編中的 int 20h 或 mov ah,4ch/int 21h,也就是程序退出。而 MessageBox 也是一個標(biāo)準(zhǔn)的 API,功能是在屏幕上顯示一個消息框,具體的參數(shù)上面已經(jīng)解釋過了還有要注意的是 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK 語句中, MB_OK 和 NULL 已經(jīng)預(yù)定義在 Windows.inc 中。
Win32匯編教程三
一個簡單的對話框 --- 兼談資源文件的使用
Windows 的資源文件
不管在Dos下編程還是在Windows下編程,我們總是要用到除了可執(zhí)行文件外的很多其他數(shù)據(jù),如聲音數(shù)據(jù),圖形數(shù)據(jù),文本等等,在Dos下編程,我們可以自己定義這些文件的格式,但這樣一來就造成了很多資源共享的問題,大家可能還記的Dos下的很多游戲,它們的圖形都是按自己的格式存放的,你無法用標(biāo)準(zhǔn)的看圖軟件來看。也無法把它另存為其他格式。雖然在Win32編程中,我們?nèi)匀豢梢赃@樣做,但Win32編程給了我們一個方案 ---- 就是格式統(tǒng)一的資源文件,把字符串、圖形、對話框包括上面的按鈕,文本等定義到一個資源文件中,就可以方便的在不同的文件中使用它,最重要的是,如果我們用自己的文件格式,使用時就要涉及到這些文件的讀寫操作,比較復(fù)雜,但使用資源文件時,Windows提供了一系列的API來裝入資源。非常方便。現(xiàn)在,讓我們來看一個很簡單的資源文件的源文件,它的擴(kuò)展名是 .rc,當(dāng)它用資源編譯器編譯以后產(chǎn)生 .res 文件就可以在 link的時候連入.exe 文件中:
#i nclude <Resource.h>
#define DLG_MAIN 1
DLG_MAIN DIALOGEX 0, 0, 236, 185
STYLE DS_MODALFRame | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SY***ENU
CAPTION "對話框模板"
FONT 9, "宋體"
BEGIN
DEFPUSHBUTTON "退出",IDOK,177,163,50,14
CONTROL "",-1,"Static",SS_ETCHEDHORZ,7,155,222,1
END
現(xiàn)在我簡單解釋一下 .rc文件的語法:
#i nclude <resource.h> -- resource.h文件包括資源文件的一些常量定義,如下面的 WS_POPUP,WS_VISIBLE 等窗口的風(fēng)格等等
#define DLG_MAIN 1 -- 類似于 .asm 文件的 equ 語句,和匯編源程序一樣,這些定義是為了程序的可讀性。
DLG_MAIN DIALOGEX 0,0,236,185
Windows的.rc文件可以定義 BITMAP(位圖),CURSOR(光標(biāo)),ICON(圖標(biāo)),ACCELERATORS(加速鍵),DIALOG(對話框),MENU(菜單),STRINGTABLE(字符串表),RCDATA(自定義資源)等8種資源,詳細(xì)的描述可以參考有關(guān)MFC的書籍,在Win32A***中的資源編譯器的語法中,一般格式是這些資源的定義方法是:
位圖定義: nameID BITMAP [load-mem] filename
光標(biāo)定義: nameID CURSOR [load-mem] filename
圖標(biāo)定義: nameID ICON [load-mem] filename
加速鍵定義:
acctablename ACCELERATORS [optional-statements]
BEGIN event, idvalue, [type] [options]
. . .
END
等等,具體的定義和參數(shù)可以參考 Masm32v5 中的 Rc.hlp 幫助文件。我們可以用資源編輯器來所見即所得地編輯資源,也可以在文本編輯器中用上面這些語句自己定義資源。
在程序中使用資源
在程序中,要使用資源之前必須先裝如內(nèi)存,Windows定義了一系列的API來裝入資源,如 LoadMenu,LoadString,LoadBitmap 等等,如 LoadBitmap 的定義:
HBITMAP LoadBitmap(
HINSTANCE hInstance, // handle of application instance
LPCTSTR lpBitmapName // address of bitmap resource name
);
這些Load函數(shù)的返回值是一個句柄,調(diào)用參數(shù)中一般至少為兩項(xiàng): hInstance 和 ResouceName,這個 ResouceName(如BitmapName,MenuName)就是在資源文件中的 #define 指定的值,如果你用 #define MY_ICON 10/ MY_ICON ICON "Main.ico" 定義了一個圖標(biāo),那么在程序中要使用 Main.ico 圖標(biāo)就可以用 LoadIcon(hInstance,10) 來裝入已經(jīng)定義為10號的圖標(biāo)文件。另一個參數(shù) hInstance 是執(zhí)行文件的句柄,它對應(yīng)資源所在的文件名,你可以在程序開始執(zhí)行時用 invoke GetModuleHandle,NULL 獲得 hInstance。另外一些資源并不是顯式地裝入的,如對話框資源,它是在建立對話框的函數(shù)中由Windows自己裝入的,如下面例子中的 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 ,是在屏幕上顯示一個資源文件中已經(jīng)定義好了的對話框,就并不存在 LoadDialogBox 之類的API來先裝入對話框。
Win32A*** - 顯示一個對話框
介紹了這么多相關(guān)的東西,現(xiàn)在讓我們來看看如何顯示一個對話框,源程序如下:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
DLG_MAIN equ 1
.data?
hInstance dd ?
szBuffer db 256 dup (?)
_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
.code
;********************************************************************
_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,wMsg
.if eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
.elseif eax == WM_INITDIALOG
.elseif eax == WM_COMMAND
mov eax,wParam
.if eax == IDOK
invoke EndDialog,hWnd,NULL
.elseif eax == IDCANCEL
invoke EndDialog,hWnd,NULL
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;********************************************************************
start:
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0
invoke ExitProcess,NULL
end start
看了前面幾篇文章以后,這兒的大部分語句應(yīng)該是很熟悉了,我來講解幾句新的語句:
_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
PROTO 語句類似于C語言中的函數(shù)定義,在Win32匯編中,如果子程序的定義在引用以后,你就必須先定義,當(dāng)然,這個定義是針對 invoke 語句和其他帶參數(shù)的調(diào)用的,如果你的子程序沒有參數(shù),你就可以用 call 指令去調(diào)用它而不是用宏指令 invoke,這時候你就不必聲明這個函數(shù)。
_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
這個定義 proc 的語句應(yīng)該是不陌生的,要重復(fù)講解一下的是 uses 和 下面的參數(shù),uses 下的寄存器表示要編譯器自動插入保存及恢復(fù)這些寄存器的指令,\ 是在 Masm32 中接下一行的符號,表示下一行是本行的繼續(xù)內(nèi)容,以避免一行中的內(nèi)容過長。下面的 hWnd:DWORD 等語句定義了調(diào)用這個子程序的參數(shù),如果有以下定義 MyProc proc dwPara1:DWORD,dwPara2:DWORD,dwPara3:DWORD,然后你用 invoke MyProc 1,2,3 來調(diào)用它,那么,1,2,3 將分別被賦值給 dwPara1,dwPara2,dwPara3,你可以在子程序中使用這些傳遞過來的參數(shù)。如果參數(shù)的類型是雙字,那么:DWORD 可以省略。
.if/.else/.elseif/.endif
這些語句是宏指令,實(shí)際上不說你也知道它們的意思,有了這些宏指令,我們就可以把匯編編得象C一樣結(jié)構(gòu)清晰,而不必老是看到 jmp 指令了,當(dāng)然,這只不過編譯器幫你做了這些事情而已,如果你去反匯編一下,你開始會看到一大堆 jmp 指令,.if 的格式如下
.if eax == 1 如果eax等于1
.if eax != 1 如果eax不等于1
.if eax != 1 && ebx != 2 如果eax不等于1且ebx不等于2
.if eax == 1 || ebx == 2 如果eax等于1或者ebx等于2
其他的宏指令還有 .while/.endw .break 等等,可以參考 Masm32V5 的幫助文件 Masm32.hlp
最后要講到的就是 DialogBoxParam 這個API了,在Windows中,所有的窗口都要指定一個子程序,當(dāng)Windows檢測到鼠標(biāo)、定時器等和這個窗口有關(guān)的動作時,它回調(diào)用這個子程序,這就是Windows基于消息的體系的最基本的概念,換句話說,在Dos下,我們通過INT指令調(diào)用系統(tǒng),而在Windows 下,有很多時候是你指定子程序地址讓W(xué)indows來調(diào)用你。 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0中的 offset _ProcDlgMain 就指定了如果有消息發(fā)生,Windows就來執(zhí)行這個子程序,參數(shù)中的 DLG_MAIN 就是在資源文件中定義的對話框模板編號。 hInstance 是對話框所在的資源文件的句柄。
另外,在_ProcDlgMain 子程序中,Windows傳給我們4個參數(shù)hWnd,wMsg,wParam,lParam,其中,hWnd是對話框的窗口句柄,wMsg表示現(xiàn)在發(fā)生的消息事件,如這個對話框初始化時Windows會以WM_INITDIALOG為消息調(diào)用,關(guān)閉時為WM_CLOSE,按下對話框上的按鈕時為WM_COMMAND等,wParam和lParam是附加的參數(shù),對應(yīng)不同的消息對應(yīng)不同定義,具體可以參考Win32 Programmer's reference。
Win32匯編教程四
編寫一個簡單的窗口
有關(guān)窗口的基本知識
窗口是屏幕上的矩形區(qū)域。一個窗口可以從鍵盤或者鼠標(biāo)接受用戶的輸入,并在其內(nèi)部顯示圖形輸出。一個應(yīng)用程序窗口通常包含程序的標(biāo)題條、菜單、邊框,滾動條。其中,對話框也是一種窗口。不同的是,對話框表面通常包含幾個其它窗口,稱之為“子窗口”。這些子窗口的形式有壓入按鈕、單選按鈕、復(fù)選框、文本輸入?yún)^(qū)域、列表框和滾動條等。 用戶將這些窗口看成屏幕上的對象,可以通過按下一個按鈕或者滾動一個滾動條與這些對象直接交互。
窗口以“消息”的形式接收窗口的輸入,窗口也用消息與其它窗口通訊。比如在程序窗口的大小改變時,字處理器會重新格式化其中的文本。窗口大小改變的細(xì)節(jié)是由操作系統(tǒng)處理的,但程序能夠響應(yīng)這個系統(tǒng)功能。當(dāng)用戶改變窗口的大小時,Windows給程序發(fā)送一條消息指出新窗口的大小。然后,程序就可以調(diào)整窗口中的內(nèi)容,以響應(yīng)大小的變化。程序創(chuàng)建的每一個窗口都有相關(guān)的窗口過程。也就是給這個窗口指定一個子程序(窗口過程),Windows通過調(diào)用它來給窗口發(fā)送消息。窗口過程再根據(jù)此消息進(jìn)行處理,然后將控制返回給Windows。
窗口在“窗口類”的基礎(chǔ)上創(chuàng)建的。Windows定義了確省的窗口過程,如果你對所有的消息都讓W(xué)indows自己處理,那么你就能得到一個標(biāo)準(zhǔn)的窗口,同樣,你也可以選擇處理自己感興趣的消息,這樣,相當(dāng)于產(chǎn)生了不同的子類,也就形成了不同的應(yīng)用程序。同樣,子窗口也是基于同一個窗口類,并且使用同一個窗口過程。例如,所有Windows 程序中的所有按鈕都基于同一窗口類。這個窗口類有一個處理所有按鈕消息的窗口過程,但是,如果你按自己的設(shè)想設(shè)計(jì)一個按鈕,如想把按鈕的表面換成位圖,你就可以自己處理按鈕窗口的 WM_PAINT 消息,當(dāng) Windows 需要畫按鈕表面的時候,你就可以隨自己的意思去畫。
Windows程序開始執(zhí)行后,Windows為該程序創(chuàng)建一個“消息隊(duì)列”。這個消息隊(duì)列用來存放該程序可能創(chuàng)建的各種不同窗口的消息。程序中有一段代碼,叫做“消息循環(huán)”, 它用來從隊(duì)列中取出消息,并且將它們發(fā)送給相應(yīng)的窗口過程。在沒有消息發(fā)生的時候,你的程序?qū)嶋H上就在消息循環(huán)中轉(zhuǎn)圈子。
創(chuàng)建一個窗口的過程如下:
取得程序的實(shí)例句柄(hInstance)
注冊窗口類,實(shí)際上就是為你的窗口指定處理消息的過程,定義光標(biāo),窗口風(fēng)格,顏色等參數(shù)
創(chuàng)建窗口
顯示窗口
然后進(jìn)入消息循環(huán),也就是不停地檢測有無消息,并把它發(fā)送給窗口進(jìn)程去處理。
創(chuàng)建一個窗口的代碼在不同的程序中實(shí)際上是幾乎一模一樣的,所以你編一個新的程序時可以把這一段拷來拷去,稍微修改一下就行,程序的大部分代碼實(shí)際上是用在窗口過程中,因?yàn)檫@才是不同程序的不同之處。窗口過程的編程要點(diǎn)如下:
從Windows傳給窗口過程的參數(shù) uMsg 得到消息類型,并轉(zhuǎn)到不同的分枝去處理。
對自己已經(jīng)處理的消息,返回 Windows 時必須在eax 中返回0。
自己不處理的消息,必須調(diào)用 DefWindowProc 處理,并把返回值傳回Windows,否則,Windows會無法顯示。
uMsg 參數(shù)中指定的消息有280多種,實(shí)際上我們需要處理的只有重要的幾種,如Windows在創(chuàng)建的時候會發(fā)送 WM_CREATE 消息,我們就可以在這時候初始化,分配內(nèi)存等等,而退出時會發(fā)送 WM_CLOSE,我們就可以進(jìn)行釋放內(nèi)存等清除工作,當(dāng)Windows上的菜單或按鈕被按下時發(fā)送 WM_COMMAND 消息等等,具體可以參考 Win32 Programmer's Reference。下面,我們來看一個創(chuàng)建窗口的簡單程序。
一個創(chuàng)建窗口的程序
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
include gdi32.inc
includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
includelib gdi32.lib
IDI_MAIN equ 1000 ;icon
IDM_MAIN equ 4000 ;menu
IDM_EXIT equ 4001
.data?
hInstance dd ?
hWinMain dd ?
hMenu dd ?
szBuffer db 256 dup (?)
.data
szClassName db "Windows Template",0
szCaptionMain db '窗口模板',0
.code
start:
call _WinMain
invoke ExitProcess,NULL
_WinMain proc
local @stWcMain:WNDCLASSEX
local @stMsg:MSG
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke LoadIcon,hInstance,IDI_MAIN
mov hIcon,eax
invoke LoadMenu,hInstance,IDM_MAIN
mov hMenu,eax
;*************** 注冊窗口類 *****************************************
invoke LoadCursor,0,IDC_ARROW
mov @stWcMain.hCursor,eax
mov @stWcMain.cbSize,sizeof WNDCLASSEX
mov @stWcMain.hIconSm,0
mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW
mov @stWcMain.lpfnWndProc,offset WndMainProc
mov @stWcMain.cbClsExtra,0
mov @stWcMain.cbWndExtra,0
mov eax,hInstance
mov @stWcMain.hInstance,eax
mov @stWcMain.hIcon,0
mov @stWcMain.hbrBackground,COLOR_WINDOW + 1
mov @stWcMain.lpszClassName,offset szClassName
mov @stWcMain.lpszMenuName,0
invoke RegisterClassEx,addr @stWcMain
;*************** 建立輸出窗口 ***************************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\
0,0,550,300,\
NULL,hMenu,hInstance,NULL
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;*************** 消息循環(huán) *******************************************
.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
WndMainProc proc uses ebx edi esi, \
hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,uMsg
.if eax == WM_CREATE
mov eax,hWnd
mov hWinMain,eax
call _Init
;********************************************************************
.elseif eax == WM_COMMAND
.if lParam == 0
mov eax,wParam
.if ax == IDM_EXIT
call _Quit
.endif
.endif
;********************************************************************
.elseif eax == WM_CLOSE
call _Quit
;********************************************************************
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndMainProc endp
_Init proc
invoke SendMessage,hWinMain,WM_SETICON,ICON_***ALL,hIcon
ret
_Init endp
;********************************************************************
_Quit proc
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
ret
_Quit endp
;********************************************************************
end start
窗口程序的分析
讓我們來簡單分析一下這個程序,首先程序調(diào)用 _WinMain,在_WinMain 中定義了兩個局部變量 @stMsg 和 @stWinMain,數(shù)據(jù)類型分別是 MSG 和 WNDCLASSEX結(jié)構(gòu),在參考手冊中,可以看到WNDCLASSEX定義了一個窗口的所有參數(shù),如使用的菜單、光標(biāo)、顏色、窗口過程等,接下來的一大堆 mov 指令實(shí)際上就是在填寫這個數(shù)據(jù)結(jié)構(gòu),填寫完成后,最重要的兩句是 mov @stWcMain.lpfnWndProc,offset WndMainProc 定義了處理消息的窗口過程, mov @stWcMain.lpszClassName,offset szClassName 定義了你要創(chuàng)建的類的名稱,然后就是使用 RegisterClassEx 注冊這個窗口類,注意,這時候窗口并沒有創(chuàng)建,你只不過是定義好了一個子類,接下去你要用你定義的類去創(chuàng)建一個窗口。也就是使用 CreateWindowEx 函數(shù)去創(chuàng)建它。在手冊中,CreateWindowEx 是這樣定義的:
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 identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data );
其中的參數(shù) dwExStyle 是窗口的風(fēng)格,lpClassName 就是我們自己定義的類的名字。如果大家要創(chuàng)建一個已經(jīng)定義好的類,如 RichEdit 類等等,只要把 lpClassName 指向 "RichEdit32" 字符串就行了,當(dāng)然這時就不用 RegisterClass 以及編寫自己的窗口過程了。執(zhí)行 CreateWindowEx 后,得到一個返回值就是窗口句柄,這個值在以后是要經(jīng)常用到了,所以要先保存下來。這時窗口并沒有在屏幕上顯示出來,而是處于隱藏狀態(tài),我們要用 ShowWindow 來顯示出窗口并用UpdateWindow 來繪窗口的內(nèi)容。
窗口顯示出來后,程序就進(jìn)入一個循環(huán)----消息循環(huán),前面我已經(jīng)說過,作用是不停地接收 Windows 消息并發(fā)送給窗口過程去處理。GetMessage 從消息隊(duì)列中取出一條消息,如果取得的消息不是 WM_QUIT,那么 GetMessage 返回一個非零值,否則返回零,這時候循環(huán)結(jié)束,程序執(zhí)行 ExitProcess退回操作系統(tǒng)。TranslateMessage 將消息進(jìn)行一些鍵盤轉(zhuǎn)換,用于處理一些快捷鍵,DispatchMessage 將處理后的消息發(fā)回 Windows,由Windows調(diào)用窗口進(jìn)程進(jìn)行處理,當(dāng)窗口進(jìn)程處理完返回后,程序才從 DispatchMessage 返回,從而開始下一個 GetMessage 調(diào)用。這些函的參數(shù)可以參考手冊。
窗口過程的分析
窗口過程有四個參數(shù),hWnd 是本窗口的句柄,和創(chuàng)建窗口時返回的值相同,uMsg 是本次調(diào)用的消息類型,wParam 和lParam是消息的參數(shù),其含義和數(shù)值根據(jù)消息的不同而不同。在本程序中,我們處理 WM_CREATE,WM_COMMAND 和 WM_QUIT 消息,然后返回0,對不處理的消息,使用 invoke DefWindowProc,hWnd,uMsg,wParam,lParam 來處理并直接用 ret 將返回值傳回 Windows。在響應(yīng) WM_CLOSE 消息時,我們用 DestroyWindow 清除窗口并用PostQuitMessage 產(chǎn)生一條 WM_QUIT 消息,從而使程序在 消息循環(huán)調(diào)用GetMessage 時返回0,以結(jié)束消息循環(huán)并結(jié)束程序。
Win32匯編的環(huán)境和基礎(chǔ)
1.32位環(huán)境簡介
在Dos下編匯編程序,我們可以管理系統(tǒng)的所有資源,我們可以改動系統(tǒng)中所有的內(nèi)存,如自己改動內(nèi)存控制塊來分配內(nèi)存,自己修改中斷向量表來截獲中斷等,對其他操作也是如此,如我們對鍵盤端口直接操作就可以把鍵盤屏蔽掉,可以這樣來描述Dos系統(tǒng):系統(tǒng)只有一個特權(quán)級別,在編程上講,任何程序和操作系統(tǒng)都是同級的,所以在Dos下,一個編得不好的程序會影響其他所有的程序,如一個程序把鍵盤口中斷關(guān)掉了,所有程序就都不能從鍵盤獲得鍵入的數(shù)據(jù),直到任何一個程序重新打開鍵盤為止,一個程序陷入死循環(huán),也沒有其他程序可以把它終止掉。Dos下的編程思路是“單任務(wù)”的,你只要認(rèn)為你的程序會按照你的流程一步步的執(zhí)行下去,不必考慮先后問題(當(dāng)然程序可能會被中斷打斷,但你可以認(rèn)為它們會把環(huán)境恢復(fù),如果中斷程序沒有把環(huán)境恢復(fù),那是他們的錯)。
在內(nèi)存管理方式上,Dos匯編和Win32匯編也有很多的不同:Dos工作在實(shí)模式下,我們可以尋址1M的內(nèi)存,尋址時通過段寄存器來制定段的初始地址,每個段的大小為64K,超過1M的部分,就只能把他作為XMS使用,也就是說,只能用作數(shù)據(jù)存放使用而無法在其中執(zhí)行程序。
而Windows在保護(hù)模式下執(zhí)行,這里所有的資源對應(yīng)用程序來說都是被“保護(hù)”的:程序在執(zhí)行中有級別之分,只有操作系統(tǒng)工作在最高級--0級中,所有應(yīng)用程序都工作在3級中(Ring3), 在Ring3中,你無法直接訪問IO端口,無法訪問其他程序運(yùn)行的內(nèi)存,連向程序自己的代碼段寫入數(shù)據(jù)都是非法的,會在Windows的屏幕上冒出一個熟悉的藍(lán)屏幕來。只有對Ring0的程序來說,系統(tǒng)才是全開放的。
在內(nèi)存方面,Windows使用了處理器的分頁機(jī)制,使得對應(yīng)用程序來說,所有的內(nèi)存都是“平坦”的,你不必用一個段寄存器去指定段的地址,因?yàn)樵诒Wo(hù)模式下,段寄存器的含義是不同的(可以參見80386手冊方面的書籍),你可以直接指定一個32位的地址來尋址4GB的內(nèi)存。
在程序結(jié)構(gòu)方面,Windows程序也有很大的不同,它是“基于消息”的,你可以想象這樣一個常見的Windows窗口,上面有幾個按鈕,如果你用Dos編程的思路去考慮,你會發(fā)現(xiàn)實(shí)現(xiàn)它很困難:鼠標(biāo)移動到窗口邊緣時拖動會改變窗口大小,鼠標(biāo)點(diǎn)擊按鈕時再做要做的事,你會發(fā)現(xiàn),你的程序自開始執(zhí)行后就在等待,你不知道鼠標(biāo)先會點(diǎn)什么地方,實(shí)際上你是在等待所有可能的事情的發(fā)生。而在Dos下,你可以只顧自己先執(zhí)行,需要用戶輸入時,再停下來,你不輸入我就不再執(zhí)行,而且,我讓你輸入數(shù)據(jù)A你就不能輸入數(shù)據(jù)B。
好了,言歸正傳,因?yàn)橐陨鲜荳in32編程的基礎(chǔ),無論對Win32匯編還是VC++,它們都是一樣的,下面我們來看看有關(guān)Win32匯編的內(nèi)容。
2.Win32A***編譯器
Win32A***的編譯器最常用的有兩種:Borland公司的Tasm5.0和Microsoft的Masm6.11以上版本,兩種編譯器各有自己的優(yōu)缺點(diǎn),Tasm帶了一個不大不小的Import庫,而Masm沒有帶,但Masm在代碼的優(yōu)化上面好象比Tasm做得好,但它卻不帶Import庫。看來使用哪一種編譯器還是比較難選擇的,但Steve Hutchesson給了我們一個答案,他為Masm建立了一個很全的Import庫,基本上包括了Windows絕大部分的Api函數(shù),這些庫、include文件和其他工具還有Masm6.14版本一起做成了一個 Masm32編譯器 -- Masm32V5。這樣一來,我們用匯編編程就象用C一樣方便。
因?yàn)橛辛薓asm32V5,所以就我個人而言,我推薦使用Masm作為Win32A***的編譯工具,但Masm和Tasm的宏語法有很多的不同,我的這個教程是以Masm格式寫的。
3.Masm32的環(huán)境設(shè)置
在Win32編程中,由于Windows有很多的數(shù)據(jù)結(jié)構(gòu)和定義,這些都放在include文件中,還有連接時要用到Import庫(通俗的講就是Windows提供的DLL文件中的函數(shù)列表,也就是告訴程序到哪里去調(diào)用API函數(shù)),這些都放在include 和lib目錄中。我們在編譯時要指定以下的系統(tǒng)環(huán)境:
set include=\Masm32v5\Include
set lib=\Masmv5\lib
set path=\Masmv5\Bin
這樣編譯器就會到正確的路徑中去找 include 文件和 lib 文件。你可以自己在 autoexec.bat 文件中加上以上語句,為了產(chǎn)生Windows的PE格式的執(zhí)行文件,在編譯和連接中要指定相應(yīng)的參數(shù):
編譯: Ml /c /coff 文件名.asm
連接: Link /SUBSYSTEM:WINDOWS OBJ文件名.obj 資源文件名.res
為了不在每次編譯時都要打這么多的參數(shù),我們可以用 nmake 文件來代為執(zhí)行,nmake 是代碼維護(hù)程序,他會檢查 .asm .obj .exe .res 等文件的時間,如果你更新了源程序,他會自動執(zhí)行編譯程序或連接程序產(chǎn)生相應(yīng)的文件。你可以在文件名為 makefile 的文件中指定使用的編譯器和連接程序以及相應(yīng)的參數(shù),下面是一個 makefile 文件的例子:
NAME = Clock
OBJS = $(NAME).obj
RES = $(NAME).res
$(NAME).exe: $(OBJS) $(RES)
Link /DEBUG /SUBSYSTEM:WINDOWS $(OBJS) $(RES)
$(RES): $(NAME).rc
Rc $(NAME).rc
.asm.obj:
Ml /c /coff $(NAME).asm
文件告訴 nmake程序,程序名為 clock,產(chǎn)生 clock.exe 文件需要 clock.obj和 clock.res 文件,而產(chǎn)生 clock.res 文件需要 clock.rc 文件,產(chǎn)生 clock.obj 文件要用到 clock.asm 文件,至于是否需要執(zhí)行 ml, link 和 rc,程序會根據(jù)文件的時間自動判斷。
Win32匯教程二
Win32匯編程序的結(jié)構(gòu)和語法
Win32A***程序的結(jié)構(gòu)和語法
讓我們先來看看一個最簡單的Win32匯編程序:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
szCaption db 'Win32匯編例子',0
szText db 'Win32匯編,Simple and powerful!',0
.code
start:
invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK
invoke ExitProcess,NULL
end start
這就是一個能執(zhí)行的最簡單的Win32匯編程序,下面我簡單地介紹一下各部分的作用:
.386
這條語句和Dos下匯編是一樣的,是告訴編譯器我們要用到80386的指令集,因?yàn)?2位匯編程序要用到32位的寄存器如eax,ebx等,所以這一句是必須的,當(dāng)然,你也可以用.486,.586等,當(dāng)用到特權(quán)指令時,還可以用 .386p,.486p等等。
.model flat,stdcall
.model告訴編譯器程序的模式,編過Dos匯編的人可能知道在Dos程序的模式有tiny,small,...huge 等,它指定了程序內(nèi)存尋址模式,在huge等模式下,內(nèi)存尋址和子程序調(diào)用將用Far的格式,但在Win32匯編中,你只能使用一個模式即 flat 模式,因?yàn)閷in32程序來說,內(nèi)存是連續(xù)的一個4GB的段,無所謂小或大的模式。而stdcall 告訴編譯器參數(shù)的傳遞方式,在調(diào)用子程序時,參數(shù)是通過堆棧傳遞的,參數(shù)的傳遞方式有三種,stdcall,c 和 pascal,stdcall 指定了參數(shù)是從右到左壓入堆棧的,比如說對一個Windows API 如 MessageBox,在手冊中是如此定義的:
int MessageBox(
HWND hWnd, // handle of owner window
LPCTSTR lpText, // address of text in message box
LPCTSTR lpCaption, // address of title of message box
UINT uType // style of message box
);
那么在匯編中我們就可以這樣調(diào)用它:
push uType
push lpCaption
push lpText
push hWnd
call MessageBox
大家要注意最右面的參數(shù)是最后一個進(jìn)堆棧的,當(dāng)然,我們不必這樣麻煩的調(diào)用一個 API,因?yàn)镸asm中的一個宏語句不但幫助我們完成了所有的壓棧操作,還幫我們檢查參數(shù)的個數(shù)是否正確,那就是 invoke 語句,我們可以把上面的語句換成 invoke MessageBox,hWnd,lpText,lpCaption,uType 就行了。如本程序中代入實(shí)際參數(shù)就成了 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK。
include 語句
include 語句包含了一些系統(tǒng)的定義和API函說明,其中所有的Windows 數(shù)據(jù)結(jié)構(gòu)定義和常量定義包含在 windows.inc 中,而其他 API函數(shù)的說明包含在 xxx.inc 中, 如查 Microsoft Win32 Programmer's Reference 知道 ExitProcess包含在kernel32.dll 中,那么我們就要在程序中包括 include kernel32.inc 和 includelib kernel32.lib語句,否則在編譯時會出現(xiàn) API 函數(shù)未定義的錯誤。而 MessageBox 在 user32.dll 中,那么我們就要在程序中包括 include user32.inc 和 includelib user32.lib語句
.data 或 .data?
指明了接下來是數(shù)據(jù)段,.data 定義了預(yù)定義的變量,.data?定義了未初始化的變量,兩者的不同之處是 .data? 定義的變量并不占用 .exe 文件的大小,而是在程序執(zhí)行時動態(tài)分配,所以開始是不指定初始值的數(shù)據(jù)可以放在 .data? 段中,如一個1K大小的緩沖區(qū),放在 .data?中,程序?qū)⒉粫黾右粋€字節(jié)。
.code
指明了接下來是代碼段,我們的所有代碼都放在這里。最后的一句 start 語句指定了程序開始執(zhí)行的語句。程序中的 ExitProcess 是一個標(biāo)準(zhǔn)的 Win32 API,對應(yīng) Dos匯編中的 int 20h 或 mov ah,4ch/int 21h,也就是程序退出。而 MessageBox 也是一個標(biāo)準(zhǔn)的 API,功能是在屏幕上顯示一個消息框,具體的參數(shù)上面已經(jīng)解釋過了還有要注意的是 invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK 語句中, MB_OK 和 NULL 已經(jīng)預(yù)定義在 Windows.inc 中。
Win32匯編教程三
一個簡單的對話框 --- 兼談資源文件的使用
Windows 的資源文件
不管在Dos下編程還是在Windows下編程,我們總是要用到除了可執(zhí)行文件外的很多其他數(shù)據(jù),如聲音數(shù)據(jù),圖形數(shù)據(jù),文本等等,在Dos下編程,我們可以自己定義這些文件的格式,但這樣一來就造成了很多資源共享的問題,大家可能還記的Dos下的很多游戲,它們的圖形都是按自己的格式存放的,你無法用標(biāo)準(zhǔn)的看圖軟件來看。也無法把它另存為其他格式。雖然在Win32編程中,我們?nèi)匀豢梢赃@樣做,但Win32編程給了我們一個方案 ---- 就是格式統(tǒng)一的資源文件,把字符串、圖形、對話框包括上面的按鈕,文本等定義到一個資源文件中,就可以方便的在不同的文件中使用它,最重要的是,如果我們用自己的文件格式,使用時就要涉及到這些文件的讀寫操作,比較復(fù)雜,但使用資源文件時,Windows提供了一系列的API來裝入資源。非常方便。現(xiàn)在,讓我們來看一個很簡單的資源文件的源文件,它的擴(kuò)展名是 .rc,當(dāng)它用資源編譯器編譯以后產(chǎn)生 .res 文件就可以在 link的時候連入.exe 文件中:
#i nclude <Resource.h>
#define DLG_MAIN 1
DLG_MAIN DIALOGEX 0, 0, 236, 185
STYLE DS_MODALFRame | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SY***ENU
CAPTION "對話框模板"
FONT 9, "宋體"
BEGIN
DEFPUSHBUTTON "退出",IDOK,177,163,50,14
CONTROL "",-1,"Static",SS_ETCHEDHORZ,7,155,222,1
END
現(xiàn)在我簡單解釋一下 .rc文件的語法:
#i nclude <resource.h> -- resource.h文件包括資源文件的一些常量定義,如下面的 WS_POPUP,WS_VISIBLE 等窗口的風(fēng)格等等
#define DLG_MAIN 1 -- 類似于 .asm 文件的 equ 語句,和匯編源程序一樣,這些定義是為了程序的可讀性。
DLG_MAIN DIALOGEX 0,0,236,185
Windows的.rc文件可以定義 BITMAP(位圖),CURSOR(光標(biāo)),ICON(圖標(biāo)),ACCELERATORS(加速鍵),DIALOG(對話框),MENU(菜單),STRINGTABLE(字符串表),RCDATA(自定義資源)等8種資源,詳細(xì)的描述可以參考有關(guān)MFC的書籍,在Win32A***中的資源編譯器的語法中,一般格式是這些資源的定義方法是:
位圖定義: nameID BITMAP [load-mem] filename
光標(biāo)定義: nameID CURSOR [load-mem] filename
圖標(biāo)定義: nameID ICON [load-mem] filename
加速鍵定義:
acctablename ACCELERATORS [optional-statements]
BEGIN event, idvalue, [type] [options]
. . .
END
等等,具體的定義和參數(shù)可以參考 Masm32v5 中的 Rc.hlp 幫助文件。我們可以用資源編輯器來所見即所得地編輯資源,也可以在文本編輯器中用上面這些語句自己定義資源。
在程序中使用資源
在程序中,要使用資源之前必須先裝如內(nèi)存,Windows定義了一系列的API來裝入資源,如 LoadMenu,LoadString,LoadBitmap 等等,如 LoadBitmap 的定義:
HBITMAP LoadBitmap(
HINSTANCE hInstance, // handle of application instance
LPCTSTR lpBitmapName // address of bitmap resource name
);
這些Load函數(shù)的返回值是一個句柄,調(diào)用參數(shù)中一般至少為兩項(xiàng): hInstance 和 ResouceName,這個 ResouceName(如BitmapName,MenuName)就是在資源文件中的 #define 指定的值,如果你用 #define MY_ICON 10/ MY_ICON ICON "Main.ico" 定義了一個圖標(biāo),那么在程序中要使用 Main.ico 圖標(biāo)就可以用 LoadIcon(hInstance,10) 來裝入已經(jīng)定義為10號的圖標(biāo)文件。另一個參數(shù) hInstance 是執(zhí)行文件的句柄,它對應(yīng)資源所在的文件名,你可以在程序開始執(zhí)行時用 invoke GetModuleHandle,NULL 獲得 hInstance。另外一些資源并不是顯式地裝入的,如對話框資源,它是在建立對話框的函數(shù)中由Windows自己裝入的,如下面例子中的 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 ,是在屏幕上顯示一個資源文件中已經(jīng)定義好了的對話框,就并不存在 LoadDialogBox 之類的API來先裝入對話框。
Win32A*** - 顯示一個對話框
介紹了這么多相關(guān)的東西,現(xiàn)在讓我們來看看如何顯示一個對話框,源程序如下:
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
DLG_MAIN equ 1
.data?
hInstance dd ?
szBuffer db 256 dup (?)
_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
.code
;********************************************************************
_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,wMsg
.if eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
.elseif eax == WM_INITDIALOG
.elseif eax == WM_COMMAND
mov eax,wParam
.if eax == IDOK
invoke EndDialog,hWnd,NULL
.elseif eax == IDCANCEL
invoke EndDialog,hWnd,NULL
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;********************************************************************
start:
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0
invoke ExitProcess,NULL
end start
看了前面幾篇文章以后,這兒的大部分語句應(yīng)該是很熟悉了,我來講解幾句新的語句:
_ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
PROTO 語句類似于C語言中的函數(shù)定義,在Win32匯編中,如果子程序的定義在引用以后,你就必須先定義,當(dāng)然,這個定義是針對 invoke 語句和其他帶參數(shù)的調(diào)用的,如果你的子程序沒有參數(shù),你就可以用 call 指令去調(diào)用它而不是用宏指令 invoke,這時候你就不必聲明這個函數(shù)。
_ProcDlgMain proc uses ebx edi esi, \
hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD
這個定義 proc 的語句應(yīng)該是不陌生的,要重復(fù)講解一下的是 uses 和 下面的參數(shù),uses 下的寄存器表示要編譯器自動插入保存及恢復(fù)這些寄存器的指令,\ 是在 Masm32 中接下一行的符號,表示下一行是本行的繼續(xù)內(nèi)容,以避免一行中的內(nèi)容過長。下面的 hWnd:DWORD 等語句定義了調(diào)用這個子程序的參數(shù),如果有以下定義 MyProc proc dwPara1:DWORD,dwPara2:DWORD,dwPara3:DWORD,然后你用 invoke MyProc 1,2,3 來調(diào)用它,那么,1,2,3 將分別被賦值給 dwPara1,dwPara2,dwPara3,你可以在子程序中使用這些傳遞過來的參數(shù)。如果參數(shù)的類型是雙字,那么:DWORD 可以省略。
.if/.else/.elseif/.endif
這些語句是宏指令,實(shí)際上不說你也知道它們的意思,有了這些宏指令,我們就可以把匯編編得象C一樣結(jié)構(gòu)清晰,而不必老是看到 jmp 指令了,當(dāng)然,這只不過編譯器幫你做了這些事情而已,如果你去反匯編一下,你開始會看到一大堆 jmp 指令,.if 的格式如下
.if eax == 1 如果eax等于1
.if eax != 1 如果eax不等于1
.if eax != 1 && ebx != 2 如果eax不等于1且ebx不等于2
.if eax == 1 || ebx == 2 如果eax等于1或者ebx等于2
其他的宏指令還有 .while/.endw .break 等等,可以參考 Masm32V5 的幫助文件 Masm32.hlp
最后要講到的就是 DialogBoxParam 這個API了,在Windows中,所有的窗口都要指定一個子程序,當(dāng)Windows檢測到鼠標(biāo)、定時器等和這個窗口有關(guān)的動作時,它回調(diào)用這個子程序,這就是Windows基于消息的體系的最基本的概念,換句話說,在Dos下,我們通過INT指令調(diào)用系統(tǒng),而在Windows 下,有很多時候是你指定子程序地址讓W(xué)indows來調(diào)用你。 invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0中的 offset _ProcDlgMain 就指定了如果有消息發(fā)生,Windows就來執(zhí)行這個子程序,參數(shù)中的 DLG_MAIN 就是在資源文件中定義的對話框模板編號。 hInstance 是對話框所在的資源文件的句柄。
另外,在_ProcDlgMain 子程序中,Windows傳給我們4個參數(shù)hWnd,wMsg,wParam,lParam,其中,hWnd是對話框的窗口句柄,wMsg表示現(xiàn)在發(fā)生的消息事件,如這個對話框初始化時Windows會以WM_INITDIALOG為消息調(diào)用,關(guān)閉時為WM_CLOSE,按下對話框上的按鈕時為WM_COMMAND等,wParam和lParam是附加的參數(shù),對應(yīng)不同的消息對應(yīng)不同定義,具體可以參考Win32 Programmer's reference。
Win32匯編教程四
編寫一個簡單的窗口
有關(guān)窗口的基本知識
窗口是屏幕上的矩形區(qū)域。一個窗口可以從鍵盤或者鼠標(biāo)接受用戶的輸入,并在其內(nèi)部顯示圖形輸出。一個應(yīng)用程序窗口通常包含程序的標(biāo)題條、菜單、邊框,滾動條。其中,對話框也是一種窗口。不同的是,對話框表面通常包含幾個其它窗口,稱之為“子窗口”。這些子窗口的形式有壓入按鈕、單選按鈕、復(fù)選框、文本輸入?yún)^(qū)域、列表框和滾動條等。 用戶將這些窗口看成屏幕上的對象,可以通過按下一個按鈕或者滾動一個滾動條與這些對象直接交互。
窗口以“消息”的形式接收窗口的輸入,窗口也用消息與其它窗口通訊。比如在程序窗口的大小改變時,字處理器會重新格式化其中的文本。窗口大小改變的細(xì)節(jié)是由操作系統(tǒng)處理的,但程序能夠響應(yīng)這個系統(tǒng)功能。當(dāng)用戶改變窗口的大小時,Windows給程序發(fā)送一條消息指出新窗口的大小。然后,程序就可以調(diào)整窗口中的內(nèi)容,以響應(yīng)大小的變化。程序創(chuàng)建的每一個窗口都有相關(guān)的窗口過程。也就是給這個窗口指定一個子程序(窗口過程),Windows通過調(diào)用它來給窗口發(fā)送消息。窗口過程再根據(jù)此消息進(jìn)行處理,然后將控制返回給Windows。
窗口在“窗口類”的基礎(chǔ)上創(chuàng)建的。Windows定義了確省的窗口過程,如果你對所有的消息都讓W(xué)indows自己處理,那么你就能得到一個標(biāo)準(zhǔn)的窗口,同樣,你也可以選擇處理自己感興趣的消息,這樣,相當(dāng)于產(chǎn)生了不同的子類,也就形成了不同的應(yīng)用程序。同樣,子窗口也是基于同一個窗口類,并且使用同一個窗口過程。例如,所有Windows 程序中的所有按鈕都基于同一窗口類。這個窗口類有一個處理所有按鈕消息的窗口過程,但是,如果你按自己的設(shè)想設(shè)計(jì)一個按鈕,如想把按鈕的表面換成位圖,你就可以自己處理按鈕窗口的 WM_PAINT 消息,當(dāng) Windows 需要畫按鈕表面的時候,你就可以隨自己的意思去畫。
Windows程序開始執(zhí)行后,Windows為該程序創(chuàng)建一個“消息隊(duì)列”。這個消息隊(duì)列用來存放該程序可能創(chuàng)建的各種不同窗口的消息。程序中有一段代碼,叫做“消息循環(huán)”, 它用來從隊(duì)列中取出消息,并且將它們發(fā)送給相應(yīng)的窗口過程。在沒有消息發(fā)生的時候,你的程序?qū)嶋H上就在消息循環(huán)中轉(zhuǎn)圈子。
創(chuàng)建一個窗口的過程如下:
取得程序的實(shí)例句柄(hInstance)
注冊窗口類,實(shí)際上就是為你的窗口指定處理消息的過程,定義光標(biāo),窗口風(fēng)格,顏色等參數(shù)
創(chuàng)建窗口
顯示窗口
然后進(jìn)入消息循環(huán),也就是不停地檢測有無消息,并把它發(fā)送給窗口進(jìn)程去處理。
創(chuàng)建一個窗口的代碼在不同的程序中實(shí)際上是幾乎一模一樣的,所以你編一個新的程序時可以把這一段拷來拷去,稍微修改一下就行,程序的大部分代碼實(shí)際上是用在窗口過程中,因?yàn)檫@才是不同程序的不同之處。窗口過程的編程要點(diǎn)如下:
從Windows傳給窗口過程的參數(shù) uMsg 得到消息類型,并轉(zhuǎn)到不同的分枝去處理。
對自己已經(jīng)處理的消息,返回 Windows 時必須在eax 中返回0。
自己不處理的消息,必須調(diào)用 DefWindowProc 處理,并把返回值傳回Windows,否則,Windows會無法顯示。
uMsg 參數(shù)中指定的消息有280多種,實(shí)際上我們需要處理的只有重要的幾種,如Windows在創(chuàng)建的時候會發(fā)送 WM_CREATE 消息,我們就可以在這時候初始化,分配內(nèi)存等等,而退出時會發(fā)送 WM_CLOSE,我們就可以進(jìn)行釋放內(nèi)存等清除工作,當(dāng)Windows上的菜單或按鈕被按下時發(fā)送 WM_COMMAND 消息等等,具體可以參考 Win32 Programmer's Reference。下面,我們來看一個創(chuàng)建窗口的簡單程序。
一個創(chuàng)建窗口的程序
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
include comctl32.inc
include comdlg32.inc
include gdi32.inc
includelib user32.lib
includelib kernel32.lib
includelib comctl32.lib
includelib comdlg32.lib
includelib gdi32.lib
IDI_MAIN equ 1000 ;icon
IDM_MAIN equ 4000 ;menu
IDM_EXIT equ 4001
.data?
hInstance dd ?
hWinMain dd ?
hMenu dd ?
szBuffer db 256 dup (?)
.data
szClassName db "Windows Template",0
szCaptionMain db '窗口模板',0
.code
start:
call _WinMain
invoke ExitProcess,NULL
_WinMain proc
local @stWcMain:WNDCLASSEX
local @stMsg:MSG
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke LoadIcon,hInstance,IDI_MAIN
mov hIcon,eax
invoke LoadMenu,hInstance,IDM_MAIN
mov hMenu,eax
;*************** 注冊窗口類 *****************************************
invoke LoadCursor,0,IDC_ARROW
mov @stWcMain.hCursor,eax
mov @stWcMain.cbSize,sizeof WNDCLASSEX
mov @stWcMain.hIconSm,0
mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW
mov @stWcMain.lpfnWndProc,offset WndMainProc
mov @stWcMain.cbClsExtra,0
mov @stWcMain.cbWndExtra,0
mov eax,hInstance
mov @stWcMain.hInstance,eax
mov @stWcMain.hIcon,0
mov @stWcMain.hbrBackground,COLOR_WINDOW + 1
mov @stWcMain.lpszClassName,offset szClassName
mov @stWcMain.lpszMenuName,0
invoke RegisterClassEx,addr @stWcMain
;*************** 建立輸出窗口 ***************************************
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW OR WS_VSCROLL OR WS_HSCROLL,\
0,0,550,300,\
NULL,hMenu,hInstance,NULL
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;*************** 消息循環(huán) *******************************************
.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
WndMainProc proc uses ebx edi esi, \
hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
mov eax,uMsg
.if eax == WM_CREATE
mov eax,hWnd
mov hWinMain,eax
call _Init
;********************************************************************
.elseif eax == WM_COMMAND
.if lParam == 0
mov eax,wParam
.if ax == IDM_EXIT
call _Quit
.endif
.endif
;********************************************************************
.elseif eax == WM_CLOSE
call _Quit
;********************************************************************
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
WndMainProc endp
_Init proc
invoke SendMessage,hWinMain,WM_SETICON,ICON_***ALL,hIcon
ret
_Init endp
;********************************************************************
_Quit proc
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
ret
_Quit endp
;********************************************************************
end start
窗口程序的分析
讓我們來簡單分析一下這個程序,首先程序調(diào)用 _WinMain,在_WinMain 中定義了兩個局部變量 @stMsg 和 @stWinMain,數(shù)據(jù)類型分別是 MSG 和 WNDCLASSEX結(jié)構(gòu),在參考手冊中,可以看到WNDCLASSEX定義了一個窗口的所有參數(shù),如使用的菜單、光標(biāo)、顏色、窗口過程等,接下來的一大堆 mov 指令實(shí)際上就是在填寫這個數(shù)據(jù)結(jié)構(gòu),填寫完成后,最重要的兩句是 mov @stWcMain.lpfnWndProc,offset WndMainProc 定義了處理消息的窗口過程, mov @stWcMain.lpszClassName,offset szClassName 定義了你要創(chuàng)建的類的名稱,然后就是使用 RegisterClassEx 注冊這個窗口類,注意,這時候窗口并沒有創(chuàng)建,你只不過是定義好了一個子類,接下去你要用你定義的類去創(chuàng)建一個窗口。也就是使用 CreateWindowEx 函數(shù)去創(chuàng)建它。在手冊中,CreateWindowEx 是這樣定義的:
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 identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data );
其中的參數(shù) dwExStyle 是窗口的風(fēng)格,lpClassName 就是我們自己定義的類的名字。如果大家要創(chuàng)建一個已經(jīng)定義好的類,如 RichEdit 類等等,只要把 lpClassName 指向 "RichEdit32" 字符串就行了,當(dāng)然這時就不用 RegisterClass 以及編寫自己的窗口過程了。執(zhí)行 CreateWindowEx 后,得到一個返回值就是窗口句柄,這個值在以后是要經(jīng)常用到了,所以要先保存下來。這時窗口并沒有在屏幕上顯示出來,而是處于隱藏狀態(tài),我們要用 ShowWindow 來顯示出窗口并用UpdateWindow 來繪窗口的內(nèi)容。
窗口顯示出來后,程序就進(jìn)入一個循環(huán)----消息循環(huán),前面我已經(jīng)說過,作用是不停地接收 Windows 消息并發(fā)送給窗口過程去處理。GetMessage 從消息隊(duì)列中取出一條消息,如果取得的消息不是 WM_QUIT,那么 GetMessage 返回一個非零值,否則返回零,這時候循環(huán)結(jié)束,程序執(zhí)行 ExitProcess退回操作系統(tǒng)。TranslateMessage 將消息進(jìn)行一些鍵盤轉(zhuǎn)換,用于處理一些快捷鍵,DispatchMessage 將處理后的消息發(fā)回 Windows,由Windows調(diào)用窗口進(jìn)程進(jìn)行處理,當(dāng)窗口進(jìn)程處理完返回后,程序才從 DispatchMessage 返回,從而開始下一個 GetMessage 調(diào)用。這些函的參數(shù)可以參考手冊。
窗口過程的分析
窗口過程有四個參數(shù),hWnd 是本窗口的句柄,和創(chuàng)建窗口時返回的值相同,uMsg 是本次調(diào)用的消息類型,wParam 和lParam是消息的參數(shù),其含義和數(shù)值根據(jù)消息的不同而不同。在本程序中,我們處理 WM_CREATE,WM_COMMAND 和 WM_QUIT 消息,然后返回0,對不處理的消息,使用 invoke DefWindowProc,hWnd,uMsg,wParam,lParam 來處理并直接用 ret 將返回值傳回 Windows。在響應(yīng) WM_CLOSE 消息時,我們用 DestroyWindow 清除窗口并用PostQuitMessage 產(chǎn)生一條 WM_QUIT 消息,從而使程序在 消息循環(huán)調(diào)用GetMessage 時返回0,以結(jié)束消息循環(huán)并結(jié)束程序。