본문 바로가기
IT 실무/기타 프로그래밍

[Windows API] 파일 열기, 읽기, 쓰기, 닫기

by id 2012. 11. 10.
반응형

처음부터 끝까지 읽으면 사용할 수 있다. 천천히 읽어 보길..


기본적인 파일 입출력을 위해선 4개의 함수를 사용한다.

CreateFile, WriteFile, ReadFile, CloseHandle

기본적인 개념부터 설명 하자면, CreateFile은 언뜻 보기엔 파일을 만들기만 할것 같이 생겼지만 그게 아니다.
쉽게 말하면 파일 핸들을 할당하는 함수이다.

WriteFile로 파일에 특정 값을 쓰거나
ReadFile로 파일의 값을 읽어 올때 모두 파일 핸들을 이용한다. "abc.txt"같은 특정 파일에 다이렉트로 접근 할 수 없는 것이다.
이런식으로 특정 파일에 접근할 수 있는 핸들 할당을 위한 함수가 CreateFile이다. 

  1. fileHandle = CreateFile("abc.txt", CREATE); //이해를 돕기 위한 것으로, 원형이 이렇진 않음
  2. fileHandle = CreateFile("abc.txt", OPEN);

이런식으로 파일을 새로 만들거나 기존 파일을 열어서 fileHandle에 할당하고

  1. WriteFile(fileHandle, "abcde");
  2. ReadFile(fileHandle, &variable);


파일에 값을 쓰거나 어떤 주소로 파일의 값을 읽는다. 그리고

  1. CloseHandle(fileHandle);

핸들을 닫아준다. 

이게 끝이다. 대충 이런식으로 사용한다. 물론 저렇게 단순하게 생겨먹은 함수는 아니다. 
인자들이 워낙 복잡하기에 한눈에 이해를 못하는 경우가 있어서 간소화 하여 설명 한 것이다. 그럼 복잡한 원형을 보자.


딱 사용 할 수 있을 정도로만 설명 할 것이다.

CreateFile

HANDLE CreateFile(
    LPCTSTR lpFileName,
    DWORD dwDesiredAccess,
    DWORD dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition,
    DWORD dwFlagsAndAttributes,
    HANDLE hTemplateFile );

