2007. 2. 16. 12:21
DevX Win32 Assembly Tutorial 13: Memory Mapped Files

Tutorial 13: Memory Mapped Files

이번에는, 메모리맵파일(=memory mapped file, MM파일)이 무엇인지와 사용법을 설명한다. 이장을 읽어보면, MM파일을 간단하게 사용할 수 있게 된다.
 소스    리소스 스크립트    실행 결과  

Theory:

이전장의 예제를 유심히 살펴 보면, 중대한 결함을 발견할 것이다. 예를 들면, 확보한 메모리영역보다 큰파일을 읽게 되면 어떻게 될까? 혹은, 검색문자열이 메모리영역에 존재하지 않으면 어떻게 할까? 등이다. 전자의 해결책은, 전통적으로 파일을 다 읽을 때까지 일정크기의 데이터를 반복해서 읽어들이는 방법이다. 후자의 해결책은, 메모리블럭의 마지막에 특별한문자를 미리 설정하는 방법이 있다. 이런 것은 버퍼 오버플로우 에러(BOF)로 불려지며 프로그래머를 힘들게하는 매우 자주 일어날수 있는 버그들이다.

파일의 내용을 저장하기 위해서 아주 큰 영역을 확보하면 좋겠지만,그렇게 되면 한정되어있는 리소스를 쓸데없게 낭비하게 된다. 이것은 파일 매핑 이란 방법으로 해결할 수 있다. 파일 매핑을 사용함으로써, 파일의 모든내용을 메모리에 로드 한 것과 같이 작동 되게 할 수 있다. 이것은 메모리포인터를 사용해서, 파일데이터를 읽고 쓰기할 수가 있다. 방법은 매우 간단하다. 메모리처리 API 함수를 사용할 필요도 없고, 파일 IO의 API 함수로 파일을 분할할 필요도 없다.

파일 매핑은, 프로세스간에 데이터를 공유하는 용도로도 사용된다. 이런 목적으로 파일 매핑을 사용하는 경우는, 실제 파일이 없어도, 모든 프로세스가 공통적으로 참조할 수 있는 메모리블럭이라고 생각할 수 있다. 그러나, 프로세스간에 데이터를 공유하는 것은 실제로는 매우 어렵기 때문에, 쉽게 사용해서는 안된다. 정확하게 프로세스간,혹은 thread간에 데이터의 동기화를 하지 않는다면 프로그램은 아주 쉽게 깨지게 될것이다.

이 번장에서는, 파일 매핑의 이런 공유 메모리적인 사용 방법은 다루지 않는다. 단순히 파일 매핑을 파일과 메모리를 매핑 한다 라는 용도로만 사용한다. 실제로, PE로더(Windows가 제공하는 실행 파일 실행하는 모듈)은 실행 파일을 메모리에 매핑 할 경우에 파일 매핑을 사용한다. 이는 아주 편리해서, 하드 디스크에 존재하는 파일로부터 필요한 부분을 선택적으로 읽어들일 수가 있다. Win32에서는, 최대한 파일 매핑을 사용해야 한다.

그러나, 몇개의 제한이 있다. 한 번 MM파일을 생성하면, 그 세션에서는 MM파일의 크기를 변경할 수 없다. 그래서, 파일 매핑은 읽기전용 파일이나, 파일크기에 관계없는 파일의 조작 등에 적당하다. 그러나 이것은, 파일크기가 증가할 때에 파일 매핑을 사용할 수 없다는 것은 아니고, 파일크기가 얼마나 커질까를 예상해서, 그 크기에 근거해서 MM파일을 생성하면 좋다는 의미이다.

이것으로 설명은 충분할 것이다. 그럼 실제로 파일 매핑의 예를 살펴보자. 파일 매핑을 사용하려면 , 다음과 같은 단계를 따라야한다.

  1. CreateFile함수를 호출해서, 매핑 하고 싶은 파일을 오픈한다
  2. CreateFile 함수로 얻은 파일핸들을 인수로 해서 CreateFileMapping함수를 호출 한다. 이 함수에 의해, 지정된 파일에 대한 파일매핑오브젝트를 생성한다.
  3. MapViewOfFile함수를 호출해서, 매핑 하는영역이, 파일전체인지 일부분 인지를 선택한다. 이 함수는 MAP과 파일영역의 선두의 포인터를 돌려준다.
  4. 그 포인터를 이용해서 파일에 읽고 쓰기를 수행한다
  5. UnmapViewOfFile함수를 호출해서, 파일 매핑을 해제한다
  6. CreateFileMapping 함수로 생성한 파일 매핑 오브젝트를 인수로 해서 CloseHandle함수를 호출한다
  7. CreateFile 함수로 생성한 파일 핸들을 인수로 해서 한번 더CloseHandle함수를 호출해서, 파일을 닫는다

Example:

.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

파일 오픈 다이얼로그 박스로 파일을 선택하면, CreateFile 함수로 그 파일을 오픈한다. 이때, 읽기전용을 의미하는 GENERIC_READ 플래그를 지정하고, 이 프로그램의 조작중에 다른프로세스로부터의 조작을 허가하지 않게 하기 위해서 dwShareMode 를 0으로 설정한다.

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

