Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1758932
  • 博文数量: 290
  • 博客积分: 10653
  • 博客等级: 上将
  • 技术积分: 3178
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-24 23:08
文章存档

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类:

2007-12-30 23:07:34

Tutorial 13: Memory Mapped Files

第十三课:内存映射文件


I'll show you what memory mapped files are and how to use them to your advantages. Using a memory mapped file is quite easy as you'll see in this tutorial.

我将向你说明什么是内存映射文件以及你们如何使用它们来获得优势。在这一课中,你将发现使用内存映象文件是非常简单的。

Download the example here.

Theory:

原理:

If you examine the example in the previous tutorial closely, you'll find that it has a serious shortcoming: what if the file you want to read is larger than the allocated memory block? or what if the string you want to search for is cut off in half at the end of the memory block? The traditional answer for the first question is that you should repeatedly read in the data from the file until the end of file is encountered. The answer to the second question is that you should prepare for the special case at the end of the memory block. This is called a boundary value problem. It presents headaches to programmers and causes innumerable bugs.

如果你仔细分析前一课中的例子,你将发现它有一个严重的缺点:如果你想读的文件比已分配的内存块大得多,怎么办?或者是你想查找的字串在内存块的结尾处一分为二,又如何解决。对于第一个问题,传统的答案是:你应该重复的从文件中读进数据直到文件结束为止。而对于第二个问题的答案是:你应该在内存块的边界处做一些处理。这是调用一个边界值的问题。它对程序员来说是非常头痛的并且导致程序中存在无数缺陷(bug臭虫 ,有软件臭虫和硬件臭虫之分)的原因。
It would be nice if we can allocate a very large block of memory, enough to store the whole file but our program would be a resource hog. File mapping to the rescue. By using file mapping, you can think of the whole file as being already loaded into memory and you can use a memory pointer to read or write data from the file. As easy as that. No need to use memory API functions and separate File I/O API functions anymore, they are one and the same under file mapping. File mapping is also used as a means to share data between processes. Using file mapping in this way, there's no actual file involved. It's more like a reserved memory block that every process can *see*. But sharing data between processes is a delicate subject, not to be treated lightly. You have to implement process and thread synchronization else your applications will crash in very short order.

如果我们能分配一块非常大的内存,大得足够存储整个文件,那是多么美好的事。但是那样我们的程序就成了一头贪吃资源的肥猪。文件映射拯救了它。(gog?)

通过使用文件映射,你可以认为整个文件都已经装进内存并且你能用一个内存指针从文件中读写数据。这样您甚至不需要调用那些分配、释放内存块和文件输入/输出的API函数,它们在文件映射中是一样的。文件映射也同样作为进程之间共享数据的一种方法。使用文件映射这种方法是非常复杂的。文件映象并没有包括实际的文件,它更像是为每个进程保留一个能见(* see*!!) 的内存块。但是在进程之间共享数据是一个棘手的主题,并不是轻轻松松就能够处理的。你必须实现进程和线程的同步, 否则 你的应用程序将在很短的时间内崩溃。(进程的不同步执行导致程序的不可再现性,即每次运行程序都将得到一个不同的结果)

 

We will not touch the subject of file mapping as a means to create a shared memory region in this tutorial. We'll concentrate on how to use file mapping as a means to "map" a file into memory. In fact, the PE loader uses file mapping to load executable files into memory. It is very convenient since only the necessary portions can be selectively read from the file on the disk. Under Win32, you should use file mapping as much as possible.

在这课中,我们讲述的内容将不涉及这种为进程同步而创建一内存共享区的方法。我们将把精力集中在 如何把一个文件映射在内存中 的 这种文件映射方法。

实际上,PE文件的装入程序就是用文件映射的方法来把可执行文件装入内存中。

它是非常方便的 因为 仅仅是必须的那一部分(保证程序能执行的数据和代码) 能被从磁盘上的文件中读进内存。(而文件的其它的部分只有在缺页的时候才被调入。)在win32中,你应该尽可能的用文件映射方法。

 There are some limitation to file mapping though. Once you create a memory mapped file, its size cannot be changed during that session. So file mapping is great for read-only files or file operations that don't affect the file size. That doesn't mean that you cannot use file mapping if you want to increase the file size. You can estimate the new size and create the memory mapped file based on the new size and the file will grow to that size. It's just inconvenient, that's all.

尽管文件映射方法是有一些限制的。一旦你创建了一个内存映射文件,它的大小在会话区间就不能被够改变的。所以文件映射方法最大的用处就是对于只读文件或是文件操作并不会改变文件大小的文件中。但是这并不意味着你不能用文件映射方法在那些你想增加大小的文件中。你能估计新的文件大小并且基于这个新的文件大小创建内存映射文件。这样文件的大小仍然在你的控制中。它仅仅是不方便的而已。

Enough for the explanation. Let's dive into implementation of file mapping. In order to use file mapping, these steps must be performed:

