本課中我們將學習基本的內存管理和文件輸入/輸出操作方面的知識。另外我們還將用上課學的通用對話框作為我們的顯示“設備”。
理論:
從用戶的角度來看,WIN32的內存管理是非常簡單和明了的。每一個應用程序都有自己獨立的4G地址空間,這種內存模式叫做“平坦”型地址模式,所有的段寄存器或描述符都指向同樣的起始地址,所有的地址偏移都是32位的長度,這樣一個應用程序無須變換選擇符就可以存取自己的多達4G的地址空間。這種內存管理模式是非常簡潔而便於管理的,而且我們再不用和那些令人討厭的“near”和“far”指針打交道了。
在W16下有兩種主要類型的API:全局和局部。“全局”的API 分配在其他的段中,這樣從內存角度來看他們是一些“far”(遠)函數或者叫遠過程調用,“局部”API只要和進程的堆打交道,所以把它們叫做“near”(近)函數或者近過程調用。而在WIN32中,這兩種內存模式是相同的,無論您調用GlobalAlloc還是LocalAlloc,結果都是一樣。
至於分配和使用內存的過程都是一樣的:
調用GlobalAlloc函數分配一塊內存,該函數會返回分配的內存句柄。
調用GlobalLock函數鎖定內存塊,該函數接受一個內存句柄作為參數,然後返回一個指向被鎖定的內存塊的指針。
您可以用該指針來讀寫內存。
調用GlobalUnlock函數來解鎖先前被鎖定的內存,該函數使得指向內存塊的指針無效。
調用GlobalFree函數來釋放內存塊。您必須傳給該函數一個內存句柄。
在WIN32中您也可以用“Local”替代內存分配API函數帶有“Global”字樣的函數中的“Global”,也即用LocalAlloc、LocalLock等。
在調用函數GlobalAlloc時使用GMEM_FIXED標志位可以更進一步簡化操作。使用了該標志後,Global/LocalAlloc返回的是指向已分配內存的指針而不是句柄,這樣也就不用調用Global/LocalLock來鎖定內存了,釋放內存時只要直接調用Global/LocalFree就可以了。不過在本課中我們只使用傳統的方法,因為其它地方有許多的源代碼是用這種方法寫的。
WIN32的文件輸入/輸出API和DOS下的從外表上看幾乎一樣(譯者注:也許不管內部實現多麼不同,可以想象所有的文件系統暴露給應用程序編寫者的接口的功能應該基本相同),不同的只是把DOS下的中斷方式處理文件輸入/輸出變成了對API函數的調用。以下是基本的步驟:
調用CreateFile函數生成一個文件,該函數可以應用在多方面,除了磁盤文件外,我們還可以用來打開通訊端口、管道、驅動程序或控制台。如果成功的話,會返回指向文件或設備的句柄。然後可以使用該句柄去完成對文件或設備操作。
調用SetFilePointer來把文件指針移到想讀寫的地方。.
然後調用ReadFile 或 WriteFile來完成實際的讀寫。這些函數會自己處理文件和內存之間的數據傳送,這樣免得您自己去做分配內存等繁雜的瑣事。
調用CloseHandle來關閉文件。該函數接受一個先前打開的文件句柄。
內容:
下面的代碼段演示了:打開一個“打開文件”對話框,用戶可以選擇打開一個文本文件,然後在一個編輯控件中打開該文本文件的內容,另外用戶還可以編輯該文本文件的內容並選擇保存。
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535
EditID equ 1 ; ID of the edit control
.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; Handle to the edit control
hFile HANDLE ? ; File handle
hMemory HANDLE ? ;handle to the allocated memory block
pMemory DWORD ? ;pointer to the allocated memory block
SizeReadWrite DWORD ? ; number of bytes actually read or write
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
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+1
mov wc.lpszMenuName,OFFSET MenuName
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,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,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 uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;==============================================
; Initialize the members of OPENFILENAME structure
;==============================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
--------------------------------------------------------------------------------
分析:
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
處理 WM_CREATE消息時,我們創建一個編輯控件。請注意,我們把該控件大小的有關參數都設成0,因為我們稍後將重新設置該編輯控件的大小,使得其覆蓋父窗口的整個客戶區。
注意:本例中我們沒有必要調用ShowWindow來顯示編輯控件,因為在創建時在其風格中已設置了WS_VISIBLE標志位,在創建父窗口時也可以使用這個小技巧。
;==============================================
; Initialize the members of OPENFILENAME structure
;==============================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
創建完編輯控件後,我們初始話ofn變量的成員。因為稍後在保存文件時還要使用該結構體變量,所以此處只初始化要用到的公共部分。WM_CREATE 消息的處理部分是進行這種初始化的絕佳之處。
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
當主窗口的客戶區部分大小改變時,我們的應用程序將接收到WM_SIZE 消息。當然該窗口第一次顯示時,我們也將接收到該消息。要接收到該消息,主窗口必須有CS_VREDRAW和CS_HREDRAW風格。我們應該把縮放編輯控件的動作放到此處。我們要把編輯控件變成和我們的窗口客戶區一樣大,所以先得要得到父窗口客戶區的大小。這些值包含在參數lParam中,lParam的高字部分是客戶區的高,底字部分是客戶區的寬。然後我們調用MoveWindow函數來重新調整編輯控件的大小,該函數不僅能夠移動窗口的位置,而且能夠改變窗口的大小。
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
當用戶選擇了File/Open菜單項時,我們填充ofn的其他成員,然後調用GetOpenFileName函數顯示一個“打開文件”對話框。
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
如果用戶選擇了一個文件時,我們調用CreateFile函數來打開。我們設置標志位來讓該函數的文件能夠讀寫。文件打開後我們把返回的文件句柄保存在一個全局變量中以便以後使用。CreateFile函數應用非常廣泛,其原型如下:
CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD
dwDesiredAccess 指定想要進行的操作。
0 打開文件查詢它的屬性。
GENERIC_READ 打開文件讀
GENERIC_WRITE 打開文件寫.
dwShareMode 指定文件的共享模式。
0 不讓其他進程共享,即當您打開該文件後,其他進程欲打開該文件時將失敗。
FILE_SHARE_READ 允許其他進程讀。
FILE_SHARE_WRITE 允許其他進程寫。
lpSecurityAttributes 該屬性在WIN95下無效。
dwCreationDistribution 指定欲生成的文件在其已存在和未存在時應做的動作。
CREATE_NEW 生成一個新文件。如果文件已存在則失敗。
CREATE_ALWAYS 無論文件是否存在都生成一個新文件。
OPEN_EXISTING 打開存在的文件。如果文件不存在則失敗。
OPEN_ALWAYS 打開文件,如果該文件不存在則生成,這和在dwCreationDistribution 中設置 CREATE_NEW標志位一樣。
TRUNCATE_EXISTING打開文件。打開時該文件的長度裁減到零(也即完全不要原來的文件了)。這要求調用進程必須有GENERIC_WRITE的權利,如果指定的文件不存在,該函數返回失敗。
dwFlagsAndAttributes 指定文件的屬性。
FILE_ATTRIBUTE_ARCHIVE 該文件具有一般的歸檔文件的屬性。用戶可以用該標志位來標記文件的刪除和備份。
FILE_ATTRIBUTE_COMPRESSED 文件或目錄是壓縮的。對於文件來說是壓縮其中的所有數據,而對於目錄來說新生成的子目錄和文件都要壓縮。
FILE_ATTRIBUTE_NORMAL 該文件沒有一般的屬性集。該標志位只能單獨使用。
FILE_ATTRIBUTE_HIDDEN 該文件是隱藏文件,當浏覽一般的文件目錄時將不顯示它。
FILE_ATTRIBUTE_READONLY 該文件是只讀文件。應用程序可以讀其中的內容,但不可以寫。
FILE_ATTRIBUTE_SYSTEM 該文件是系統文件。
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
文件打開後,我們將分配一塊內存供隨後的API 函數ReadFile 和 WriteFile使用。我們使用標志GMEM_MOVEABLE來使得WINDOWS總是把內存塊移到可靠的內存中去,GMEM_ZEROINIT告訴WINDOWS把剛剛分配的內存置為零。如果GlobalAlloc調用成功的話,會在eax中返回內存塊的句柄,我們把該句柄傳給GlobalLock函數以得到指向內存塊的指針。
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
使內存塊可用後,我們調用ReadFile函數從文件中讀數據。對於第一次打開的文件,文件的指針放在偏移0處,像本例中我們從偏移0處往前讀。ReadFile的第一個參數是文件句柄,第二個參數是指向內存塊的指針,接下來的參數是要讀的數據的長度,第四個參數是一個指向DWORD型的參數的指針,它用來存放實際讀的數據的長度。讀完了後,我們把這些內容存放到編輯控件中,這要用消息傳遞來完成,我們把消息WM_SETTEXT傳給編輯控件,其中的參數lParam中包含指向內存塊的指針。到此處,編輯控件就可以在它的客戶區顯示文件的內容了。
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
我們不再需要讓文件打開了,因為我們的目的是把修改後的數據保存到另一個文件而不是先前的那一個文件中去。所以我們可以調用CloseHandle來關閉文件。接下來我們解鎖內存塊,再釋放它。實際上我們可以暫不釋放內存塊,而在以後的操作中重新利用。我們為了演示的原由,選擇了釋放它。
invoke SetFocus,hwndEdit
當打開文件對話框顯示在屏幕上時,輸入的焦點切換到了該對話框上。所以在該對話框關閉後,我們必須把焦點切換到編輯控件上。 現在打開文件的階段結束了,用戶可以編輯他們打開的文件了。當用戶想把修改後的內容保存到磁盤上時,必須選擇File/Save菜單項,這時會顯示一個保存文件對話框。顯示保存文件對話框其實和打開打開文件對話框基本一樣。您甚至可以認為他們的不同只是函數名稱不一樣而已。此處可以復用大多數ofn變量先前設置的成員的值。
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
本例中我們將生成一個新文件,所以一定不能有 OFN_FILEMUSTEXIST 和 OFN_PATHMUSTEXIST標志位。dwCreationDistribution 參數應當有CREATE_NEW標志位。 接下來的代碼和打開問對話框基本一樣。最後調用:
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
現在我們把修改後的數據從編輯控件中寫回內存塊,再從內存塊寫回新文件。