win32 009 masm32

籤:model語句, 使用MASM, 內存模式, 指令集 Win32彙編

使用MASM01

 

讓編程改變世界

Change the world by program


 

使用MASM

 

經過上一講的準備工作,相信大家已經搭建好了 Win32 彙編的工作環境,並已經知道編譯、鏈接一個程序的過程和原理了。

現在,我們讓例子迴歸到經典:

#include <stdio.h>
int main(void)
{
    Printf(“Hello, worldn”);
} // 事實上想想,這不正是初生的嬰兒?!

 

Win32彙編源程序的結構

 

麻雀雖小,五臟俱全。剛剛那個C語言的”Hello, world”程序包含了C語言中的最基本的格式。

在C語言的源程序中,我們不需要爲堆棧段、數據段和代碼段的定義而煩惱,編譯器會自己解決。

 

回顧一下,在DOS 下的彙編這段代碼會變成什麼樣?

在例子中我們看到,stack、data、code都找到了自己的小窩。

迴歸主題,在Win32 彙編語言下,小麻雀”Hello World” 又會變成什麼樣子呢?

是不是又不同了?但是,我們怎麼就發覺Win32 彙編其實是前邊兩種形態的集大成者?!

接下來,小甲魚帶大家逐段來理解和接受這個新先的語言!

 

模式定義

 

程序的第一部分是模式和源程序格式的定義語句

.386
.model flat,stdcall
option casemap:none

 

這些指令定義了程序使用的指令集、工作模式和格式。

 

1)指定使用的指令集

.386語句是彙編語句的僞指令,類似的指令還有:.8086、.186、.286、.386/.386p、.486/.486p和.586/.586p等,用於告訴編譯器在本程序中使用的指令集。

在DOS的彙編中默認使用的是8086指令集,那時候如果在源程序中寫入80386所特有的指令或使用32位的寄存器就會報錯。

Win32環境工作在80386及以上的處理器中,所以這一句.386是必不可少的。

 

另外,後面帶p的僞指令則表示程序中可以使用特權指令,如:mov cr0,eax

這一類指令必須在特權級0上運行,如果只指定.386,那麼使用普通的指令是可以的,編譯時到這一句就會報錯。

 

如果我們要寫的程序是VxD等驅動程序,中間要用到特權指令,那麼必須定義.386p,在應用程序級別的Win32編程中,程序都是運行在優先級3上,不會用到特權指令,只需定義.386就夠了。

80486和Pentium處理器指令是80386處理器指令的超集,同樣道理,如果程序中要用80486處理器或Pentium處理器的指令,則必須定義.486或.586。

 

另外,Intel公司的80×86系列處理器從Pentium MMX開始增加了MMX指令集,爲了使用MMX指令,除了定義.586之外,還要加上一句.mmx僞指令:

.386
.mmx

2)model語句

.model語句在低版本的宏彙編中已經存在,用來定義程序工作的模式,它的使用方法是:

.model 內存模式 [,語言模式] [,其他模式]

內存模式的定義影響最後生成的可執行文件,可執行文件的規模從小到大,可以有很多種類型。

 

詳見下表:

內存模式

 

Windows?程序運行在保護模式下,系統把每一個Win32應用程序都放到分開的虛擬地址空間中去運行。

也就是說,每一個應用程序都擁有其相互獨立的4GB地址空間。

 

對Win32程序來說,只有一種內存模式,即flat(平坦)模式,意思是內存是很平坦地從0延伸到4GB,再沒有64KB段大小限制。

對比一下DOS的Hello World和Win32的Hello World開始部分的不同,DOS程序中有這樣語句

mov ax,data
mov ds,ax

 

意思是把數據段寄存器DS指向data數據段,data數據段在前面已經用data segment語句定義,只要DS不重新設置,那麼從此以後指令中涉及的數據默認將從data數據段中取得。

所以下面的語句是從data數據段取出szHello字符串的地址後再顯示:

mov ah,9
mov dx,offset szHello
int 21h

 

縱觀Win32彙編的源程序,沒有一處可以找到ds或es等段寄存器的使用。

因爲所有的4GB空間用32位的寄存器全部都能訪問到了,不必在頭腦中隨時記着當前使用的是哪個數據段,這就是平坦內存模式帶來的好處。

 

如果定義了.model flat,MASM自動爲各種段寄存器做了如下定義:

