2007. 2. 16. 11:49
DevX Win32 Assembly Tutorial 12: Memory Management and File I/O

Tutorial 12: Memory Management and File I/O

이 번장에서는, 메모리 관리와 파일 입출력에 대해 설명한다. 추가로, 입출력 제어용도로서 common dialog 박스도 사용한다.
 소스    리소스 스크립트    실행 결과  

Theory:

어플리케이션 관점에서 본 Win32의 메모리관리는 매우 단순하다. 프로세스는 4 GB의 주소공간을 가진다. 사용되는 메모리모델은 「플랫메모리모델」이다. 이 모델에서는, 모든 세그먼트(segment) 레지스터(or 실렉터)는 같은 시작주소를 가리키고 있다. 그리고, 오프셋(offset)은 32 비트이므로, 어플리케이션은 자신의 주소공간이라면, 실렉터값을 변경하지 않고도 어떤 메모리에서도 액세스 할 수가 있다. 이것은 메모리관리를 간단하게 해서,"near" 포인터나 "far" 포인터는 이제는 필요없게 된 것이다.

Win16에서는, 2개의 주된 메모리 관련 API 함수가 있다, 「글로벌」과「로컬」이다. 글로벌 타입의 API 함수는, 다른 세그먼트(segment)에 메모리를 확보하게 되어 있다. 그래서, 그런 함수를 "far" 타입의 메모리 함수라고 부른다. 이에대해, 로컬 타입의 API 함수는 프로세스의 로컬 힙영역에 할당의 것으로서, "naer" 타입의 메모리 함수라고 부른다.
한편, Win32에서는 이것들 2개의 타입의 함수는 동일한 것이 된다. 글로벌 타입이든, 로컬 타입이던지 결과는 같게 된다. (의미가 없다)

메모리확보와 메모리사용 방법은 다음의 순서로 사용된다.

  1. GlobalAlloc함수를 호출해서 메모리를 확보한다. 이 함수는 메모리블럭을 제어할 수 있는 핸들을 돌려준다.
  2. GlobalLock함수를 호출해서 해당 메모리를 「잠금」상태로 한다. 메모리블럭의 핸들을 인수로 취하며, 메모리블럭의 포인터를 돌려준다.
  3. 해당 포인터를 사용해서 메모리에 읽고 쓰기할 수 있다.
  4. GlobalUnlock함수를 호출해서 메모리블럭을 「잠금해제」상태로 한다. 이 함수는 메모리블럭의 포인터를 무효화 시킨다.
  5. GlobalFree함수를 호출해서 메모리블럭을 해제한다. 인수로는 당연히 메모리블럭의 핸들을 설정한다.

LocalAlloc 함수나 LocalLock 함수라는 「Local」함수에서도 「Global」함수와 같은 결과를 얻을 수 있다. GlobalAlloc 함수를 호출 할 때, 플래그 GMEM_FIXED 를 지정하게되면, 더욱더 간단하게 처리된다. 만약, GMEM_FIXED를 사용하면, GlobalAlloc 함수나 LocalAlloc 함수로부터 반환되는값은 메모리블럭의 핸들이 아니고, 메모리블럭의 포인터가 된다. 그래서, Global/LocalLock 함수를 사용할 필요가 없어지는 것이다. 그렇지만 이 번장에서는,「전통적」인 방법에 대해서만 설명한다.

Win32의 파일 입출력은 DOS의 방식과 거의 비슷하다고 볼 수 있다. 순서도 비슷하며, 도스의 파일관련 인터럽트제어 함수가 API 함수를 호출 하는 것으로 변경되었는 것 뿐이다. 구체적으로 설명하면 다음과 같다.

  1. CreateFile함수를 호출 함으로써, 파일을 오픈하거나, 존재하지 않는다면 생성한다. 이 함수는 기능이 다양해서 파일 외에 포트를 열거나 파이프나 드라이브도 다룰 수 있다. 작업이 성공하면, 생성한 파일이나 디바이스의 핸들을 반환한다. 이 핸들을 사용해서 파일이나 디바이스에 대한 조작을 수행할 수 있다. FileOpen이라는 함수는 없다.
    또한 ,SetFilePointer함수로서 파일 포인터를 임의의 위치로 이동할 수 있다.
  2. ReadFile함수,WriteFile함수로 파일에 대한 읽기/쓰기를 수행 할 수 있다. 이런 함수는 파일에서 메모리로, 또는 메모리에서 파일로 데이터를 조작한다. 그렇기 때문에, 충분한 메모리영역을 확보해 두어야만 한다.
  3. CloseHandle함수로 파일을 닫는다. 인수로 파일 핸들을 설정한다.

