程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 內存映射文件原理及實例

內存映射文件原理及實例

編輯:匯編語言

本課中我們將要講解內存映射文件並且演示如何運用它。您將會發現使用內存映射文件是非常簡單的。
理論:
如果您仔細地研究了前一課的例子, 就會發現它有一個嚴重的缺陷:如果您想讀的內容大於系統分配的內存塊怎麼辦?如果您想搜索的字符串剛好超過內存塊的邊界又該如何處理?對於第一個問題,您也許會說,只要不斷地讀就不解決了嗎。至於第二個問題,您又會說在內存塊的邊界處做一些特別的處理,譬如放上一些標志位就可以了。原理上確實是行得通,但是這隨問題復雜程度加深而顯得非常難以處理。其中的第二個問題是有名的邊界判斷問題,程序中許許多多的錯誤都是由此引起。想一想,如果我們能夠分配一個能夠容納整個文件的大內存塊該多好啊,這樣這兩個問題不都迎刃而解了嗎?是的,WIN32的內存映射文件確實允許我們分配一個裝得下現實中可能存在的足夠大的文件的內存。

利用內存映射文件您可以認為操作系統已經為您把文件全部裝入了內存,然後您只要移動文件指針進行讀寫即可了。這樣您甚至不需要調用那些分配、釋放內存塊和文件輸入/輸出的API函數,另外您可以把這用作不同的進程之間共享數據的一種辦法。運用內存映射文件實際上沒有涉及實際的文件操作,它更象為每個進程保留一個看得見的內存空間。至於把內存映射文件當成進程間共享數據的辦法來用,則要加倍小心,因為您不得不處理數據的同步問題,否則您的應用程序也許很可能得到過時或錯誤的數據甚至崩潰。本課中我們將主要講述內存映射文件,將不涉及進程間的同步。WIN32中的內存映射文件應用非常廣泛,譬如:即使是系統的核心模塊---PE格式文件裝載器也用到了內存映射文件,因為PE格式的文件並不是一次性加載到內存中來的,譬如他它在首次加載時只加載必需加載的部分,而其他部分在用到時再加載,這正好可以利用到內存映射文件的長處。實際中的大多數文件存取都和PE加載器類似,所以您在處理該類問題時也應該充分利用內存映射文件。

內存映射文件本身還是有一些局限性的,譬如一旦您生成了一個內存映射文件,那麼您在那個會話期間是不能夠改變它的大小的。所以內存映射文件對於只讀文件和不會影響其大小的文件操作是非常有用的。當然這並不意味著對於會引起改變其大小的文件操作就一定不能用內存影射文件的方法,您可以事先估計操作後的文件的可能大小,然後生成這麼大小一塊的內存映射文件,然後文件的長度就可以增長到這麼一個大小。 我們的解釋夠多的了,接下來我們就看看實現的細節:

調用CreateFile打開您想要映射的文件。
調用CreateFileMapping,其中要求傳入先前CreateFile返回的句柄,該函數生成一個建立在CreateFile函數創建的文件對象基礎上的內存映射對象。
調用MapViewOfFile函數映射整個文件的一個區域或者整個文件到內存。該函數返回指向映射到內存的第一個字節的指針。
用該指針來讀寫文件。
調用UnmapViewOfFile來解除文件映射。
調用CloseHandle來關閉內存映射文件。注意必須傳入內存映射文件的句柄。
調用CloseHandle來關閉文件。注意必須傳入由CreateFile創建的文件的句柄。
例子:
下面的例子允許用戶通過“打開文件”對話框來打開一個文件,然後用內存映射文件來打開該文件,如果成功,窗口的標題條會顯示打開的文件的名稱,您可以通過選擇“File/Save”菜單項來把換名保存。該程序將會把打開的文件的內容存到新文件中去。注意,這整個過程您根本就沒有用到GlobalAlloc這樣的分配內存的函數。
.386
.model flat,stdcall
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

.data
ClassName db "Win32ASMFileMappingClass",0
AppName db "Win32 ASM File Mapping Example",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)
hMapFile HANDLE 0 ; Handle to the memory mapped file, must be
;initialized with 0 because we also use it as
;a flag in WM_DESTROY section too

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hFileRead HANDLE ? ; Handle to the source file
hFileWrite HANDLE ? ; Handle to the output file
hMenu HANDLE ?
pMemory DWORD ? ; pointer to the data in the source file
SizeWritten DWORD ? ; number of bytes actually written by WriteFile

.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:DWORD
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 hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke GetMenu,hWnd ;Obtain the menu handle
mov hMenu,eax
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_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
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 ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFileRead,eax
invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL
mov hMapFile,eax
mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
.endif
.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 hFileWrite,eax
invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax
invoke GetFileSize,hFileRead,NULL
invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL
invoke UnmapViewOfFile,pMemory
call CloseMapFile
invoke CloseHandle,hFileWrite
invoke SetWindowText,hWnd,ADDR AppName
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED
.endif
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

end start

分析:
invoke CreateFile,ADDR buffer,\
GENERIC_READ ,\
0,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
當用戶選擇打開文件時,我們調用CreateFile來打開。注意我們指定GENERIC_READ(一般的讀)來表示我們打開的文件只能夠讀出,把dwShareMode設成0,表示我們不想其他進程在我們操作文件時來存取該文件。