ASSUME cs:FLAT, ds:FLAT, ss:FLAT, es:FLAT, fs:ERROR, gs:ERROR

也就是說,CS,DS,SS和ES段全部使用平坦模式,FS和GS寄存默認不使用,這時若在源程序中使用FS或GS,在編譯時會報錯。

 

如果有必要使用它們,只需在使用前用下面的語句聲明一下就可以了:

assume fs:nothing, gs:nothing

或者

assume fs:flat, gs:flat

 

在Win32彙編中,.model語句中還應該指定語言模式,即子程序和調用方式。

例子中用的是stdcall,它指出了調用子程序或Win32 API時參數傳遞的次序和堆棧平衡的方法。

相對於stdcall,不同的語言類型還有C, SysCall, BASIC, FORTRAN 和PASCALL,雖然各種高級語言在調用子程序時都是使用堆棧來傳遞參數。

Windows的API調用使用是的stdcall格式,所以在Win32彙編中沒有選擇,必須在.model中加上stdcall參數。

 

話題:理解stdcall和cdecl

 

(1)_stdcall調用

_stdcall是Pascal程序的缺省調用方式,參數採用從右到左的壓棧方式,被調函數自身在返回前清空堆棧。WIN32 Api都採用_stdcall調用方式。

 

(2)_cdecl調用

_cdecl是C/C++的缺省調用方式,參數採用從右到左的壓棧方式,傳送參數的內存棧由調用者維護。

_cedcl約定的函數只能被C/C++調用,每一個調用它的函數都包含清空堆棧的代碼,所以產生的可執行文件大小會比調用_stdcall函數的大。

使用MASM02

 

讓編程改變世界

Change the world by program


 

模式定義

 

3)option語句

如例子中,我們定義了 option casemap:none 的意義是告訴編譯器程序中的變量名和子程序名是否對大小寫敏感。

由於Win32 API 的API函數名稱本質是區分大小寫的,所以必須指定這個選項,否則調用API函數就會出現問題。

 

段的定義

把上面的Win32的Hello World源程序中的語句歸納精簡一下,再列在下面:

.386
.model flat,stdcall
option casemap:none
<一些include語句>
.data
<一些字符串、變量定義>
.code
      <代碼>
      <開始標號>
             <其他語句>
end 開始標號

 

模式定義中的模式、選項等定義並不會在編譯好的可執行程序中產生什麼東西,它們只是說明。

而真正的數據和代碼是定義在各個段中的,如上面的.data段和.code段,考慮到不同的數據類型,還可以有其他種類的數據段。

 

下面是包含全部段的源程序結構:

.386
.model flat,stdcall
option casemap:none
<一些include語句>
.stack [堆棧段的大小]
.data
<一些初始化過的變量定義>
.data 
<一些沒有初始化過的變量定義>
.const
<一些常量定義>
.code
      <代碼>
      <開始標號>
             <其他語句>
end 開始標號

 

.stack、.data、.data 、.const和.code是分段僞指令,Win32中實際上只有代碼和數據之分,

.data,.data 和.const這些都是指向數據段,.code是指向代碼段。

和DOS彙編不同,Win32彙編不必考慮堆棧,系統會爲程序分配一個向下擴展的、足夠大的段作爲堆棧段,所以.stack段定義常常被忽略。

 

解決之前提出的問題

 

前面我們不是說過Win32環境下不用段了嗎?

是的,這些“段”,實際上並不是DOS彙編中那種意義的段,而是內存的“分段”。

上一個段的結束就是下一個段的開始,所有的分段,合起來,包括系統使用的地址空間,就組成了整個可以尋址的4GB空間。

我們接着往下看會更加容易理解。

 

Win32彙編的內存管理使用了80386處理器的分頁機制,每個頁(4KB大小)可以自由指定屬性,所以上一個4KB可能是代碼,屬性是可執行但不可寫,下一個4KB就有可能是既可讀也可寫但不可執行的數據。

 

再下面呢?

有可能是可讀不可寫也不可執行的數據。(主要就看你放的是什麼東西……)

嘿嘿,大家是否有點理解了?沒關係,接着往下!

Win32彙編源程序中“分段”的概念實際上是把不同類型的數據或代碼歸類,再放到不同屬性的內存頁(也就是不同的“分段”)中,這中間不涉及使用不同的段選擇器。(僅僅是配合分頁機制搞搗鼓~)