Content:

아래의 프로그램은 파일을 오픈하는 다이얼로그 박스를 표시한다. 유저에게 파일을 선택받고, 작업영역의 에디트 컨트롤에 파일내용을 표시한다. 유저가 에디트 컨트롤을 편집해서, 저장하면, 그 내용이 파일에 기록된다.

.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: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 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

WM_CREATE 섹션에서, 에디트 컨트롤을 생성한다. 다음으로,에디트 컨트롤을 부모윈도우의 작업영역 전체로 확장하기 위해, 좌표값이나 크기는 전부 0 으로 해 둔다(코딩으로 변경하므로). 또한 WS_VISIBLE 스타일도 지정하고 있으므로, 에디트 컨트롤을 표시하기 위해서 ShowWindows 함수를 호출 할 필요는 없다.


추가적으로, 이런 방식은 컨트롤 뿐만 아니라 부모윈도우에서도 사용할 수 있다.

;==============================================
;       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 구조체를 초기화한다. 다이얼로그 박스의 저장커맨드에서도 ofn을 재사용하므로, GetOpenFileName 함수와 GetSaveFileName 함수로 사용할 경우에 「공통」멤버의 값으로 설정해 둔다.
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

  1. dwDesiredAccess
    파일에 대해서 어떤 처리를 할지를 설정한다.
    • 0 : 오픈하는 파일의 속성을 질문한다
    • GENERIC_READ : 읽기모드로 파일을 오픈한다
    • GENERIC_WRITE : 쓰기모드로 파일을 오픈한다
  2. dwShareMode
    다른 프로세스가 액세스 했을 때에 어떻게 처리할 지를 설정한다.
    • 0 : 다른 프로세스는 조작할 수 없다
    • FILE_SHARE_READ : 읽기 가능
    • FILE_SHARE_WRITE : 쓰기 가능
  3. lpSecurityAttributes
    Win9x에서는 무의미하다. 자식프로세스로 계승의 허가 유무
  4. dwCreationDistribution
    선택한 파일이 존재할때, 또는 존재하지 않을 때의 처리방식을 설정한다
    • CREATE_NEW : 신규파일을 생성한다. 파일이 이미 존재하면 이 함수는 실패한다.
    • CREATE_ALWAYS : 신규파일을 생성한다. 기존파일이 존재하면 덮어쓰기한다.
    • OPEN_EXISTING : 파일을 오픈한다. 이미 파일이 존재하면 이 함수는 실패한다.
    • OPEN_ALWAYS : 파일이 존재하면 오픈한다. 파일이 없다면 신규로 생성하고 오픈한다.
    • TRUNCATE_EXISTING : 파일을 오픈하지만 오픈한 뒤, 파일의 크기를 0 바이트 설정한다(삭제 후 오픈). dwDesiredAccess 파라미터로, GENERIC_WRITE를 선택해야만 사용가능하다. 파일이 없다면 이함수는 실패한다.
  5. dwFlagsAndAttributes
    파일의 속성을 지정한다.
    • FILE_ATTRIBUTE_ARCHIVE : archive파일을 대상으로 한다.
    • FILE_ATTRIBUTE_COMPRESSED : 압축된 파일, 혹은 디렉토리. 파일의 경우, 파일 전체가 압축되어 있다는 것을 의미하고, 디렉토리의 경우는 새롭게 생성되는 파일이나 서브 디렉토리를 의미한다.
    • FILE_ATTRIBUTE_NORMAL : 특별한 속성을 가지지 않는 것으로, 이 속성은 단독으로 밖에 사용할 수 없다.
    • FILE_ATTRIBUTE_HIDDEN : 숨김 파일. 이것은 보통 명령으로 표시되지 않는다.
    • FILE_ATTRIBUTE_READONLY : 읽기전용. 어플리케이션은 읽기는 가능하지만, 쓰기는 할 수 없다.
    • FILE_ATTRIBUTE_SYSTEM : OS의 파일, 혹은 일부, 시스템파일

invoke GlobalAlloc, GMEM_MOVEABLE or GMEM_ZEROINIT, MEMSIZE
mov hMemory, eax
invoke GlobalLock, hMemory
mov pMemory, eax

