Fasm -- Win32汇编学习--15
课中我们将要讲解内存映射文件并且演示如何运用它。您将会发现使用内存映射文件是非常简单的。
如果您仔细研究了前一节课的例子,就会发现一个严重的缺陷:如果您想读取的内容大于系统分配的内存块该怎么办? 如果您想搜索的字符串刚好超过内存块的边界又该如何处理? 对于第一个问题,您也许会说,只要不断地的读不久解决了吗? 至于第二个问题,您又会说在内存块的边界处做一些特别的处理,例如放上一些标志就可以了。 原理上确实可以行的通,但是随着问题的复杂程度加深而显得非常难以处理。 其中第二个问题是有名的边界判断问题,程序中许许多多的错误都是由此引起。想一想,如果我们能够分配一个能够容纳整个文件的大内存块该多好啊,这样两个问题不都迎刃而解了吗? 是的,Win32的内存映射文件确实允许我们分配一个装的下现实中可能存在的足够大的文件的内存。
(译者注: 也就是说我们可以通过内存映射的机制将整个文件的大小,直接翻到用户地址空间中,我们不需要去考虑这个文件多大,或者分配的内存块多大,操作系统会帮我们完成这些事情,并且并不是说我们只能映射整个文件到用户地址空间,我们还可以映射部分等,)
利用内存映射文件您可以认为操作系统已经把文件全部装入了内存(译者注:其实本身就是操作系统将文件全部映射到我们的用户地址空间上),此时已经映射到我们的地址空间商了,我们只要操作相应返回的内存块的指针我们就可以在这些空间商修改任意数据,嘿嘿。 当然前提是你在创建文件映射对象时候要设置可读可写的属性。(译者注:米办法,windows 利用系统空间的特性,以保护这些对象的安全性)。这样您甚至不需要调用那些分配、释放内存块、和文件输入/输出的api函数,另外你可以把这用作不同进程共享数据的一种方法。运用内存映射文件实际上并没有进行实际的文件操作,它更像为每个进程保留一个看的见的内存空间。至于把文件映射文件当成进程间共享数据的办法来用,则要“加倍小心”。因为你不得不处理数据的同步问题,否则你得到的将是过时的或者是错误的数据甚至崩溃。本课中我们将主要讲述内存映射文件,将不设计进程间额度同步。Win32的内存映射文件使用非常广泛,譬如即使是系统的核心模块----pe格式文件装载器也用到了内存映射文件,因为pe格式的文件并不是一次性加载到内存来的,譬如它在首次加载时只加载必须加载的部分,而其他部分在用到时在加载,这正好用到了内存映射文件的长处。实际商大多数文件存取都和PE加载器类似,所以您在处理该类文件也应该充分利用内存映射文件。
内存映射文件本身也有局限性,譬如你一旦生成了一个内存映射文件,所以您在那个会话期间是不能修改其大小的。所以内存映射文件对于只读文件和不会影响其大小的文件操作是非常有用的。当然这并不意味着会引起文件大小的文件操作就不能用内存映射文件,您可以事先估计一下操作后生成的文件大小,然后生成这么大一块的内存映射文件,然后文件的长度就可以增加到这么一个大小。我们解释的够多了,接下来看看实现的细节。
1.调用CreateFile打开您想要映射的文件。
2.调用CreateFileMapping,其中要求传入先前CreateFile返回的句柄,该函数生成一个建立在CreateFile函数创建的文件对象基础上的内存映射对象。
3.调用MapViewOfFile函数映射整个文件的一个区域或者整个文件到内存。该函数返回指向映射到内存的第一个字节的指针,用该指针来读写文件。
4.调用UnmapViewOfFile来解除文件映射。
5.调用CloseHandle来关闭内存映射文件。注意必须传入内存映射文件的句柄。
6.调用CloseHandle来关闭文件。注意必须传入由CreateFile创建的文件的句柄。
例子:
下面的例子允许用户通过“打开文件”对话框来打开一个文件,然后用内存映射文件来打开该文件,如果成功,窗口的标题条会显示打开的文件的名称,您可以通过选择“File/Save”菜单项来把换名保存。该程序将会把打开的文件的内容存到新文件中去。注意,这整个过程您根本就没有用到GlobalAlloc这样的分配内存的函数。
format PE GUI 4.0
include 'win32ax.inc'
IDM_OPEN equ 1000
IDM_SAVE equ 1001
IDM_EXIT equ 1002
EDITID equ 1003
.data
hInstance rd 1
hEdit rd 1
hFile rd 1
hFileWrite rd 1
hMapFile rd 1
hMenu rd 1
lpMemory rd 1
lpSizeWrite rd 1
szClassName db 'xfish', 0
szWndName db '偶的程序', 0
szEditName db 'edit', 0
lpFilterName db 'all file (*.*)', 0, '*.*', 0, 0
szBuffer db 100 dup (?)
lpOfn OPENFILENAME <>
.code
entry $
invoke GetModuleHandle, NULL
mov [hInstance], eax
stdcall _WinMain, [hInstance], NULL, NULL, NULL
invoke ExitProcess, NULL
proc _WinMain hInstance:DWORD, hPrevInstance:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
local @wc:WNDCLASS
local @msg:MSG
local @hWin:DWORD
; +-------------------------------------------+
; | Register Class |
; +-------------------------------------------+
invoke RtlZeroMemory, addr @wc, sizeof.WNDCLASS
mov [@wc.style], CS_VREDRAW or CS_HREDRAW
mov [@wc.lpfnWndProc], _WndProc
memmov @wc.hInstance, hInstance
mov [@wc.hbrBackground], COLOR_WINDOW + 1
mov [@wc.lpszMenuName], 30
mov [@wc.lpszClassName], szClassName
invoke LoadIcon, NULL, IDI_ASTERISK
mov [@wc.hIcon], eax
invoke LoadCursor, NULL, IDC_ARROW
mov [@wc.hCursor], eax
invoke RegisterClass, addr @wc
; +------------------------------------------+
; | Create Window |
; +------------------------------------------+
invoke CreateWindowEx,/
NULL,/
szClassName,/
szWndName,/
WS_OVERLAPPEDWINDOW,/
CW_USEDEFAULT,/
CW_USEDEFAULT,/
300,/
200,/
NULL,/
NULL,/
[hInstance],/
NULL
mov [@hWin], eax
invoke ShowWindow, [@hWin], SW_SHOWNORMAL
invoke UpdateWindow, [@hWin]
; +-----------------------------------------+
; | Msg Loop |
; +-----------------------------------------+
msgloop:
invoke GetMessage, addr @msg, NULL, NULL, NULL
or eax, eax
je msgend
invoke TranslateMessage, addr @msg
invoke DispatchMessage, addr @msg
jmp msgloop
msgend:
mov eax, [@msg.wParam]
ret
endp
proc _WndProc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
cmp [uMsg], WM_CREATE
je wndcreate
cmp [uMsg], WM_SIZE
je wndsize
cmp [uMsg], WM_COMMAND
je menucmd
cmp [uMsg], WM_DESTROY
je wndend
invoke DefWindowProc, [hWnd], [uMsg], [wParam], [lParam]
ret
wndcreate:
; +-------------------------------------+
; | 创建编辑框 |
; +-------------------------------------+
invoke CreateWindowEx,/
NULL,/
szEditName,/
NULL,/
WS_CHILD or WS_VISIBLE or WS_HSCROLL or/
WS_VSCROLL or ES_MULTILINE or ES_LEFT,/
0, 0, 0, 0,/
[hWnd],/
EDITID,/
[hInstance],/
NULL
mov [hEdit], eax
invoke SetFocus, [hEdit]
invoke GetMenu, [hWnd]
mov [hMenu], eax
; +-------------------------------------+
; | 初始化lpofn |
; +-------------------------------------+
mov [lpOfn.lStructSize], sizeof.OPENFILENAME
memmov lpOfn.hInstance, hInstance
mov [lpOfn.lpstrFilter], lpFilterName
mov [lpOfn.lpstrFile], szBuffer
mov [lpOfn.nMaxFile], 100
jmp quit
wndsize:
push edx
mov eax, [lParam]
movzx edx, ax ;edx = 宽
shr eax, 16 ;eax = 高
invoke MoveWindow, [hEdit], 0, 0, edx, eax, TRUE
invoke GetLastError
pop edx
jmp quit
menucmd:
mov eax, [wParam]
cmp ax, IDM_OPEN
je menuopen
cmp ax, IDM_SAVE
je menusave
cmp ax, IDM_EXIT
je menuexit
jmp quit
menuopen:
mov [lpOfn.Flags], OFN_EXPLORER or OFN_FILEMUSTEXIST or /
OFN_PATHMUSTEXIST or OFN_READONLY
invoke GetOpenFileName, lpOfn
or eax, eax
je quit
invoke CreateFile,/
szBuffer,/
GENERIC_READ,/
NULL,/
NULL,/
OPEN_EXISTING,/
FILE_ATTRIBUTE_ARCHIVE,/
NULL
test eax, -1
je quit
mov [hFile], eax
invoke CreateFileMapping,/
[hFile],/
NULL,/
PAGE_READONLY,/
0,0,NULL
mov [hMapFile], eax
push edx
mov edx, szBuffer
movzx eax, [lpOfn.nFileOffset]
add eax, edx
invoke SetWindowText, [hWnd], eax
pop edx
invoke EnableMenuItem, [hMenu], IDM_OPEN, MF_GRAYED
invoke EnableMenuItem, [hMenu], IDM_SAVE, MF_ENABLED
jmp quit
menusave:
mov [lpOfn.Flags], OFN_EXPLORER or OFN_LONGNAMES or OFN_HIDEREADONLY
invoke GetSaveFileName, lpOfn
or eax, eax
je quit
invoke CreateFile,/
szBuffer,/
GENERIC_WRITE or GENERIC_READ,/
FILE_SHARE_READ or FILE_SHARE_WRITE,/
NULL,/
CREATE_NEW,/
FILE_ATTRIBUTE_ARCHIVE,/
NULL
test eax, -1
je menuerror
mov [hFileWrite], eax
invoke MapViewOfFile,/
[hMapFile],/
FILE_MAP_READ,/
NULL,/
NULL,/
0
or eax, eax
je quit
mov [lpMemory], eax
invoke GetFileSize, [hFile], NULL
invoke WriteFile,/
[hFileWrite],/
[lpMemory],/
eax,/
lpSizeWrite,/
NULL
invoke UnmapViewOfFile, [lpMemory]
invoke CloseHandle, [hFileWrite]
call _CloseHandle
invoke SetWindowText, [hWnd], szWndName
invoke EnableMenuItem, [hMenu], IDM_OPEN, MF_ENABLED
invoke EnableMenuItem, [hMenu], IDM_SAVE, MF_GRAYED
jmp quit
menuerror:
invoke MessageBox, NULL, '失败', 'test', MB_OK
jmp quit
testhandle:
call _CloseHandle
jmp quitmessage
menuexit:
wndend:
cmp [hMapFile], 0
jnz testhandle
quitmessage:
invoke PostQuitMessage, 0
quit:
xor eax, eax
ret
endp
proc _CloseHandle
invoke CloseHandle, [hMapFile]
mov [hMapFile], 0
invoke CloseHandle, [hFile]
ret
endp
.import
library kernel32, 'kernel32.dll',/
user32, 'user32.dll',/
comdlg32, 'comdlg32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
include 'api/comdlg32.inc'
section '.rsrc' data readable resource
include 'string.fnt'
directory RT_MENU, Menu
resource Menu, 30,/
LANG_NEUTRAL, MenuMain
menu MenuMain
menuitem str_file_chs, 0, MFR_POPUP + MFR_END
menuitem str_open_chs, 1000, 0
menuitem str_save_chs, 1001, 0
menuseparator
menuitem str_exit_chs, 1002, MFR_END
分析:
invoke CreateFile,/
szBuffer,/
GENERIC_READ,/
NULL,/
NULL,/
OPEN_EXISTING,/
FILE_ATTRIBUTE_ARCHIVE,/
NULL
当用户选择一个文件的时候,我们调用CreateFile函数来打开,注意我们指定GENERIC_READ(一般的读)来表示我们打开的文件只能够读出,把dwShareMode设成0,表示我们不想其他进程在我们操作文件时来存取该文件。
invoke CreateFileMapping,/
[hFile],/
NULL,/
PAGE_READONLY,/
0,0,NULL
mov [hMapFile], eax
然后我们调用CreateFileMapping来创建一个文件映射对象,因为我们windows的各种资源都是以对象的形式来呈现给我们的编程人员的,操作每个对象之前我们必须取得对象的句柄。(我的想法:句柄就是这些对象数据块在系统地址空间的索引值,我们创建一个对象,windows默认在系统空间创建其对象,这些对象拥有一些安全属性等等,这样的好处是为了保护这些对象的安全,以及可以共享其对象)。我们创建一个文件映射对象后,返回的是这个对象的句柄,而这个对象在系统空间创立,并且写入了相关的属性,例如什么权限,属性等。
您应当知道该函数并没有必要把整个文件映射到内存中去,您可以用该函数来只映射文件的一部分。您可以在参数dwMaximumSizeHigh和dwMaximumSizeLow中指定内存映射文件的大小,如果您指定的值大于实际的文件,则实际的文件将增长到指定的大小,如果想要映射的内存大小正好和文件的实际大小相等,则把两个参数中都设成为0。您可以设定lpFileMappingAttributes为NULL,让WINDOWS赋予该内存映射文件于缺省的安全属性。
flProtect定义了内存映射文件的保护属性,我们指定它为PAGE_READONLY来规定该内存映射文件只能够读。注意该属性不能和CreateFile中指定的属性相矛盾,否则就不能生成内存映射文件。
lpName指定内存映射文件的名称,如果您想要该内存映射文件同时可以供其它的进程使用,就必须给它取个名称。不过在我们的例子中,只有我们的进程使用该内存映射文件故我们忽略该参数。
push edx
mov edx, szBuffer
movzx eax, [lpOfn.nFileOffset]
add eax, edx
invoke SetWindowText, [hWnd], eax
pop edx
这句指令是用来将我们窗口的菜单栏的标题设置为我们打开的文件的名称。如果函数CreateFileMapping调用成功,我们把窗口的标题条换成被打开文件的名称。保存在缓冲区中的文件名是带有路径的全文件名,所以为了只显示文件名我们需要利用OPENFILENAME结构体中的成员nFileOffset的值来找到文件名的起始地址。
invoke EnableMenuItem,hMenu,IDM_OPEN,MF_GRAYED
invoke EnableMenuItem,hMenu,IDM_SAVE,MF_ENABLED
为了避免用户一次性打开多个文件,我们让“打开文件”菜单项呈灰色显示,使得打开文件的菜单项失效。函数EnableMenuItem可以用来改变菜单项的属性。 之后用户可能保存文件或者直接关闭应用程序。如果用户选择关闭应用程序,则事先必须关闭内存映射文件和打开的文件, 代码如下:
cmp [hMapFile], 0
jnz testhandle
testhandle:
call _CloseHandle
jmp quitmessage
在上面的代码段中,当WINDOWS的消息处理过程接收到WM_DESTROY消息后,它首先检测hMapFile值是否为0。如果不为0则表示相关的文件未关闭,这样就需要调用CloseMapFile来关闭它们。
proc _CloseHandle
invoke CloseHandle, [hMapFile]
mov [hMapFile], 0
invoke CloseHandle, [hFile]
ret
endp
上述过程调用是用来关闭内存映射文件和原来打开的文件的,这样可以使得程序退出时没有资源泄漏。如果用户选择保存文件的话,就弹出一个“保存文件”对话框,当用户输入了新文件的名称后,我们调用CreateFile函数来创建新文件---输出文件。
invoke MapViewOfFile,/
[hMapFile],/
FILE_MAP_READ,/
NULL,/
NULL,/
0
在输出文件创建后,我们调用MapViewOfFile函数来映射希望映射到我们用户地址空间的部分。
dwDesiredAccess用来指定我们想对文件进行的操作。在我们例子中,我们只想读,故指定标志FILE_MAP_READ。
dwFileOffsetHigh 和 dwFileOffsetLow 用来指定打开文件中欲映射的起始偏移位置。我们的例子中想映射整个的文件,故指定它们的值为0。
dwNumberOfBytesToMap 用来指定欲映射的字节数,如果想映射整个的文件,设定该值为0。
调用MapViewOfFile后,我们希望的部分就已经映射到内存中去了。您将得到一个指向起始内存块的指针。
invoke GetFileSize, [hFile], NULL
调用该函数可以得到文件的大小,其值通过eax传送,如果文件的长度超过4G,那么文件长度DWORD的高值部分(也即超过4G的部分)保存在FileSizeHighWord中。因为我们估计一般的文件将没有这么大,故忽略该值。
invoke WriteFile,/
[hFileWrite],/
[lpMemory],/
eax,/
lpSizeWrite,/
NULL
把内存映射文件中的数据写到输出文件中去。
invoke UnmapViewOfFile, [lpMemory]
写完后,我们解除映射。此时windows会撤销之前的内存块。
invoke SetWindowText, [hWnd], szWndName
invoke EnableMenuItem, [hMenu], IDM_OPEN, MF_ENABLED
invoke EnableMenuItem, [hMenu], IDM_SAVE, MF_GRAYED
最后我们修改标题,然后激活菜单栏的“打开”菜单按钮。