雖然使用和DOS彙編同樣的.code和.data語句來定義,意思可是完全不同了!

至此,相信大家和小甲魚一樣清晰啦,感謝老師,感謝拉登,感謝嫦娥^_^

 

數據段

 

.data、.data 和.const定義的是數據段,分別對應不同方式的數據定義,在最後生成的可執行文件中也分別放在不同的節區(Section)中。

(這個在我們講解PE結構的時候會很細緻描述)

 

程序中的數據定義一段可以歸納爲3類:

– 第一類是可讀可寫的已定義變量。

– 第二類是可讀可寫的未定義變量。

– 第三類數據是一些常量。

 

可讀可寫的已定義變量

這些數據在源程序中已經被定義了初始值,而且在程序的執行中有可能被更改。

如一些標誌等,這些數據必須定義在.data段中,.data段是已初始化數據段。

其中定義的數據是可讀可寫的,在程序裝入完成的時候,這些值就已經在內存中了,.data段存放在可執行文件的_DATA節區內。

 

可讀可寫的未定義變量

這些變量一般是當做緩衝區或者在程序執行後纔開始使用的,這些數據可以定義在.data段中,也可以定義在.data 段中。

但一般把它放到.data 段中。

雖然定義在這兩種段中都可以正常使用,但定義在.data 段中不會增大.exe文件的大小。

 

舉例說明,如果要用到一個100KB的緩衝區,可以在數據段中定義:

szBuffer db 100 * 1024 dup ( )

 

如果放在.data段中,編譯器認爲這些數據在程序裝入時就必須有效,所以它在生成可執行文件的時候保留了所有的100KB的內容,即使它們是全零!

如果程序其他部分的大小是50KB,那麼最後的.exe文件就會是150KB大小,如果緩衝區定義爲1MB,那麼.exe文件會增大到1050KB。

 

.data 段則不同,其中的內容編譯器會認爲程序在開始執行後纔會用到,所以在生成可執行文件的時候只保留了大小信息,不會爲它浪費磁盤空間。

和上面同樣的情況下,即使緩衝區定義爲1MB,可執行文件同樣只有50KB!總之,.data 段是未初始化數據段,其中的數據也是可讀可寫的,但在可執行文件中不佔空間,.data 段在可執行文件中存放在_BSS節區中。

 

數據是一些常量

 

如一些要顯示的字符串信息,它們在程序裝入的時候也已經有效,但在整個執行過程中不需要修改,這些數據可以放在.const段中,.const段是常量段,它是可讀不可寫的。

一般爲了方便起見,在小程序中常常把常量一起定義到.data段中,而不另外定義一個.const段。

在程序中如果不小心寫了對.const段中的數據做寫操作的指令,會引起保護錯誤,Windows會顯示一個提示框並結束程序。

使用MASM03

 

讓編程改變世界

Change the world by program


 

代碼段

 

.code段是代碼段,所有的指令都必須寫在代碼段中,在可執行文件中,代碼段是放在_TEXT節區(區塊)中的。

Win32環境中的數據段是不可執行的,只有代碼段有可執行的屬性。

對於工作在特權級3的應用程序來說,.code段是不可寫的,在編寫DOS彙編程序的時候,我們可以爲非作歹。

如果企圖在Win32彙編下做同樣的事情,結果就是和上面同樣 “非法操作”!

 

當然事物總有兩面性,在Windows95下,在特權級0下運行的程序對所有的段都有讀寫的權利,包括代碼段。

另外,在優先級3下運行的程序也不是一定不能寫代碼段,代碼段的屬性是由可執行文件PE頭部中的屬性位決定的。

通過編輯磁盤上的.exe文件,把代碼段屬性位改成可寫,那麼在程序中就允許修改自己的代碼段。

 

一個典型的應用就是一些針對可執行文件的壓縮軟件和加殼軟件,如Upx和PeCompact等。

這些軟件靠把代碼段進行變換來達到解壓縮和解密的目的,被處理過的可執行文件在執行時需要由解壓代碼來將代碼段解壓縮。

這就需要寫代碼段,所以這些軟件對可執行文件代碼段的屬性預先做修改。

 

爲了帶大家更好認識這些花花綠綠的“段”到底是什麼回事,小甲魚帶大家看一張圖……

 

程序結束和程序入口

 