파일이 오픈될 때, ReadFile 함수, WriteFile 함수로 사용하기 위한 메모리를 확보한다. 이 때, GMEM_MOVEABLE 플래그를 설정해서 , Windows에 메모리관리를 위임 할 수 있도록 하기 위해, 이동가능 메모리로 할당한다. GMEM_ZEROINIT 플래그는, 0 으로 초기화된 메모리블럭을 생성한다.

GlobalAlloc 함수가 성공적으로 수행되면, eax 레지스터에 메모리블럭의 핸들이 반환되어 설정된다. 이 핸들을 GlobalLock 함수에 넘겨주어, 메모리블럭의 포인터를 받도록 한다.

invoke ReadFile, hFile, pMemory, MEMSIZE-1, ADDR SizeReadWrite, NULL
invoke SendMessage, hwndEdit, WM_SETTEXT, NULL, pMemory

메모리블럭을 사용 할 준비가 되면, ReadFile 함수로, 파일로부터 데이터를 읽어낼 수가 있다. 파일이 오픈되고, 생성된 직후의 상태에서, 파일 포인터는 파일의 선두로 지정되어 있다. 그래서 이런 경우, 파일의 선두로부터 읽게 된다. ReadFile 함수의 처음 인수는 파일 핸들이며, 두번째는 메모리블럭의 포인터, 세번째는 파일로부터 몇 바이트 읽어들이는지를 지정하고, 네번째의 인수는 DWORD형 변수로서 , 실제로 파일로부터 몇 바이트 읽어들였는지가 저장된다.

메모리에 데이터가 저장된 후, 에디트 컨트롤에 파일내용을 쓰기 때문에, 메모리의 포인터를 lParam에 설정 하고, WM_SETTEXT 메세지를 전송한다. 이 함수를 호출 한 후, 에디트 컨트롤은 작업영역에 해당 파일의 내용을 출력한다.

    invoke CloseHandle, hFile
    invoke GlobalUnlock, pMemory
    invoke GlobalFree, hMemory
.endif

유저가 어떤 편집을 하고 난 후, 저장할 때는 파일을 오픈해 두는 것은 무의미하기 때문에, CloseHandle 함수를 호출해서 파일을 닫는다. 다음으로, 메모리블럭의 잠금을 해제한다. 실제로, 다음에 저장할 경우에 메모리를 사용하므로, 여기서 메모리를 해제할 필요는 없지만, 본예제에서는 여기서 해제하고 있다.

invoke SetFocus, hwndEdit

파일 오픈 다이얼로그 박스가 화면에 표시되면 포커스를 파일오픈 다이얼로그로 옮기고 , 다이얼로그 박스가 닫히면 에디트 컨트롤에 포커스를 이동해야 한다.

이것으로, 파일의 읽기작업은 끝이다. 여기서, 유저가 에디트 컨트롤에서 어떤 편집을 하고, 그 데이터를 다른 파일에 저장할 경우에는, 「File」→「Save」를 선택하게 되면 파일을 저장하는 다이얼로그 박스가 표시된다. 파일 저장다이얼로그 박스는, 파일을 오픈하는 다이얼로그 박스와 다른점이 없다. 단지 호출 함수이름만 다를 뿐이다.차이점은 GetOpenFileName 에서, GetSaveFileName 라는 함수명 뿐이다. 그래서, ofn 구조체의 플래그 멤버를 재사용할 수 있다.

mov ofn.Flags, OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY

이 경우, 신규파일을 생성하기 때문에, OFN_FILEMUSTEXIST 와 OFN_PATHMUSTEXIST 를 지정해야만 한다.
CreateFile 함수의 dwCreationDistribution 파라미터는 신규 파일을 생성하므로,CREATE_NEW로 변경해야 한다.

나머지의 코드는 다음 부분을 제외하고 파일을 오픈하는 코드와 같으므로, 이부분만 설명한다.

invoke SendMessage, hwndEdit, WM_GETTEXT, MEMSIZE-1, pMemory
invoke WriteFile, hFile, pMemory, eax, ADDR SizeReadWrite, NULL

WM_GETTEXT 메세지를 에디트 컨트롤에 전송해서, 에디트 컨트롤의 데이터를 메모리에 복사한다. 반환값으로 eax 레지스터에 버퍼의 크기가 반환된다. 메모리에 데이터가 들어가면, 신규생성한 파일에 쓴다.


Posted by openserver