lpFileName 은 말그래도 파일 명이다. LTPSTR형의 변수를 입력 해 주거나 _T("Filename.file")과 같이 직접 적어준다.

  1. hFile = CreateFile(_T("Filename.file"),

dwDesiredAccess 은 접근 권한이다.
GENERIC_READ, GENERIC_WRITE 등의 여러 권한들이 있으며 논리합 연산자 "|"를 이용해서 여러개를 적을 수 있다.

  1. hFile = CreateFile(_T("Filename.file"), GENERIC_READ | GENERIC_WRITE,
 

dwShareMode 는 다른 프로세스에서 사용 가능 하도록 할 것인지를 성정한다. 이 또한 논리합 연산자를 사용 가능하다.
멀티 프로세싱을 할것이라면 추가적으로 검색 해 보기 바라며, 기본적으로 0을 적어 준다.

  1. hFile = CreateFile(_T("Filename.file"), GENERIC_READ | GENERIC_WRITE, 0,
 

lpSecurityAttributes 는 보안에 관련된 구조체를 가리키는 값이며, 초보적인 수준에서는 잘 다루지 않는다.
이 또한 궁금하면 추가적으로 찾아보길 바란다. 기본적으로 NULL을 적어준다.

  1. hFile = CreateFile(_T("Filename.file"), GENERIC_READ | GENERIC_WRITE, 0, NULL,

dwCreationDisposition 는 파일을 열것인지, 새로 만들 것인지를 설정한다. 이에 대한 옵션을 알아보자.
CREATE_NEW or 1 : 같은 파일 명이 존재하지 않을 경우에 새 파일을 만든다. 파일이 이미 존재하면 ERROR_FILE_EXISTS (80)에러를 발생시키고 fail을 리터한다.
CREATE_ALWAYS or 2 : 항상 새 파일을 만든다. 이미 존재 할 경우 기존 파일을 새 파일로 덮어 쓴다. 파일이 이미 존재하면 ERROR_ALREADY_EXISTS (183)에러가 발생되나 success를 리턴한다.
OPEN_EXISTING or 3 :  파일이 존재 할 경우에만 연다. 파일이 존재하지 않을 경우 ERROR_FILE_NOT_FOUND (2) 
OPEN_ALWAYS or 4 : 파일이 존재 할 경우 열고, 존재하지 않으면 새로 만들고 연다. 존재하지 않을 경우 ERROR_ALREADY_EXISTS (183)
TRUNCATE_EXISTING or 5 : 파일이 존재할 경우 파일을 초기화 하고(크기를 0으로) 연다.
파일이 존재하지 않을 경우 ERROR_FILE_NOT_FOUND (2) 오류를 발생시킨다.

  1. hFile = CreateFile(_T("Filename.file"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
 

dwCreationDisposition 는 파일 파일의 특정들을 설정하는 플래그를 지정한다. 총 16개가 있다. 이는 파일을 만들때만 사용되는 것이며 기존 파일을 열때에는 기존 파일의 플래그를 따른다. 즉, 입력값은 무시된다.
일반적으론 0으로 설정 해 주면 된다.

  1. hFile = CreateFile(_T("Filename.file"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0


hTemplateFile 은 GENERIC_READ를 지정해서 연 기존 파일의 핸들이다. 파일의 특성들이 저장되므로 동일한 특성들을 가진 새 파일을 만들때 쓰이나 일반적으로는 NULL을 넣는다.

hFile = CreateFile(_T("Filename.file"), GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);

결국 이렇게 쓰면 완성이다. 알아서 찾아 보라는게 많아 황당 할 수도 있겠으나 Windows API는 어떻게 보면 고급 프로그래밍이다. 즉, 기본적인 예제 및 연습용 프로그래밍에서는 쓰이지도 않을 수많은 옵션들을 제공하는 경우가 많기에 이런것들을 하나하나 다 알아가며 공부 할 수는 없다. 일단 필요한 것만 알아서 쓰고, 더 필요하다고 생각되면 찾아 쓰면 되는것이다.

http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx

WriteFile

BOOL WINAPI WriteFile(
   HANDLE hFile, 
   LPCVOID lpBuffer,
   DWORD nNumberOfBytesToWrite,
   LPDWORD lpNumberOfBytesWritten,
   LPOVERLAPPED lpOverlapped
);

ReadFile

BOOL WINAPI ReadFile(
   HANDLE hFile,
   LPVOID lpBuffer,
   DWORD nNumberOfBytesToRead,
   LPDWORD lpNumberOfBytesRead,
   LPOVERLAPPED lpOverlapped
);

보면 알다시피 두 함수의 인자는 상당히 비슷하다. CreateFile에 비해서 비교적 간단하므로 간단히 설명 하겠다.

hFile 은 CreateFile에서 할당 해 줬던 핸들을 입력 해 준다.

fHandle=CreateFile(_T("data.txt"),GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS, 0, NULL);
ReadFile(fHandle,
 

lpBuffer -     WriteFile에서는 파일에 쓰고 싶은 스트링이나 오브젝트를 입력한다.
                  ReadFile에서는 파일에서 읽은 값을 저장하고 싶은 스트링이나 오브젝트의 주소를 입력한다.

TCHAR Strings[5];
WriteFile(fHandle, _T("abcde"),
ReadFile(fHandle, Strings,


nNumberOfBytesToRead, nNumberOfBytesToWrite 는 말 그대로 얼마나 읽고 얼마나 쓸 것인가에 해당하는 크기 값을 입력한다.

WriteFile(fHandle, _T("abcde"), sizeof(TCHAR)*3,
ReadFile(fHandle, Strings, sizeof(TCHAR)*2,


lpNumberOfBytesRead, lpNumberOfBytesWritten 는 얼마나 읽고 쓰여졌는가 결과값이 저장되는 주소값을 입력 할 수 있다.

DWORD result;
WriteFile(fHandle, _T("abcde"), sizeof(TCHAR)*3, &result,
ReadFile(fHandle, Strings, sizeof(TCHAR)*2, &result,

lpOverlapped 는 지금 알 필요 없다. 지금 단계에서 꼭 써야 하는 개념도 아니고, 해당 지면에서 설명하기도 힘들다. 그냥 NULL을 넣어 주면 된다. 필요 하면 찾아 보도록!

완성된 예제를 보자

  1. #include <Windows.h>
  2. #include <tchar.h>
  3. #include <locale.h>
  4.  
  5. int _tmain(int argc, LPTSTR argv[]) {
  6.     HANDLE fHandle;
  7.     TCHAR Strings[5];
  8.     DWORD result;
  9.     LARGE_INTEGER curPtr;
  10.     _wsetlocale(LC_ALL, _T("Korean"));
  11.     fHandle=CreateFile(_T("data.txt"),GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS, 0, NULL);
  12.     WriteFile(fHandle, _T("abcde"), sizeof(TCHAR)*3, &result, NULL);
  13.     _tprintf(_T("쓰여진 바이트 수 : %d\n"), result);
  14.  
  15.     curPtr.QuadPart = 0; //파일 포인터 조정
  16.     SetFilePointerEx(fHandle,curPtr, NULL, FILE_BEGIN);
  17.  
  18.     ReadFile(fHandle, Strings, sizeof(TCHAR)*2, &result, NULL);
  19.     _tprintf(_T("읽혀진 바이트 수 : %d\n"), result);
  20.     _tprintf(_T("%s\n"),Strings);
  21.  
  22.     CloseHandle(fHandle);
  23. }



이중에서 이해 할 수 없는 부분은 딱 한군데. SetFilePointerEx 일 것이다.
파일 핸들은 파일을 컨트롤 하기 위해 항상 포인터 정보를 가지고 있다. 파일을 읽을때 항상 처음부터 끝까지 읽는게 아니라 원하는 어느 부분부터 어느 부분까지도 읽을 수 있어야 하기 때문이다. 쓸 때도 마찬가지다. 항상 끝에서 쓰는게 아니라, 중간에 끼워서 써야 될 수도 있고 제일 첫부분에 추가시켜야 되는 내용도 있을 수 있다.
그러므로 파일 포인터을 이동시켜 가며 파일을 제어 하는데, 글을 입력할때 깜박거리는 커서로 이해 하면 쉬울 것이다.
글을 다 쓰고 나면 이 커서는 항상 제일 끝에 있다. 그래야 다음에 추가로 쓸게 있더라도 편하게 쓰기만 하면 알아서 이어 지는 것이다.
하지만 우리가 원하는 것은 파일의 처음부터 읽는 것이므로 그 포인터를 제일 앞으로 옮겨 줘야 한다. 이런식으로 포인터를 인위적으로 조작 할때 SetFilePointerEx를 쓴다. (SetFilePointer 함수를 확상시킨 함수이지만, SetFilePointer함수는 옛날에 만들어진 함수로 4G이상의 파일을 컨트롤 할 수 없기 때문에 요즘은 거의 쓰이지 않는다.)

BOOL WINAPI SetFilePointerEx(
  HANDLE hFile,
  LARGE_INTEGER liDistanceToMove,
  PLARGE_INTEGER lpNewFilePointer,
  DWORD dwMoveMethod
);

앞의 글을 차근차근 읽었다면 이 인자를 어렵지 않게 이해 할 수 있을 것이다.

hFile 은 포인트를 조정 할 파일 핸들,
liDistanceToMove 는 얼마나 이동 할 것인가
lpNewFilePointer 옮겨진 포인터를 확인 할 수 있다. 굳이 확인 할 필요가 없을 경우엔 NULL
dwMoveMethod 어디서 이동 할 것인가 플래그들은 다음과 같다.

FILE_BEGIN : 파일의 처음부터 
FILE_END : 파일을 끝에서
FILE_CURRENT : 현재 위치에서

더 성명할 필요 없을거라 믿는다.
단, 중요한건 여기서는 LARGE_INTEGER를 사용 한다는 것인데, 이는 큰 용량의 파일을 컨트롤 하기 위해 쓰이는 파일 형이다.
Windows.h에서 이미 정의되어 있는 공용체(UNION)인데, 일반적으로는 QuadPart만 사용하면 일반 DWORD처럼 사용 가능하다. 자세한건 여기서 다루지 않겠다.

LARGE_INTEGER curPtr; //Windows.h에서 이미 정의되어 있으므로 그냥 사용
curPtr.QuadPart = 10 //이렇게 그냥 DWORD처럼 사용 할 수 있다.

아래 소스를 돌려 보고 위 소스와의 차이, 결과값을 보면 이해하기 쉬울 것이다.


  1. #include <Windows.h>
  2. #include <tchar.h>
  3. #include <locale.h>
  4.  
  5. int _tmain(int argc, LPTSTR argv[]) {
  6.     HANDLE fHandle;
  7.     TCHAR Strings[5];
  8.     DWORD result;
  9.     LARGE_INTEGER curPtr;
  10.     LARGE_INTEGER thisPtr;
  11.  
  12.     _wsetlocale(LC_ALL, _T("Korean"));
  13.  
  14.     fHandle=CreateFile(_T("data.txt"),GENERIC_READ|GENERIC_WRITE,0,NULL,CREATE_ALWAYS, 0, NULL);
  15.     WriteFile(fHandle, _T("abcde"), sizeof(TCHAR)*3, &result, NULL);
  16.     _tprintf(_T("쓰여진 바이트 수 : %d\n"), result);
  17.  
  18.     curPtr.QuadPart = sizeof(TCHAR);
  19.     SetFilePointerEx(fHandle,curPtr, &thisPtr, FILE_BEGIN);
  20.  
  21.     ReadFile(fHandle, Strings, sizeof(TCHAR)*2, &result, NULL);
  22.     _tprintf(_T("읽혀진 바이트 수 : %d\n현재 포인터 위치 : %d\n"), result, thisPtr.QuadPart);
  23.     _tprintf(_T("%s\n"),Strings);
  24.  
  25.     CloseHandle(fHandle);
  26. }


반응형

댓글2