在C語言源程序中,程序不必顯式地指定程序由哪裏開始執行,編譯器已經約定好從main() 函數開始執行了。

而在彙編程序中,並沒有一個main函數,程序員可以指定從代碼段的任何一個地方開始執行,這個地方由程序最後一句的end語句來指定:

end [開始地址]

 

這句語句同時表示源程序結束,所有的代碼必須在end語句之前。

end start

 

上述語句指定程序從start這個標號開始執行。當然,start標號必須在程序的代碼段中有所定義。

但是,一個源程序不必非要指定入口標號,這時候可以把開始地址忽略不寫,這種情況發生在編寫多模塊程序的單個模塊的時候。

 

當分開寫多個程序模塊時,每個模塊的源程序中也可以包括.data、.data、.const和.code段,結構就和上面的Win32 Hello World一樣,只是其

他模塊最後的end語句必須不帶開始地址。

 

當最後把多個模塊鏈接在一起的時候,只能有一個主模塊指定入口地址,在多個模塊中指定入口地址或者沒有一個模塊指定了入口地址,鏈接程序都會報錯。

 

註釋

 

註釋是源程序中不可忽略的一部分,彙編源程序的註釋以分號(;)開始,註釋既可以在一行的頭部,也可以在一行的中間,一行中所有在分號之後的字符全部當做註釋處理,但在字符串的字義中包含的引號內的分號不當做是註釋的開始。

;這裏是註釋
call _PrintChar             ;這裏是註釋
szChar    db    ‘Hello, world; ’,0dh,0ah

 

換行

 

當源程序的某一行過長,不利於閱讀的時候,可以分行書寫,分行的辦法是在一行的最後用反斜槓()做換行符,如:

invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK

可以寫爲:

invoke MessageBox, 
             NULL,                        ;父窗口句柄
             offset szText,                      ;消息框中的文字
             offset szCaption,          ;標題文字
             MB_OK

 

調用API函數

 

首先,API 是什麼?

答:Win32程序是構築在Win32 API基礎上的。在Win32 API中,包括了大量的函數、結構和消息等。

它不僅爲應用程序所調用,也是Windows自身的一部分,Windows自身的運行也調用這些API函數。

在DOS下,操作系統的功能是通過各種軟中斷來實現的,如大家都知道int 21h是DOS中斷,int 13h和int 10h是BIOS中的磁盤中斷和視頻中斷。

 

當應用程序要引用系統功能時,要把相應的參數放在各個寄存器中再調用相應的中斷,程序控制權轉到中斷中去執行,完成以後會通過iret中斷返回指令回到應用程序中。

 

DOS彙編下的Hello World程序中有下列語句:

mov ah, 9
mov dx, offset szHello
int 21h

 

解釋:這3條語句調用DOS系統模塊中的屏幕顯示功能,功能號放在ah中,9號功能表示屏幕顯示,要輸出到屏幕上的內容的地址放在dx中,然後去調用int 21h,字符串就會顯示到屏幕上。

這個例子說明了應用程序調用系統功能的一般過程。首先,系統提供功能模塊並約定參數的定義方法,同時約定調用的方式,同時約定調用的方式,應用程序按照這個約定來調用系統功能。

 

在這裏,ah中放功能號9,dx中放字符串地址就是約定的參數,int 21h是約定的調用方式。

下面來看看這種方法的不便這處。首先,所有的功能號定義是冷冰冰的數字,int 21h的說明文檔是這樣的:int 21h說明文檔

 

再進入09號功能看使用方法:

Print string (Func 09)
AH = 09h
DS:DX -> string terminated by “$”

 

這就是DOS時代彙編程序員都有一厚本《中斷大全》的原因,因爲所有的功能編號包括使用的參數定義僅從字面上看,是看不出一點頭緒來的。

另外,80×86系列處理器能處理的中斷最多隻能有256個,不同的系統服務程序使用了不同的中斷號,這少得可憐的中斷數量就顯得太少了。

結果到最後是中斷掛中斷,大家搶來搶去的,把好好的一個系統搞得像接力賽跑一樣。

API函數, invoke語句, win32彙編, 關於DLL Win32彙編

使用MASM04

 

讓編程改變世界

Change the world by program


 

調用API函數

 

習慣工作於DOS彙編的程序員同志都有一個願望:如果說,能夠以功能名稱作爲子程序名直接調用,他們願意以生命中的十年壽命作爲交換……

