第十二课 内存管理和文件输入/输出
理论:
从用户的角度来看,WIN32的内存管理是非常简单和明了的。每一个应用程序都有自己独立的4G地址空间,这种内存模式叫做“平坦”型地址模式,所有的段寄存器或描述符都指向同样的起始地址,所有的地址偏移都是32位的长度,这样一个应用程序无须变换选择符就可以存取自己的多达4G的地址空间。这种内存管理模式是非常简洁而便于管理的,而且我们再不用和那些令人讨厌的“near”和“far”指针打交道了。
  在W16下有两种主要类型的API:全局和局部。“全局”的API 分配在其他的段中,这样从内存角度来看他们是一些“far”(远)函数或者叫远过程调用,“局部”API只要和进程的堆打交道,所以把它们叫做“near”(近)函数或者近过程调用。而在WIN32中,这两种内存模式是相同的,无论您调用GlobalAlloc还是LocalAlloc,结果都是一样。
  至于分配和使用内存的过程都是一样的: 
在WIN32中您也可以用“Local”替代内存分配API函数带有“Global”字样的函数中的“Global”,也即用LocalAlloc、LocalLock等。
  在调用函数GlobalAlloc时使用GMEM_FIXED标志位可以更进一步简化操作。使用了该标志后,Global/LocalAlloc返回的是指向已分配内存的指针而不是句柄,这样也就不用调用Global/LocalLock来锁定内存了,释放内存时只要直接调用Global/LocalFree就可以了。不过在本课中我们只使用传统的方法,因为其它地方有许多的源代码是用这种方法写的。
WIN32的文件输入/输出API和DOS下的从外表上看几乎一样(译者注:也许不管内部实现多么不同,可以想象所有的文件系统暴露给应用程序编写者的接口的功能应该基本相同),不同的只是把DOS下的中断方式处理文件输入/输出变成了对API函数的调用。以下是基本的步骤: 
  
    
内容:
下面的代码段演示了:打开一个“打开文件”对话框,用户可以选择打开一个文本文件,然后在一个编辑控件中打开该文本文件的内容,另外用户还可以编辑该文本文件的内容并选择保存。
.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 
处理 WM_CREATE消息时,我们创建一个编辑控件。请注意,我们把该控件大小的有关参数都设成0,因为我们稍后将重新设置该编辑控件的大小,使得其覆盖父窗口的整个客户区。
  注意:本例中我们没有必要调用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 
创建完编辑控件后,我们初始话ofn变量的成员。因为稍后在保存文件时还要使用该结构体变量,所以此处只初始化要用到的公共部分。WM_CREATE 消息的处理部分是进行这种初始化的绝佳之处。
    .ELSEIF uMsg==WM_SIZE 
          mov eax,lParam 
          mov edx,eax 
          shr edx,16 
          and eax,0ffffh 
          invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE 
当主窗口的客户区部分大小改变时,我们的应用程序将接收到WM_SIZE 消息。当然该窗口第一次显示时,我们也将接收到该消息。要接收到该消息,主窗口必须有CS_VREDRAW和CS_HREDRAW风格。我们应该把缩放编辑控件的动作放到此处。我们要把编辑控件变成和我们的窗口客户区一样大,所以先得要得到父窗口客户区的大小。这些值包含在参数lParam中,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 
当用户选择了File/Open菜单项时,我们填充ofn的其他成员,然后调用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 
如果用户选择了一个文件时,我们调用CreateFile函数来打开。我们设置标志位来让该函数的文件能够读写。文件打开后我们把返回的文件句柄保存在一个全局变量中以便以后使用。CreateFile函数应用非常广泛,其原型如下:
CreateFile proto lpFileName:DWORD,\ 
                             
  dwDesiredAccess:DWORD,\ 
                             
  dwShareMode:DWORD,\ 
                             
  lpSecurityAttributes:DWORD,\ 
                             
  dwCreationDistribution:DWORD\, 
                             
  dwFlagsAndAttributes:DWORD\, 
                             
  hTemplateFile:DWORD 
dwDesiredAccess 指定想要进行的操作。
lpSecurityAttributes 该属性在WIN95下无效。 
  dwCreationDistribution 指定欲生成的文件在其已存在和未存在时应做的动作。
dwFlagsAndAttributes 指定文件的属性。
文件打开后,我们将分配一块内存供随后的API 函数ReadFile 和 WriteFile使用。我们使用标志GMEM_MOVEABLE来使得WINDOWS总是把内存块移到可靠的内存中去,GMEM_ZEROINIT告诉WINDOWS把刚刚分配的内存置为零。如果GlobalAlloc调用成功的话,会在eax中返回内存块的句柄,我们把该句柄传给GlobalLock函数以得到指向内存块的指针。
                    
  invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL 
                      
  invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory 
使内存块可用后,我们调用ReadFile函数从文件中读数据。对于第一次打开的文件,文件的指针放在偏移0处,像本例中我们从偏移0处往前读。ReadFile的第一个参数是文件句柄,第二个参数是指向内存块的指针,接下来的参数是要读的数据的长度,第四个参数是一个指向DWORD型的参数的指针,它用来存放实际读的数据的长度。读完了后,我们把这些内容存放到编辑控件中,这要用消息传递来完成,我们把消息WM_SETTEXT传给编辑控件,其中的参数lParam中包含指向内存块的指针。到此处,编辑控件就可以在它的客户区显示文件的内容了。
                    
  invoke CloseHandle,hFile 
                      
  invoke GlobalUnlock,pMemory 
                      
  invoke GlobalFree,hMemory 
                  
  .endif 
我们不再需要让文件打开了,因为我们的目的是把修改后的数据保存到另一个文件而不是先前的那一个文件中去。所以我们可以调用CloseHandle来关闭文件。接下来我们解锁内存块,再释放它。实际上我们可以暂不释放内存块,而在以后的操作中重新利用。我们为了演示的原由,选择了释放它。
invoke SetFocus,hwndEdit
当打开文件对话框显示在屏幕上时,输入的焦点切换到了该对话框上。所以在该对话框关闭后,我们必须把焦点切换到编辑控件上。 现在打开文件的阶段结束了,用户可以编辑他们打开的文件了。当用户想把修改后的内容保存到磁盘上时,必须选择File/Save菜单项,这时会显示一个保存文件对话框。显示保存文件对话框其实和打开打开文件对话框基本一样。您甚至可以认为他们的不同只是函数名称不一样而已。此处可以复用大多数ofn变量先前设置的成员的值。
                
  mov ofn.Flags,OFN_LONGNAMES or\ 
                                  
  OFN_EXPLORER or OFN_HIDEREADONLY 
本例中我们将生成一个新文件,所以一定不能有 OFN_FILEMUSTEXIST 和 OFN_PATHMUSTEXIST标志位。dwCreationDistribution 参数应当有CREATE_NEW标志位。 接下来的代码和打开问对话框基本一样。最后调用:
                        
  invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory 
                          
  invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL 
现在我们把修改后的数据从编辑控件中写回内存块,再从内存块写回新文件。