我已经解释的足够多了。让我们来钻研文件映射方法的实现。

为了用文件映射方法,下面这些步骤必须完成:

  1. call CreateFile to open the file you want to map.

调用CreateFile 打开你想映射的文件。

  1. call CreateFileMapping with the file handle returned by CreateFile as one of its parameter. This function creates a file mapping object from the file opened by CreateFile.

传递CreateFile返回的文件句柄 作为CreateFileMapping函数的一个参数并调用它。这个函数从被CreateFile函数打开的文件中创建一个文件映射对象。

  1. call MapViewOfFile to map a selected file region or the whole file to memory. This function returns a pointer to the first byte of the mapped file region.

调用MapViewOfFile来映射一个被选定的文件区域或是整个文件到内存中。这个函数的返回值是一个指针,这个指针指向映射文件区域的第一个字节。

  1. Use the pointer to read or write the file

用这个指针读或写这个文件

  1. call UnmapViewOfFile to unmap the file.

调用UnmapViewOfFile来解除文件映射。

 

  1. call CloseHandle with the handle to the mapped file as the parameter to close the mapped file.

将映射文件的句柄作为CloseHandle函数的参数并调用它来关闭内存映射文件。

 

  1. call CloseHandle again this time with the file handle returned by CreateFile to close the actual file.

再一次传递CreateFile返回的文件句柄给CloseHandle函数并调用它来关闭实际的文件。

Example:

例子:

The program listed below lets you open a file via an open file dialog box. It opens the file using file mapping, if it's successful, the window caption is changed to the name of the opened file. You can save the file in another name by select File/Save as menuitem. The program will copy the whole content of the opened file to the new file. Note that you don't have to call GlobalAlloc to allocate a memory block in this program.

 

下面的程序列表让你通过一个打开文件对话框打开一个文件。它用文件映射方法打开这个文件,如果它是成功的,窗口的标题将变成被打开文件的名称。你能选择文件/保存这个菜单项来把文件用另一个名字保存。这个程序将复制被打开文件的所有内容到新文件中。注意:在这个程序中,你不能用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
 

Analysis:

分析:

                    invoke CreateFile,ADDR buffer,\
                                                GENERIC_READ ,\
                                                0,\
                                                NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
                                                NULL

When the user selects a file in the open file dialog, we call CreateFile to open it. Note that we specify GENERIC_READ to open this file for read-only access and dwShareMode is zero because we don't want some other process to modify the file during our operation.

当用户在打开文件对话框中选择一个文件时,我们调用CreateFile来打开它。注意:为了以只读方式打开这个文件我们指定了GENERIC_READ标志字并且将dwShareMode设为0,因为我们不想其它进程在我们操作区间修改这个文件.

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

Then we call CreateFileMapping to create a memory mapped file from the opened file. CreateFileMapping has the following syntax:

然后我们调用FileMapping从被打开文件创建一个内存映射文件CreateFileMapping句法如下: 

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

You should know first that CreateFileMapping doesn't have to map the whole file to memory. You can use this function to map only a part of the actual file to memory. You specify the size of the memory mapped file in dwMaximumSizeHigh and dwMaximumSizeLow params. If you specify the size that 's larger than the actual file, the actual file will be expanded to the new size. If you want the memory mapped file to be the same size as the actual file, put zeroes in both params.

首先,你应该知道CreateFileMapping函数并不是把整个文件都映射到内存中.你能用这个函数来映射实际文件的一部分到内存中.你可以在dwMaximumSizehighdwMaximumSizeLow参数中指定内存映射文件的大小.如果指定了大小大于实际的文件大小,那么实际文件的大小将被扩充到新的大小.如果你想内存映射文件和实际的文件有相同的大小,请将两个参数的值设为零.


You can use NULL in lpFileMappingAttributes parameter to have Windows creates a memory mapped file with default security attributes.

你能用NULL填充lpFileMappingAttributes参数来让windows创建一个有默认安全属性的内存映射文件.


flProtect defines the protection desired for the memory mapped file. In our example, we use PAGE_READONLY to allow only read operation on the memory mapped file. Note that this attribute must not contradict the attribute used in CreateFile else CreateFileMapping will fail.

FlProtect为内存映射文件定义了保护模式.在我们的例子中,我们用PAGE_READONLY来允许在内存映射文件上执行读操作.注意:这个属性不能和用在CreateFile函数中的属性值相抵触否则CreateFileMapping将失效。


lpName points to the name of the memory mapped file. If you want to share this file with other process, you must provide it a name. But in our example, our process is the only one that uses this file so we ignore this parameter.

LpName指向内存映射文件的名称。如果你想和其它进程共享这个文件,你必须提供它的名字。但是在我们的例子中,我们只有一个进程用于这个文件所以我们可以不理睬这个参数。

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

If CreateFileMapping is successful, we change the window caption to the name of the opened file. The filename with full path is stored in buffer, we want to display only the filename in the caption so we must add the value of nFileOffset member of the OPENFILENAME structure to the address of buffer.