隨着Win32的到來,他們的願望實現了!這就是API函數,它事實上就是以一種新的方法代替了DOS下的中斷。

 

與DOS中斷相比,Win32的系統功能模塊放在Windows的動態鏈接庫(DLL)中。

DLL是一種Windows的可執行文件,採用的是和我們熟悉的.exe文件同樣的PE(PortableExecutable)約定格式。

DLL文件的原理

 

關於DLL

 

DLL事實上只是一個大大的集裝箱,裝着各種系統的API函數。

應用程序在使用的時候由Windows自動載入DLL程序並調用相應的函數。

實際上,Win32的基礎就是由DLL組成的。

 

Win32API的核心由3個DLL提供,它們是:

– KERNEL32.DLL——系統服務功能。包括內存管理、任務管理和動態鏈接等。

– GDI32.DLL——圖形設備接口,處理圖形繪製。

– USER32.DLL——用戶接口服務。建立窗口和傳送消息等。

 

當然,Win32API還包括其他很多函數,這些也是由DLL提供的,不同的DLL提供了不同的系統功能。

如使用TCP/IP協議進行網絡通信的DLL是Wsock32.dll,它所提供的API稱爲SocketAPI;

專用於電話服務方面的API稱爲TAPI(TelephonyAPI),包含在Tapi32.dll中。

 

所有的這些DLL提供的函數組成了現在使用的Win32編程環境。

我們也經常自己打包自己的“集裝箱”!

 

API函數的參數

 

在DOS下,我們演示過無數次,通過中斷來調用系統“函數”,其中的“參數”是通過放在寄存器(ah)中。

Win32API是用堆棧來傳遞參數的,調用者把參數一個個壓入堆棧,DLL中的函數程序再從堆棧中取出參數處理,並在返回之前將堆棧中已經無用的參數丟棄。

在Microsoft發佈的《MicrosoftWin32Programmer’sReference》中定義了常用API的參數和函數聲明。

 

intMessageBox(
    HWNDhWnd,           //handletoownerwindow
    LPCTSTRlpText,      //textinmessagebox
    LPCTSTRlpCaption,   //messageboxtitle
    UINTuType           //messageboxstyle
);//注意,上邊是用C語言表示!

 

Win32彙編語言012

 

API函數的參數

 

上述函數聲明說明了MessageBox有4個參數,這些數據類型看起來很複雜。

但有一點是很重要的,對於彙編語言來說,Win32環境中的參數實際上只有一種類型,那就是一個32位的整數,所以這些HWND,LPCTSTR和UINT實際上就是彙編中的dword(doubleword,雙字型,4個字節,兩個字,32位)之所以定義爲不同的模樣,主要是用來說明了用途。

由於Windows是用C寫成的,世界上的程序員好像也是用C語言的最多,所以Windows所有編程資料發佈的格式也是C格式。

 

上面的聲明用匯編的格式來表達就是:

MessageBoxProtohWnd:dword,lpText:dword,lpCaption:dword,uType:dword

 

在彙編中調用MessageBox函數的方法是:

pushuType
pushlpCaption
pushlpText
pushhWnd
callMessageBox

 

在源程序編譯鏈接成可執行文件後,callMessageBox語句中的MessageBox會被換成一個地址,指向可執行文件中的導入表的一個索引(函數名或索引號)。

導入表中指向MessageBox函數的實際地址會在程序裝入內存的時候,根據User32.dll在內存中的位置由Windows系統動態填入。

 

使用invoke語句

 

API是可以調用了,另一個煩人的問題又出現了,Win32的API動輒就是十幾個參數,整個源程序一眼看上去基本上都是把參數壓堆棧的push指令,參數的個數和順序很容易搞錯,由此引起的莫名其妙的錯誤源源不斷,源程序的可讀性看上去也很差。

如果寫的時候少寫了一句push指令,程序在編譯和鏈接的時候都不會報錯,但在執行的時候必定會崩潰,原因是堆棧對不齊了。

 

有木有解決的辦法呢?那是必須得!最好是像C語言一樣,能在同一句中打入所有的參數,並在參數使用錯誤的時候能夠提示。

好消息又來了,Microsoft終於做了一件好事,在MASM中提供了一個僞指令實現了這個功能,那就是invoke僞指令,它的格式是:

invoke函數名[,參數1][,參數2]…[,參數n]

 