invoke CreateFileMapping,hFileRead,NULL,PAGE_READONLY,0,0,NULL

我們調用CreateFileMapping來在打開的文件的基礎上生成內存映射文件。CreateFileMapping的語法如下:

CreateFileMapping proto hFile:DWORD,\
lpFileMappingAttributes:DWORD,\
flProtect:DWORD,\
dwMaximumSizeHigh:DWORD,\
dwMaximumSizeLow:DWORD,\
lpName:DWORD

您應當知道該函數並沒有必要把整個文件映射到內存中去,您可以用該函數來只映射文件的一部分。您可以在參數dwMaximumSizeHigh和dwMaximumSizeLow中指定內存映射文件的大小,如果您指定的值大於實際的文件,則實際的文件將增長到指定的大小,如果想要映射的內存大小正好和文件的實際大小相等,則把兩個參數中都設成為0。您可以設定lpFileMappingAttributes為NULL,讓WINDOWS賦予該內存映射文件於缺省的安全屬性。
flProtect定義了內存映射文件的保護屬性,我們指定它為PAGE_READONLY來規定該內存映射文件只能夠讀。注意該屬性不能和CreateFile中指定的屬性相矛盾,否則就不能生成內存映射文件。
lpName指定內存映射文件的名稱,如果您想要該內存映射文件同時可以供其它的進程使用,就必須給它取個名稱。不過在我們的例子中,只有我們的進程使用該內存映射文件故我們忽略該參數。

mov eax,OFFSET buffer
movzx edx,ofn.nFileOffset
add eax,edx
invoke SetWindowText,hWnd,eax

如果函數CreateFileMapping調用成功,我們把窗口的標題條換成被打開文件的名稱。保存在緩沖區中的文件名是帶有路徑的全文件名,所以為了只顯示文件名我們需要利用OPENFILENAME結構體中的成員nFileOffset的值來找到文件名的起始地址。

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED

為了避免用戶一次性打開多個文件,我們讓“打開文件”菜單項呈灰色顯示,使得打開文件的菜單項失效。函數EnableMenuItem可以用來改變菜單項的屬性。 之後用戶可能保存文件或者直接關閉應用程序。如果用戶選擇關閉應用程序,則事先必須關閉內存映射文件和打開的文件, 代碼如下:

.ELSEIF uMsg==WM_DESTROY
.if hMapFile!=0
call CloseMapFile
.endif
invoke PostQuitMessage,NULL

在上面的代碼段中,當WINDOWS的消息處理過程接收到WM_DESTROY消息後,它首先檢測hMapFile值是否為0。如果不為0則表示相關的文件未關閉,這樣就需要調用CloseMapFile來關閉它們。

CloseMapFile PROC
invoke CloseHandle,hMapFile
mov hMapFile,0
invoke CloseHandle,hFileRead
ret
CloseMapFile endp

上述過程調用是用來關閉內存映射文件和原來打開的文件的,這樣可以使得程序退出時沒有資源洩漏。如果用戶選擇保存文件的話,就彈出一個“保存文件”對話框,當用戶輸入了新文件的名稱後,我們調用CreateFile函數來創建新文件---輸出文件。

invoke MapViewOfFile,hMapFile,FILE_MAP_READ,0,0,0
mov pMemory,eax

在輸出文件創建後我們調用MapViewOfFile來映射希望映射到內存中的部分。該函數的語法如下:

MapViewOfFile proto hFileMappingObject:DWORD,\
dwDesiredAccess:DWORD,\
dwFileOffsetHigh:DWORD,\
dwFileOffsetLow:DWORD,\
dwNumberOfBytesToMap:DWORD

dwDesiredAccess用來指定我們想對文件進行的操作。在我們例子中,我們只想讀,故指定標志FILE_MAP_READ。
dwFileOffsetHigh 和 dwFileOffsetLow 用來指定打開文件中欲映射的起始偏移位置。我們的例子中想映射整個的文件,故指定它們的值為0。
dwNumberOfBytesToMap 用來指定欲映射的字節數,如果想映射整個的文件,設定該值為0。
調用MapViewOfFile後,我們希望的部分就已經映射到內存中去了。您將得到一個指向起始內存塊的指針。

invoke GetFileSize,hFileRead,NULL

調用該函數可以得到文件的大小,其值通過eax傳送,如果文件的長度超過4G,那麼文件長度DWORD的高值部分(也即超過4G的部分)保存在FileSizeHighWord中。因為我們估計一般的文件將沒有這麼大,故忽略該值。

invoke WriteFile,hFileWrite,pMemory,eax,ADDR SizeWritten,NULL

把內存映射文件中的數據寫到輸出文件中去。

invoke UnmapViewOfFile,pMemory

寫完後,我們解除映射。

call CloseMapFile
invoke CloseHandle,hFileWrite

關閉內存映射文件和輸出文件的句柄。

invoke SetWindowText,hWnd,ADDR AppName

恢復窗口的標題條到應用程序的名稱。

invoke EnableMenuItem,hMenu,IDM_OPEN,MF_ENABLED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_GRAYED

恢復“打開文件”和“保存文件”菜單項使的可以重新開始新的打開、編輯和保存循環。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved