全部博文(290)
分类:
2007-12-29 16:41:12
Tutorial 12: Memory Management and File I/O
第十二课:内存管理和文件输入输出
We will learn the rudimentary of memory management and file i/o operation in this tutorial. In addition we'll use common dialog boxes as input-output devices.
我们将在这一课中学习内存管理的基本原理和文件的I/O操作。另外我们将用通用对话框作为输入输出设备。
Download the example here.
Theory:
原理:
Memory management under Win32 from the application's point of view is quite simple and straightforward. Each process owns a 4 GB memory address space. The memory model used is called flat memory model. In this model, all segment registers (or selectors) point to the same starting address and the offset is 32-bit so an application can access memory at any point in its own address space without the need to change the value of selectors. This simplifies memory management a lot. There's no "near" or "far" pointer anymore.
从应用程序的角度来看WIN32中的内存管理是非常简单和直接的.每个程序都拥有4GB的地址空间.这种内存模式叫做平坦内存模式.在这种模式中,所有的段寄存器(或选择器)都指向同一个开始地址并且偏移量是32位的,所以一个应用程序能在它的地址空间中访问任何地址而不需要改变段选择器的值. 这种内存管理模式简单了很多.在平坦模式中再也没有了 near 和 far 指针.( 即段内和段间转移)
Under Win16, there are two main categories of memory API functions: Global and Local. Global-type API calls deal with memory allocated in other segments thus they're "far" memory functions. Local-type API calls deal with the local heap of the process so they're "near" memory functions. Under Win32, these two types are identical. Whether you call GlobalAlloc or LocalAlloc, you get the same result.
在win16中,有两种主要类型的内存API函数: 全局的和局部的.全局类型的API函数调用处理在其他段中内存分配(编译中的静态分配)从而它们是”far”内存函数. .局部类型的API调用处理一个过程的局部内存堆(编译中的动态分配)所以它们是”near”内存函数.在win32中,这两种类型是一样的.无论你调用的是GlobalAlloc(全局分配) 或是 LocalAlloc(局部分配),你得到的结果是一样的.
Steps in allocating and using memory are as follows:
分配和使用内存的步骤如下:
通过调用GlobalAlloc分配一个内存块.这个函数返回一个请求到的(向操作系统请求) 内存块.
通过调用GlobalLock锁住这个内存块。这个函数接收一个内存块句柄并返回这个内存块指针。(锁住的内存块)
你能用指针读或写内存。
通过调用GlobalUnlock函数为锁住的内存块解锁。这个函数使指向那个内存块的指针无效。
通过调用GlobalFree函数释放申请到的内存块。这个函数接收一个内存块句柄。
You can also substitute "Global" by "Local" such as LocalAlloc, LocalLock,etc.
你也能用“Local”代替“Global”例如LocalAlloc,LocalLock等。
The above method can be further simplified by using a flag in GlobalAlloc call, GMEM_FIXED. If you use this flag, the return value from Global/LocalAlloc will be the pointer to the allocated memory block, not the memory block handle. You don't have to call Global/LocalLock and you can pass the pointer to Global/LocalFree without calling Global/LocalUnlock first. But in this tutorial, I'll use the "traditional" approach since you may encounter it when reading the source code of other programs.
上面这些方法能通过在GlobalAlloc调用中使用一个GMEM_FIXED标志而得到进一步的简化。如果你用这个标志,从Global/localAlloc中返回的值将是一个指向已分配内存块的指针,而不是内存块句柄。你也没必要再调用Global/LocalLock这个函数来锁定内存块 并且 在释放内存时 你能传递这个指针给Global/LocalFree而不用首先调用Global/LocalUnlock函数来为这个内存块解锁。但是在这一课中,我将使用“传统的” 方法因为当你阅读其他程序的源代码时你可能遭遇它 。
File I/O under Win32 bears remarkable semblance to that under DOS. The steps needed are the same. You only have to change interrupts to API calls and it's done. The required steps are the followings:
在win32中的文件i/o操作比起在DOS中的文件i/o操作具有显著的外表(伪装),但需要的步骤是相同的。你仅需要的是把DOS中的中断调用变成API调用。 下面的步骤是必须的:
通过调用CreateFile函数来打开或创建文件。这个函数是非常有用的: 除文件外,它能打开通信端口,管道,磁盘驱动器或控制台。如果成功的话,它将返回一个文件或设备的句柄。然后,你能用这个句柄完成对文件和设备的操作。通过调用SetFilePointer函数来把文件指针移到想读写的地方。
通过调用ReadFile或WriteFile来完成读或写操作。这些函数在内存块和文件之间传递数据。所以你必须分配一个足够大的内存块来容纳数据。
通过调用CloseHandle函数来关闭文件。这个函数接收一个文件句柄。
Content:
内容:
The program listed below displays an open file dialog box. It lets the user select a text file to open and shows the content of that file in an edit control in its client area. The user can modify the text in the edit control as he wishes, and can choose to save the content in a file.
下面的程序清单显示一个打开文件对话框。它让用户选择一个文本文件来打开并将文件的内容显示在一个编辑控件的客户区内。用户能在编辑框中按他希望的形式修改文本,并且能选择保存文件内容。
.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
Analysis:
分析:
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
In WM_CREATE section, we create an edit control. Note that the parameters that specify x, y, width,height of the control are all zeroes since we will resize the control later to cover the whole client area of the parent window.
Note that in this case, we don't have to call ShowWindow to make the edit control appear on the screen because we include WS_VISIBLE style. You can use this trick in the parent window too.
在WM_CREATE节区中,我们创建一个编辑控件。注意指定控件的x,y,width,height参数为空是因为我们稍后将调整这个控件的大小来覆盖整个父窗口的客户区。注意在这个例子中,我们没有必要调用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
After creating the edit control, we take this time to initialize the members of ofn. Because we want to reuse ofn in the save as dialog box too, we fill in only the *common* members that're used by both GetOpenFileName and GetSaveFileName.
在创建了编辑控件后,我们初始化ofn结构成员变量。因为在保存文件对话框中还要用到这个结构,所以我们只初始化它的公共成员变量,这些公共成员将被GetOpenFileName和GetSaveFileName函数使用。
WM_CREATE section is a great place to do once-only initialization.
WM_CRREATE 消息的是对变量仅初始化一次的好地方。
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
We receive WM_SIZE messages when the size of the client area of our main window changes. We also receive it when the window is first created. In order to be able to receive this message, the window class styles must include CS_VREDRAW and CS_HREDRAW styles. We use this opportunity to resize our edit control to the same size as the client area of the parent window. First we have to know the current width and height of the client area of the parent window. We get this info from lParam. The high word of lParam contains the height and the low word of lParam the width of the client area. We then use the information to resize the edit control by calling MoveWindow function which, in addition to changing the position of the window, can alter the size too.
当主窗口客户区大小被改变的时候我们收到WM_SIZE消息。在窗口第一次创建的时候我们同样能接收到这个消息。为了能接收到这个消息,窗口样式必须包含CS_VREDRAW和CS_HREDRAW样式。这个时候我们将编辑控件的大小调整得和父窗口客户区一样大。首先,我们必须知道当前父窗口客户区的宽和高。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
When the user selects File/Open menu item, we fill in the Flags member of ofn structure and call GetOpenFileName function to display the open file dialog box.
当用户选定文件/打开菜单项时,我们填写ofn结构的Flags成员变量并调用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
After the user selects a file to open, we call CreateFile to open the file. We specifies that the function should try to open the file for read and write. After the file is opened, the function returns the handle to the opened file which we store in a global variable for future use. This function has the following syntax:
当用户选择了他想打开的文件之后,我们调用CreateFile函数来打开这个文件。我们指定标志位来让打开的文件能够读和写。在文件被打开后,这个函数将返回被打开的文件句柄,为了方便以后使用我们将它存储在一个全局变量中。这个函数句法如下:
CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD
dwDesiredAccess specifies which operation you want to perform on the file.
dwDesiredAccess指定你想在这个文件上完成的操作。
dwShareMode specifies which operation you want to allow other processes to perform on the file that 's being opened.
指定其他进程在你打开的文件上允许执行的操作。
lpSecurityAttributes has no significance under Windows 95.
该属性在win95中无效。
dwCreationDistribution specifies the action CreateFile will perform when the file specified in lpFileName exists or when it doesn't exist.
指定 当文件指定在lpFileName中的成员存在或不存在的时候CreateFile函数将执行的动作
dwFlagsAndAttributes specifies the file attributes
dwFlagsAndAttributes 指定文件属性.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
When the file is opened, we allocate a block of memory for use by ReadFile and WriteFile functions. We specify GMEM_MOVEABLE flag to let Windows move the memory block around to consolidate memory. GMEM_ZEROINIT flag tells GlobalAlloc to fill the newly allocated memory block with zeroes.
When GlobalAlloc returns successfully, eax contains the handle to the allocated memory block. We pass this handle to GlobalLock function which returns a pointer to the memory block.
当文件被打开时,我们分配一块内存供ReadFile和WriteFile函数使用.我们指定GMEM_MOVEABLE标志让Windows移动分配内存块周围的内存来整理内存.GMEM_ZERO INIT标志告诉GlobalAlloc函数用零将新分配的内存块填满.当GlobalAlloc返回成功,eax包含分配内存块的句柄.我们传递这个句柄给GlobalLock函数,这个函数返回内存块的指针.
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
When the memory block is ready for use, we call ReadFile function to read data from the file. When a file is first opened or created, the file pointer is at offset 0. So in this case, we start reading from the first byte in the file onwards. The first parameter of ReadFile is the handle of the file to read, the second is the pointer to the memory block to hold the data, next is the number of bytes to read from the file, the fourth param is the address of the variable of DWORD size that will be filled with the number of bytes actually read from the file.
After we fill the memory block with the data, we put the data into the edit control by sending WM_SETTEXT message to the edit control with lParam containing the pointer to the memory block. After this call, the edit control shows the data in its client area.
当内存块准备好被使用时,我们调用ReadFile函数从文件中读进数据.当文件是第一次被打开或创建时,文件指针在偏移量0的地方 .所以在这个例子中,我们从第一个字节向前的读这个文件.ReadFile的第一个参数是要读的那个文件句柄,第二个参数是存放数据的内存块指针,下一个成员是从文件中读取的字节数.第四个参数是DWORD长度的地址变量,这个成员存放从文件中实际读取的字节数.
在用数据填满这个内存块后,我们通过在WM_SETTEXT消息的lParam中包含内存块的指针并发送WM_SETTEXT消息给编辑控件来把数据放置在编辑控件中。在这个函数调用后,编辑控件将数据显示在它的客户区。
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
At this point, we have no need to keep the file open any longer since our purpose is to write the modified data from the edit control to another file, not the original file. So we close the file by calling CloseHandle with the file handle as its parameter. Next we unlock the memory block and free it. Actually you don't have to free the memory at this point, you can reuse the memory block during the save operation later. But for demonstration purpose , I choose to free it here.
这里需要指出的是,我们再也不需要让文件保持打开状态了,因为我们的目的是把在编辑框中修改了的数据写进其它的文件中而不是原来的文件。所以我们指定文件句柄作为CloseHandle的参数,然后调用这个函数来关闭文件。下一步我们解锁这块内存并释放它。实际上你没必要在这里释放这块内存,你可以在稍后的保存操作中重新使用这块内存。但是作为演示的用途,我在这里选择释放它。
invoke SetFocus,hwndEdit
When the open file dialog box is displayed on the screen, the input focus shifts to it. So after the open file dialog is closed, we must move the input focus back to the edit control.
This end the read operation on the file. At this point, the user can edit the content of the edit control.And when he wants to save the data to another file, he must select File/Save as menuitem which displays a save as dialog box. The creation of the save as dialog box is not much different from the open file dialog box. In fact, they differ in only the name of the functions, GetOpenFileName and GetSaveFileName. You can reuse most members of the ofn structure too except the Flags member.
当打开文件对话框被显示在屏幕上时,输入焦点切换给它。所以在对话框被关闭后,我们必须移动输入焦点到编辑控件上。
最后是在文件上的写操作。这里需要指出的是,用户能编辑这个编辑控件中的内容。并且当用户想把数据保存在另一个文件中时,他必须选定文件/保存菜单项,这个菜单项弹出一个保存文件对话框。产生的保存对话框和打开文件对话框有很多相似之处。事实上,它们的不同之处仅仅是函数的名字,GetOpenFileName 和GetSaveFileName。除了标志成员外你可以重新使用ofn结构的大多数成员。
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
In our case, we want to create a new file, so OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST must be left out else the dialog box will not let us create a file that doesn't already exist.
The dwCreationDistribution parameter of the CreateFile function must be changed to CREATE_NEW since we want to create a new file.
The remaining code is identical to those in the open file section except the following:
在我们的例子中,我们想创建一个新的文件,所以OFN_FILEMUSTEXIST和OFN_PATHMUSTEXIST标志必须被遗弃,否则这个对话框将不能让我们创建一个文件,因为这个文件不是现有的。
CreateFile函数的dwCreationDistribution 参数必须变成CREATE_NEW 因为我们想创建一个新文件。
剩下的代码除了下面这几行,其它的和那些在打开文件节区中的一样:
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
We send WM_GETTEXT message to the edit control to copy the data from it to the memory block we provide, the return value in eax is the length of the data inside the buffer. After the data is in the memory block, we write them to the new file.
我们发送WM_GETTEXT消息给编辑控件,复制数据到我们预先提供的内存块中。在eax中的返回值是缓冲区中的数据长度。当数据复制到内存块中后,我们把它们写到新文件中。
This article come from Iczelion's asm page,
风向改变翻译于
由于考试复习,放了一段时间。