invokeMessageBox,NULL,offsetszText,offsetszCaption,MB_OK

 

注意,invoke並不是80386處理器的指令,而是一個MASM編譯器的僞指令,在編譯的時候它把上面的指令展開成我們需要的4個push指令和一個call指令,同時,進行參數數量的檢查工作,如果帶的參數數量和聲明時的數量不符,編譯器報錯:errorA2137:toofewargumentstoINVOKE

 

編譯時看到這樣的錯誤報告,首先要檢查的是有沒有少寫一個參數。

對於不帶參數的API調用,invoke僞指令的參數檢查功能可有可無。

所以既可以用callAPI_Name這樣的語法,也可以用invokeAPI_Name這樣的語法。

 

API函數的返回值

 

有的API函數有返回值,如MessageBox定義的返回值是int類型的數,返回值的類型對彙編程序來說也只有dword一種類型,它永遠放在eax中。

如果要返回的內容不是一個eax所能容納的,Win32API採用的方法一般是返回一個指針,或者在調用參數中提供一個緩衝區地址,乾脆把數據直接返回到緩衝區中去。

Change the world by program


 

函數的聲明

 

在調用API函數的時候,函數原型也必須預先聲明,否則,編譯器會不認這個函數。invoke僞指令也無法檢查參數個數。聲明函數的格式是:

函數名 proto [距離] [語言] [參數1]:數據類型, [參數2]:數據類型

 

句中的proto是函數聲明的僞指令,距離可以是NEAR,FAR,NEAR16,NEAR32,FAR16或FAR32。

Win32中只有一個平坦的段,無所謂距離,所以在定義時是忽略的;語言類型就是.model那些類型,如果忽略,則使用.model定義的默認值。

 

對Win32彙編來說只存在dword類型的參數,所以所有參數的數據類型永遠是dword。

另外對於編譯器來說,它只關心參數的數量,參數的名稱在這裏是無用的,僅是爲了可讀性而設置的,可以省略掉。

 

所以下面兩句消息框函數的定義實際上是一樣的:

MessageBox Proto hWnd:dword, lpText:dword, lpCaption:dword, uType:dword

MessageBox Proto :dword, :dword, :dword, :dword

 

在Win32環境中,和字符串相關的API共有兩類,分別對應兩個字符集:一類是處理ANSI字符集(1B)的,另一類是處理Unicode字符集(2B)的。

前一類函數名字的尾部帶一個A字符,處理Unicode的則帶一個W字符。

我們比較熟悉的ANSI字符串是以NULL結尾的一串字符數組,每一個ANSI字符佔一個字節寬。

對於歐洲語言體系,ANSI字符集已足夠了,但對於有成千上萬個不同字符的幾種東方語言體系來說,Unicode字符集更有用。

 

MessageBox和顯示字符串有關,同樣它有兩個版本,嚴格地說,系統中有兩個定義:

MessageBoxA Proto hWnd:dword, lpText:dword, lpCaption:dword, uType:dword

MessageBoxB Proto hWnd:dword, lpText:dword, lpCaption:dword, uType:dword

 

雖然《Microsoft Win32 Programmer’s Reference》中只有一個MessageBox定義,但User32.dll中確確實實沒有MessageBox,而只有MessageBoxA和MessageBoxW,那麼爲什麼還是可以使用MessageBox呢?Follow

由於並不是每個Win32系統都支持W系統的API,例如在Windows 9x系列中,對Unicode是不支持的,很多的API只有ANSI版本,只有Windows NT系列纔對Unicode完全支持。

爲了編寫在幾個平臺中通用的程序,一般應用程序都使用ANSI版本的API函數集。

這樣的話,爲了使程序更有移植性,在源程序中一般不直接指明使用Unicode還是ANSI版本,而是使用宏彙編中的條件彙編功能來統一替換。

 

如在源程序中使用MessageBox,但在頭文件中定義:

if UNICODE
       MessageBox    equ   <MessageBoxW>
else
       MessageBox    equ   <MessageBoxA>
endif

 

所有涉及版本問題的API都可以按此方法定義,然後在源程序的頭指定UNICODE=1或UNICODE=0,重新編譯後就能產生不同的版本。

 

include語句

 

對於所有要用到的API函數,在程序的開始部分都必須預先聲明,但這一個步驟顯然是比較麻煩的,爲了簡化操作,可以採用各種語言通用的解決辦法,就是把所有的聲明預先放在一個文件中,在用到的時候再用include語句包含進來。

