WIN32彙編: 13.內存映射文件

第十三課 內存映射文件


本課中我們將要講解內存映射文件並且演示如何運用它。您將會發現使用內存映射文件是非常簡單的。

理論:

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

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

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

  1. 調用CreateFile打開您想要映射的文件。
  2. 調用CreateFileMapping,其中要求傳入先前CreateFile返回的句柄,該函數生成一個建立在CreateFile函數創建的文件對象基礎上的內存映射對象。
  3. 調用MapViewOfFile函數映射整個文件的一個區域或者整個文件到內存。該函數返回指向映射到內存的第一個字節的指針。
  4. 用該指針來讀寫文件。
  5. 調用UnmapViewOfFile來解除文件映射。
  6. 調用CloseHandle來關閉內存映射文件。注意必須傳入內存映射文件的句柄。
  7. 調用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

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

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