首先我們看一個“復雜”的Win32匯編程序
程序用來顯示一個消息框
--------------------------------------------------
;文件名:3.asm
.386
.model flat ,stdcall
NULL equ 0
MB_OK equ 0
ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
includelib kernel32.lib
includelib user32.lib
.data
szText db "Hello, world!",0
szCaption db "Win32Asm",0
.code
start:
push MB_OK
lea eax,szCaption
push eax
lea eax,szText
push eax
push NULL
call messageboxa
xor eax,eax
push eax
call exitprocess
end start
--------------------------------------------------
編譯鏈接:
分下面兩步進行:
ml /c /coff 3.asm
link /subsystem:windows /libpath:d:\masm7\lib 3.obj
第一步編譯生成3.obj文件
/c 表示只編譯,不鏈接
/coff 表示生成COFF格式的目標文件
第二步鏈接生成3.exe文件
/subsystem:windows 表示生成windows文件
/libpath:d:\masm7\lib 表示引入庫的路徑為:d:\masm7\lib。
在安裝Masm32後,引入庫位於Masm32\Lib目錄下。
也可設置環境變量Lib的值:在dos提示符下鍵入Set Lib=d:\masm7\lib,這樣“鏈接”就可簡單寫成:
link /subsystem:windows 3.obj,試想一下,在程序調試過程中,修改源程序是常用的事啦,每次編譯鏈接都要帶/libpath:...那該有多煩人呢。當然,我們也可在源程序中直接給出引入庫的位置,這樣,鏈接時就方便啦,如下:
includelib d:\masm7\lib\kernel32.lib
includelib d:\masm7\lib\user32.lib
--------------------------------------------------
執行:在dos提示符下鍵入3,回車,出現一個消息框,哈哈,真正的Win32程序!
--------------------------------------------------
深入分析:
看一下源程序,有這麼兩行:call messageboxa\call exitprocess。大家一看都知道,這是子程序調用,但是我們並沒寫這樣的子程序,事實上,這些是API函數。作為函數,我們在調用時可能需要傳送給函數一些參數,程序怎麼知道傳送的參數有哪些,類型是什麼呢?就是通過函數原型定義,如下所示:
ExitProcess PROTO :DWORD
MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
可以看出,ExitProcess有一個參數,MessageBoxA有四個參數,這些參數都是DWORD類型。
在Win32中,參數的傳遞都是通過堆棧來完成的。象MessageBoxA這個函數有四個參數,究竟是左邊的先壓入堆棧還是右邊的先入棧呢?.model flat,stdcall給出了答案。stdcall 指定參數是從右到左壓入堆棧的,且調整堆棧是在子程序返回時完成的。在源程序中不需要用“add sp,值”來保持堆棧平衡。對MessageBox,在API手冊中是這樣定義的:
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
)
;所以會有我們的程序段:
push MB_OK
lea eax,szCaption
push eax
lea eax,szText
push eax
push NULL
call messageboxa
看看上面的程序,不難想到,假如在寫程序時,少往堆棧裡壓入一個數據,那將是一個致命的錯誤。能不能將這種檢查參數個數是否匹配的工作交給計算機來完成呢?這是可以的,INVOKE指令可以幫助我們完成這樣的工作。假如你的參數個數不正確,連接器將給出錯誤提示。所以,極力建議你使用invoke代替call來調用子程序,當然,這不是絕對的。使用invoke上面的指令就可簡寫成下面的樣子,看起來簡煉多啦,查錯也方便啦!
invoke messageboxa, NULL,addr szText,addr szCaption,MB_OK
另外,像NULL,MB_OK都是一些常量,這樣的常量有很多,還有很多的結構,如果在我們的程序中一開始都寫這麼多的東西,可能一下子就把你嚇怕啦,也容易出錯,更不便於看程序的主要部分。hutch整理的windows.inc包含了WIN32編程所需要的常量和結構體的定義,我們可簡單的用一個include指令將這些常量和結構的定義插入到我們的文件中:
include d:\masm32\include\windows.inc
但是windows.inc中並不包含函數原型的聲明,還要從其他的頭文件中得到函數原型的聲明,比如:messageboxa的原型聲明在user32.inc文件中,exitprocess在kernel32.inc文件中。這些頭文件都放在 \masm32\include文件夾下。
還有,要用windows.inc,必須使用option casemap:none,它的意思是告訴 MASM 要區分符號的大小寫,譬如:start和START是不一樣的。否則,一個小小的程序,可能會出成百上千的錯誤呀!
其他的,就不再細說啦,到此,上面的程序可重新修改如下:
-----------------------------------------------------------------
;最終的結果
.386 ;表示要用到386指令
.model flat,stdcall ;32位程序,要用flat啦!;stadcall,標准調用
option casemap:none ;區別大小寫
include windows.inc ;包括常量及結構定義
include kernel32.inc ;函數原型聲明
include user32.inc
includelib kernel32.lib ;用到的引入庫
includelib user32.lib
.data;數據區,定義2個字符串
szText db "Hello, world!",0
szCaption db "Win32Asm",0
.code ;代碼開始執行處
start:
invoke MessageBox,NULL,addr szText,addr szCaption,MB_OK
;調用MessageBoxAPI函數
invoke ExitProcess,NULL ;程序退出
end start;結束
------------------------------------
編譯鏈接:
ml /c /coff /I d:\masm7\include 3.asm ;注意開關符識別大小寫
/I d:\masm7\include 表示*.inc文件的位置,也可設置環境變量Set include=d:\masm7\include來簡化操作,也可在程序中明確指出*.inc的位置。
前面講的都是用兩條指令來完成編譯鏈接,實際上用一條指令也可完成,如下:
ml /coff /I d:\masm7\include 3.asm /link /subsystem:windows /libpath:lib
若*.inc及引入庫在源程序中都明確指出其位置,則可簡化為:
ml /coff 3.asm /link /subsystem:windows