그리고, 오픈한 파일로부터 MM파일을 생성하기 위해서, CreateFileMapping 함수를 호출 한다. CreateFileMapping 함수의 프로토타입은 다음과 같다.

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

가장 먼저 알아두어야 할 것은, CreateFileMapping 함수로 파일 전체를 메모리에 매핑 할 필요는 없다는 것이다. 파일의 일부를 매핑 해 두면 된다. 어느 정도의 영역을 매핑 할지는, dwMaximumSizeHigh와 dwMaximumSizeLow의 인수에 설정하는 값에 따르지만, 만약, 실제파일크기보다 큰 크기를 설정 했을 경우, 그 크기에 맞추어 파일 크기가 커진다. 파일크기와 같은크기의 메모리매핑영역으로 하고 싶다면, dwMaximumSizeHigh와 dwMaximumSizeLow의 양쪽 모두의 파라미터를 0 으로 설정하면 된다.

Windows에 기본 보안속성을 가진 MM파일을 생성하기 위해서, lpFileMappingAttributes 인수를 NULL로 설정하면 된다.

flProtect는 MM파일의 보호속성을 정의한다. 이 번장에서는, MM파일을 읽기전용으로 사용하기 때문에, PAGE_READONLY를 사용하고 있다. 주의할 것은, 이 속성은 CreateFile 함수에서 지정한 플래그와 충돌되지 않게 해야한다는 것이다. 충돌이란 CreateFile 함수에서는 읽기전용인데, CraeteFileMapping 함수에서는 읽기/쓰기가능이라는 것과 같은 경우를 의미하고,이런경우 CreateFileMapping 함수는 실패하게 된다.

lpName는 MM파일명을 가리키는 포인터로, 다른프로세스와 이 파일을 공유하는 경우는, 이름을 붙여야만 한다. 그러나, 이 예제에서는, 다른프로세스와 공유하지않기 때문에, 이 파라미터는 무시하고 있다.

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

안전대책으로 유저가 한 번에 여러개의 파일을 오픈할 수 없게 하기 위해, Open 메뉴 아이템을 회색으로 처리하고, Save 메뉴 아이템만 선택가능하게 하고 있다. EnableMenuItem 함수는 메뉴 아이템의 속성을 변경하기 위해서 사용하는 함수이다.

이 후부터는, 유저가 「File」→「Save」메뉴를 선택하는지, 프로그램을 종료할지를 선택할 수있게 된다. 만약 유저가 종료를 선택하면, 보통 파일조작때와 같이, MM파일도 닫아주어야만 한다. 그 코드는 다음과 같다.

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

위의 코드는, 윈도우 프로시저가 WM_DESTROY 메세지를 받았을 때의 처리를 하고있다.먼저 hMapFile이 0 인지를 체크하고 있다. 만약 0 이라면, 다음과 같이CloseMapFile 함수를 호출 하게 된다.

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

CloseMapFile 함수는, Windows로 제어를 돌려줄 때, 자원누출(Leak)을 없애기 위해, MM파일과 실제 파일을 닫는다. 유저가 데이터를 다른 파일에 저장하기 위해 Save 메뉴를 선택하면, 프로그램은 파일을 저장하는 다이얼로그 박스를 표시하고 ,저장 다이얼로그 박스에 유저가 새로운 파일명을 입력해서, CreateFile 함수로, 그 이름의 파일을 생성한다.

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

출력 파일이 생성된 직후에, MapViewOfFile 함수를 호출 해서, 메모리에 MAP 하는 파일영역을 지정한다. 이 함수의 프로토타입은 다음과 같다.

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

  • dwDesiredAccess
    파일에 어떤 처리를 하고 싶은지를 지정한다.예제에는, read가 적당하기 때문에, FILE_MAP_READ를 사용하고 있다.
  • dwFileOffsetHigh,dwFileOffsetLow
    메모리에 MAP 하고 싶은 영역을 파일선두로부터 어느 부분까지인지를 지정한다. 예제에는, 파일 전체를 MAP 하기 때문에, 선두로부터 매핑을 시작하고 있다.
  • dwDesiredAccess
    메모리에 MAP 하고 싶은 영역을 바이트 단위로 지정한다. 만약 파일 전체를(CreateFileMapping 함수에 의해) 매핑 하고 싶다면, 0 을 지정한다.

MapViewOfFile 함수를 호출 하면, 메모리에 지정한 부분을 읽어들이고, 파일의 데이터가 저장되어 있는 메모리블럭의 포인터가 반환된다.

invoke GetFileSize, hFileRead, NULL

파일의 크기를 얻기위해서, GetFileSize 함수를 호출 하면, eax 레지스터에 파일크기가 저장된다. 만약 크기가 4 GB이상이라면, 두번째 인수에 DWORD형태의 메모리주소를 지정함으로써, 상위 DWORD(=4바이트)가 그 두번째 인수에 저장되게 되지만, 이 예제에서는 그정도로 큰 파일을 다루지 않기 때문에 NULL을 지정하고 있다. 4 GB미만의 파일크기인 경우는 NULL을 지정하는 것이다.

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

메모리에 MAP 되고 있는 데이터를 파일에 출력한다

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

Open 메뉴를 선택 가능하게 하고, Save 메뉴를 회색으로 처리해서 사용하지 못하게 한다.


Posted by openserver