如果CreateFielMapping是成功的,我们改变窗口标题为打开文件的名字。文件名和全路径被存储在缓冲区中。我们只是在标题栏中显示文件名所以我们必须增加OPENFILENAME结构的nFileOffset成员的值来指定缓冲区的地址。(为了找到文件名)

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

As a precaution, we don't want the user to open multiple files at once, so we gray out the Open menu item and enable the Save menu item. EnableMenuItem is used to change the attribute of menu item.
After this, we wait for the user to select File/Save as menu item or close our program. If the user chooses to close our program, we must close the memory mapped file and the actual file like the code below:

作为一个预防,我们不想用户一次打开多个文件,所以我们让打开菜单项呈现灰色并且让保存菜单项正常使用。EnableMenuItem被用于改变一个菜单项的属性。在这之后,我们等待用户选择文件/保存菜单项或是关闭我们的程序。如果用户选择关闭我们的应用程序,我们必须关闭内存映射文件和实际文件,代码如下所示: 

 

 

 

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

In the above code snippet, when the window procedure receives the WM_DESTROY message, it checks the value of hMapFile first whether it is zero or not. If it's not zero, it calls CloseMapFile function which contains the following code:

上面的这些代码片断,当窗口过程接受到WM_DESTROY消息时,它首先检查hMapFile的值看看它是否为零。如果它不为零,它调用CloseMapFile函数,它包含的代码如下:

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

CloseMapFile closes the memory mapped file and the actual file so that there 'll be no resource leakage when our program exits to Windows.
If the user chooses to save that data to another file, the program presents him with a save as dialog box. After he types in the name of the new file, the file is created by CreateFile function.

CloseMapFile关闭内存映射文件和实际的文件这样当我们程序退出windows时这里将不会有资源泄露。如果用户选择了保存数据到另外一个文件中,程序呈现一个 保存文件对话框给用户。在他输入了新的文件名之后,这个文件被CreateFile 函数创建。

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

Immediately after the output file is created, we call MapViewOfFile to map the desired portion of the memory mapped file into memory. This function has the following syntax:

在输出文件被创建后,我们立即调用MapViewOfFile来映射我们想得到的内存映射文件的一部分到内存中。这个函数的句法如下:

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

dwDesiredAccess specifies what operation we want to do to the file. In our example, we want to read the data only so we use FILE_MAP_READ.

dwDwsiredAccess 指定我们想要这个文件执行什么样的操作。在我们的例子中,我们仅仅是读文件的数据所以我们用FILE_MAP_READ


dwFileOffsetHigh and dwFileOffsetLowspecify the starting file offset of the file portion that you want to map into memory. In our case, we want to read in the whole file so we start mapping from offset 0 onwards.

DwFileOffsetHighdwFileOffsetLow指定你想映射进内存中的那一部分文件的文件偏移量. 在我们的例子中,我们想读整个文件所以我们从偏移量为0的地方向前地开始映射.


dwNumberOfBytesToMap specifies the number of bytes to map into memory. If you want to map the whole file (specified by CreateFileMapping), pass 0 to MapViewOfFile.

 After calling MapViewOfFile, the desired portion is loaded into memory. You'll be given the pointer to the memory block that contains the data from the file.

DwNumberOfBytesToMap指定映射进内存中的字节数.如果你想映射整个文件( 通过CreateFileMapping函数指定),传递0MapViewOfFile

在调用了MapViewFile之后,我们想要的部分已经被装入内存.你将得到一个指针,这个指针指向一个内存块,内存块包含文件中的数据

                    invoke GetFileSize,hFileRead,NULL

Find out how large the file is. The file size is returned in eax. If the file is larger than 4 GB,  the high DWORD of the file size is stored in FileSizeHighWord. Since we don't expect to handle such large file, we can ignore it.

查明文件的大小.文件的大小被返回在eax.如果文件大于4GB,文件大小( dword) 的高值( 超过出的那一部分,因为32位数最大值是4GB) 存放在FileSizeHighWord.因为我们并不希望处理这么大的文件,所以我们可以忽略它。

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

Write the data that is mapped into memory into the output file.

把映射到内存中数据( 影射文件的数据)写到输出文件中去。

                    invoke UnmapViewOfFile,pMemory

When we're through with the input file, unmap it from memory.

当我们完成文件的输入,从内存中撤消它(映射文件)

                    call   CloseMapFile
                    invoke CloseHandle,hFileWrite

And close all the files.

关闭所有的文件。

                    invoke SetWindowText,hWnd,ADDR AppName

Restore the original caption text.

恢复原来的标题文本

 

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

Enable the Open menu item and gray out the Save As menu item.

恢复打开菜单项的正常操作并让另存为菜单项呈现灰色。


This article come from Iczelion's asm page,

风向改变翻译于2007-12-30 日 

 

 

阅读(2408) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~