現在回到Win32 Hello World程序,這個程序用到了兩個API函數:MessageBox和ExitProcess,它們分別在User32.dll和Kernel32.dll中。

 

MASM32工具包中已經包括了所有DLL的API函數聲明列表,每個DLL對應<DLL名.inc>文件(這些文件就是存放對應的函數聲明),在源程序中只要使用include語句包含進來就可以了:

include user32.inc

include kernel32.inc

 

當用到其他的API函數時,只需相應增加對應的include語句。

編譯器對include語句的處理僅是簡單地把這一行用指定的文件內容替換掉而而已。

 

include語句的語法是:

include 文件名

include <文件名>

當遇到要包括的文件名和MASM的關鍵字同名等可能會引起編譯器混淆的情況時,可以用<>將文件名括起來。

 

includelib語句

 

在DOS彙編中,使用中斷調用系統功能是不必聲明的,處理器自己知道到中斷向量表中去取中斷地址。

在Win32彙編中使用API函數,程序必須知道調用的API函數存在於哪個DLL中。

 

否則,操作系統必須搜索系統中存在的所有DLL,並且無法處理不同DLL中的同名函數,這顯然是不現實的。

所以,必須有個文件包括DLL庫正確的定位信息,這個任務是由導入庫來實現的。

 

在使用外部函數的時候,DOS下有函數庫的概念,那時的函數庫實際上是靜態庫,靜態庫是一組已經編寫好的代碼模塊,在程序中可以自由引用。

在源程序編譯成目標文件,最後要鏈接可執行文件的時候,由link程序從庫中找出相應的函數代碼,一起鏈接到最後的可執行文件中。

 

DOS下C語言的函數庫就是典型的靜態庫。

庫的出現爲程序員節省了大量的開發時間,缺點就是每個可執行文件中都包括了要用到的相同函數的代碼,佔用了大量的磁盤空間,在執行的時候,這些代碼同樣重複佔用了寶貴的內存。

 

Win32環境中,程序鏈接的時候仍然要使用函數庫來定位函數信息,只不過由於函數代碼放在DLL文件中。

庫文件中只留有函數的定位信息和參數數目等簡單信息,這種庫文件叫做導入庫。

 

一個DLL文件對應一個導入庫,如User32.dll文件用於編程的導入庫是User32.lib,MASM32工具包中包含了所有DLL的導入庫。

爲了告訴鏈接程序使用哪個導入庫,使用的語句是:

includelib 庫文件名

includelib <庫文件名>

和include的用法一樣,在要包括讓編譯器混淆的文件名時加括號。

 

Win32 Hello World程序用到的兩個API函數MessageBox和ExitProcess分別在User32.dll和Kernel32.dll中,那麼在源程序使用的相應語句爲:

includelib user32.lib

includelib kernel32.lib

 

和include語句的處理不同,includelib不會把.lib文件插入到源程序中,它只是告訴鏈接器在鏈接的時候到指定的庫文件中去找而已。

Dll文件中的函數沒有包括聲明,所以才需要將.inc文件插進去!

使用MASM06

 

讓編程改變世界

Change the world by program


 

API參數中的等值定義(宏)

 

回過頭來看顯示消息框的語句:

invoke MessageBox, NULL, offset szText, offset szCaption, MB_OK

 

還是這個函數,不過這次我們關注的焦點有所改變:MB_OK

地球人都知道,MB_OK 就是使得程序彈出來的時候有個“確定”的選項!

我們這次來探索他背後的數字含義。

 

回顧一下原型:

int MessageBox( 
    HWND hWnd,	
    LPCTSTR lpText, 
    LPCTSTR lpCaption, 
    UINT uType );

 

在uType這個參數中使用了MB_OK,這個MB_OK是什麼意思?小甲魚帶大家着手來查找文檔!

 

實例演練

 

1. MB_OK 事實上是 1

2. 修改helloworld顯示一個問號、一個確定按鈕、一個取消按鈕

3. 在以上基礎上當按下確定的時候彈出另一個對話框,說”您剛剛按下了確定按鈕”,按下取消的時候同樣要彈一個對話框提醒

4. 事實上用 je, jmp 已經OUT 啦,在MASM下,我們可以用 if, elseif , else……

5. 探究 .if 背後的真相!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章