본문 바로가기
코딩 아카이브/C, C++

[ C/C++ ] Windows.h 의 Create/Write/ReadFile API 사용법

by SteadyForDeep 2021. 1. 5.
반응형

//서론

윈도우는 <Windows.h>에서 제공하는 API로 여러가지 작업을 할 수 있는데

그중 가장 기본적인 방법이 이 File API라고 할 수 있다.

C/C++상에서 버퍼를 처리하는 방법으로써

오브잭트나 인스턴스와는 다른 개념으로 데이터를 운반하고 수정할 수 있다.

나는 특정한 목적을 위해 디자인되는 윈도우 디바이스 드라이버와 내가 설계한 코어엔진이

적절한 연동을 이루게 하기 위해서 이 작업을 선택했다.

만들어진 파일은 일반적인 버퍼보다 더욱 까다로운 보안과 접근 권한을 지니며

내부의 데이터를 원하는 포인터에서 쓰고 적을 수 있게 해주어서 편리하다. 

 

 

 

 

//본론

'파일'이라고 하면 아이콘으로 보이는 그 파일들을 생각하기 쉽지만

생각해보면 프로세스에서 옮겨지고 처리하는 데이터 덩어리들과 별반 다르지 않다.

여기서 말하는 File도 그러한 관점에서 저장하고 나중에 다시 불러올 수 있는 정보로서의

일반적인 파일이 아니라 임시로 생성되는 소규모 데이터 뭉치를 말한다고 보면 된다.

*실재로 파일을 생성한 위치로 가보면 파일이 생겨있긴 하다.

여기서 임시로 생성한다고 이야기한 이유는 결국 소스 마지막에 파일을 닫거나 없애는 경우가 많기 때문이다. 

 

이 파일은 파일 자체로 존재하지 않고 핸들이라는 단위로 다뤄진다.

이 API를 사용하는 기본 순서는 아래와 같다.

 

1. 파일을 만든다.

이때 파일을 가진 핸들러가 생성되며 핸들러는 빈 파일 뿐만 아니라

파일의 이름, 접근 및 수정 권한, 보안, 플래그 등등의 여러가지 다른 정보들까지 포함하여 다룰 수 있게 해준다.

특히 중요한 것은 파일의 '현재 파일 포인터'인데 쓰고 읽는 함수를 사용할 때 굉장히 중요하다.

 

2. 파일에 데이터를 쓴다.

핸들러를 통째로 넘겨 받아서 쓰고 싶은 데이터를 파일 위에 쓴다.

 

3. 파일의 현재 파일 포인터를 초기화 한다.

핸들러의 현재 파일 포인터가 움직였기 때문에 원하는 위치로 다시 움직여준다.

 

4. 현재 파일 포인터로부터 데이터를 읽는다.

데이터를 읽어서 다른 버퍼에 다시 적는다.

 

비유를 하자면

빈 원고지와 펜 지우개 등의 학용품 세트를 핸들

빈 원고지가 파일

빈 원고지의 몇 번째 줄에서 펜이 멈춰 있는 지가 파일 포인터 이다.

 

 

그러면 예제코드를 통해서 알아보도록 하자.

"abcdefghijklmnopqrs" 라는 문자열로부터 일부를 복사하여 파일에 적고

"AABBCCDDEEFFGGAABBCCDDEEFFGGHHIIJJKKLLMMNNOOPP" 라는 문자열의 처음 부분을

파일에 적혀있는 데이터로 대체하는 작업을 해보겠다.

 

 

 

 

 

 

 

#include <iostream>
#include <string>

#include <Windows.h> // 윈도우즈 기능을 사용하기 위함.
#include <tchar.h>   // LPCTSTR (Long Point Constant Tstring), TCHAR을 쓰기 위한 라이브러리
#include <locale.h>  

포함해야 하는 헤더들이다.

윈도우즈 API를 쓰기 위해서 <Windows.h>를 불러온다.

윈도우즈 API는 typedef로 재선언된 타입들이 많으므로 LPCTSTR 등의 타입을 사용하기 위해 <tchat.h>를 불러온다.

그리고 TCHAR와 같은 기호들을 충돌없이 사용하기 위해서 <local.h>또한 불러온다.

 

int _tmain(int argc, LPTSTR argv[])
{
    HANDLE fHandle;
    TCHAR Strings[] = _T("AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPP");
    //DWORD result;
    LARGE_INTEGER curPtr;
    _wsetlocale(
        LC_ALL,
        _T("Korean")
    );

 

바로 메인함수를 열어준다. 이때 TCHAR나 wchar 등을 사용하는 경우는 _tmain으로 열어주도록 한다.

자세한 내용은 글 하단의 참고자료들을 보기 바란다.

 

앞서 말한 핸들을 선언해준다.

TCAHR 형식의 String은 초기화를 시켜줬다. 저 문자열의 일부를 파일을 이용해 바꿔보자.

LARGE_INTEGER 라는 타입으로 선언된 curPtr은 핸들의 파일포인터 역할을 해줄 것이다.

지역을 한국으로 초기화 하고 넘어가자.

 

 fHandle = CreateFile(                // HANDLE CreateFile();
        _T("data.txt"),               // LPCTSTR lpFileName, TCHAR 타입의 파일이름
        GENERIC_READ | GENERIC_WRITE, // DWORD dwDesiredAccess, 만들고자 하는 파일의 접근 권한 |(or) 를 이용해서 여러개 적을 수 있다.
        0,                            // DWORD dwShareMode, 다른 프로세스에서 이 파일에 접근할때의 권한 마찬가지로 | 사용가능
        NULL,                         // LPSECURITY_ATTRIBUTES lpSecurityAttributes, 보안에 관련된 구조체 설정. 기본적으로 NULL을 쓴다.
        CREATE_ALWAYS,                // dwCreationDisposition, 파일을 새로 만들어서 열지 기존의 파일을 열어서 수정할지 결정하는 옵션.
        0,                            // DWORD dwFlagsAndAttributes, 파일의 특성을 지정하는 플래그. 기존의 파일을 열때는 무시된다. 일반적으로 0
        NULL);                        // HANDLE hTemplateFile, 존재하는 파일을 열었을때 기존 파일의 핸들. 일반적으로 NULL

    // fHandle = [/]

CreateFile을 통해서 핸들을 만들 수 있다. 인수를 순서대로 간략하게 설명하면

 

LPCTSTR lpFileName,         TCHAR 타입의 파일 이름

DWORD dwDesiredAccess, 만들고자 하는 파일의 접근 권한 |(or) 를 이용해서 여러개 적을 수 있다.

DWORD dwShareMode,     다른 프로세스에서 이 파일에 접근할 때의 권한 마찬가지로 | 사용가능

LPSECURITY_ATTRIBUTES lpSecurityAttributes, 보안에 관련된 구조체 설정. 기본적으로 NULL을 쓴다.

DWORD dwCreationDisposition, 파일을 새로 만들어서 열지 기존의 파일을 열어서 수정할지 결정하는 옵션.

DWORD dwFlagsAndAttributes, 파일의 특성을 지정하는 플래그. 기존의 파일을 열 때는 무시된다. 일반적으로 0

HANDLE hTemplateFile, 존재하는 파일을 열었을때 기존 파일의 탬플릿. 일반적으로 NULL을 선언

 

자세한 내용은 글 하단의 참고자료들을 보기 바란다.

 

중요한 것은 이때 핸들러가 가진 파일의 형태가

 

pointer :

File      : [ ]

 

로 비어 있다는 것이다. 이때는 파일의 내부에서 포인터를 움직일 수도 값을 읽어올 수도 없다.

 

WriteFile(                           //BOOL WINAPI WriteFile();
        fHandle,                     // HANDLE hFile, 만들어둔 파일의 핸들.
        _T("abcdefghijklmnopqrs"),   // LPCVOID lpBuffer, 핸들로 다루는 파일에 쓰고싶은 데이터
        sizeof(TCHAR) * 13,          // DWORD nNumberOfBytesToWrite, 얼마나 쓸 것인가
        NULL,//&result,              // LPDWORD lpNumberOfBytesWritten, 얼마나 쓰여졌는가에 대한 결과값
        NULL                         // LPOVERLAPPED lpOverlapped, 어려운거. NULL을 일반적으로 넣어줌
    );

    // fHandle = [/abcdefghijklm]

    //_tprintf(_T("쓰여진 바이트 수 : %d\n"), result);

 

이번엔 핸들러안에 있는 파일에 데이터를 써보자.

 

HANDLE hFile, 만들어둔 파일의 핸들.

LPCVOID lpBuffer, 핸들로 다루는 파일에 쓰고 싶은 데이터 포인터

DWORD nNumberOfBytesToWrite, IpBuffer로 부터 몇 바이트의 데이터를 파일로 복사할 것인가

LPDWORD lpNumberOfBytesWritten, 몇 바이트의 데이터를 파일로 복사하였는가

LPOVERLAPPED lpOverlapped, 비동기 입출력을 필요로할때 선언 -> 아직 안 써도 무관함

 

lpBuffer 주소로부터 nNumberOfBytesToWrite 길이 만큼의 데이터를 복사해서

hFile의 핸들 안에 있는 파일에 적어준다.

이때 현재 파일 포인터로부터 lpNumberOfBytesWritten만큼의 길이를 붙여 넣은 것이 된다.

 

예제상에서는

 

pointer :                            ↓  => 열세칸(sizeof(TCHAR)*13)

File      : [a b c d e f g h i j k l m]

 

이렇게 되어있는 상태다.

문자열을 바로 넣은 것처럼 보이지만 실재로는 문자열 시작endpoint를 가리키는 포인터를 넣은 것이므로

문자열의 처음부터 출발하여 복사된다. 이때 파일 포인터 또한 움직이며 붙여넣었으므로 끝까지 밀려있는 상태다

 

curPtr.QuadPart = sizeof(TCHAR)*4; // 파일 포인터 조정
    
    SetFilePointerEx(              // BOOL WINAPI SetFilePointerEx(); 파일 내에서 이동할 포인터 조작 함수
        fHandle,                   // 핸들 
        curPtr,                    // 얼마나 이동할 것인가
        NULL,                      // 이동이 끝난 결과 값이 궁금할 때
        FILE_BEGIN                 // 어디서 시작해서 이동할 것인가 (FILE_BEGIN, FILE_END,FILE_CURRENT)
    );
    //     curPtr |--->|
    // fHandle = [ abcd efghijklm]

 

curPtr을 까보면 알겠지만 단순한 타입이 아니라 구조체로 되어있다.

그리고 중요한 것은 이게 파일 포인터가 아니라는 점이다.

그냥 현재 상태의 파일 포인터를 몇바이트 이동할 것인가 하는 숫자에 불과하다.

 

  (FILE_BEGIN)

pointer : ↓---->↓                    => 네칸(sizeof(TCHAR)*4)

File      : [a b c d e f g h i j k l m]

 

핸들러 안은 이런 상황이 된 셈이다.

 

    _tprintf(_T("%s\n"), Strings);

    ReadFile(                            // BOOL WINAPI ReadFile()
        fHandle,                         // HANDLE hFile,
        Strings,                         // LPVOID lpBuffer,
        sizeof(TCHAR)*7,                 // DWORD nNumberOfBytesToRead,
        NULL,//&result,                         // LPDWORD lpNumberOfBytesRead,
        NULL                             // LPOVERLAPPED lpOverlapped,
    );
    //     curPtr      |------>|
    // fHandle = [ abcd efghijk lm]


    //_tprintf(_T("읽혀진 바이트 수 : %d\n현재 포인터 위치 : %d\n"), result, curPtr.QuadPart);

    _tprintf(_T("%s\n"), Strings);

    CloseHandle(fHandle);
}

 

이제 읽기 함수로 읽어보자.

 

읽기 함수는 쓰기 함수와 거의 동일한 형태를 지닌다. 

 

                      (읽어낸 부분)

pointer :          ↓---------->↓      => 일곱칸(sizeof(TCHAR)*7)

File      : [a b c d e f g h i j k l m]

 

이렇게 읽은 부분을 원하는 버퍼인 Strings에 쓰는 것이다.

 

초기화 값을 넣어주지 않으면 아무 글자나 출력되는 문제가 생기므로

꼭 몇 칸 움직였는지 알아볼 수 있는 문자열로 해보기 바란다.

 

참고자료 이후에 예제 전문을 첨부했다.

 

 

 

 

//참고자료


기본적인 예제는 아래의 블로그를 참고했다.
https://raisonde.tistory.com/entry/Windows-API-파일-열기-읽기-쓰기-닫기

 

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

처음부터 끝까지 읽으면 사용할 수 있다. 천천히 읽어 보길.. 기본적인 파일 입출력을 위해선 4개의 함수를 사용한다. CreateFile, WriteFile, ReadFile, CloseHandle 기본적인 개념부터 설명 하자면, CreateFile.

raisonde.tistory.com

_tmain이나 TCHAT등의 궁금증은 아래를 참고했다.

noirstar.tistory.com/11

 

[C/C++] main은 알겠는데.. wmain, _tmain 는 무엇일까요?

Main 이란? int main(int argc, char* argv[]) -세번째 입력 인수가 있긴하지만 잘안쓰죠 main은 무엇일까요? 다들 아시다시피 main 이라는 특수함수는 모든 c및 c++ 프로그램이 실행이 시작되는 지점입니다. in

noirstar.tistory.com

blog.daum.net/redhair/18266970

 

[스크랩] [펌] char*, LPCTSTR, TCHAR 의 차이

어떠한 문자열을 처리하는 자료형은 보통 char, wchar, TCHAR 를 사용한다. 쉽게 보면, char* => LPSTR 라고 생각하면 되고 const char* => LPCSTR 라고 할 수 있다. 가운데 'C' 는 const 의 의미다...

blog.daum.net

<locale.h>의 역할이 무엇인지는 아래를 참고했다.

m.blog.naver.com/PostView.nhn?blogId=isentator&logNo=10183690935&proxyReferer=https:%2F%2Fwww.google.com%2F

 

[C/C++] Locale 에 의존적인 함수들

Locale 에 의존적인 함수들 프로그래밍을 하다보면 문자열을 다루는 것이 상당히 까다롭다는 것을 자주 느...

blog.naver.com

 

 

//예제원문

 

/*
* 작성일 : 20210104
* 작성자 : davi06000
* 
* 소스에 관한 설명
* 
* https://davi06000.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts
* 
* 내용
* 
* Windows에서 제공하는ㄹileAPI를 이해하기위한 간단한 튜토리얼
* 
* 참고
* https://raisonde.tistory.com/entry/Windows-API-%ED%8C%8C%EC%9D%BC-%EC%97%B4%EA%B8%B0-%EC%9D%BD%EA%B8%B0-%EC%93%B0%EA%B8%B0-%EB%8B%AB%EA%B8%B0
* https://noirstar.tistory.com/11
* http://blog.daum.net/redhair/18266970
* https://m.blog.naver.com/PostView.nhn?blogId=isentator&logNo=10183690935&proxyReferer=https:%2F%2Fwww.google.com%2F
*  
*/

#include <iostream>
#include <string>

#include <Windows.h> // 윈도우즈 기능을 사용하기 위함.
#include <tchar.h>   // LPCTSTR (Long Point Constant Tstring), TCHAR을 쓰기 위한 라이브러리
#include <locale.h>  

int _tmain(int argc, LPTSTR argv[])
{
    HANDLE fHandle;
    TCHAR Strings[] = _T("AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPP");
    //DWORD result;
    LARGE_INTEGER curPtr;
    _wsetlocale(
        LC_ALL,
        _T("Korean")
    );

    fHandle = CreateFile(                // HANDLE CreateFile();
        _T("data.txt"),                  // LPCTSTR lpFileName, TCHAR 타입의 파일이름
        GENERIC_READ | GENERIC_WRITE,    // DWORD dwDesiredAccess, 만들고자 하는 파일의 접근 권한 |(or) 를 이용해서 여러개 적을 수 있다.
        0,                               // DWORD dwShareMode, 다른 프로세스에서 이 파일에 접근할때의 권한 마찬가지로 | 사용가능
        NULL,                            // LPSECURITY_ATTRIBUTES lpSecurityAttributes, 보안에 관련된 구조체 설정. 기본적으로 NULL을 쓴다.
        CREATE_ALWAYS,                   // dwCreationDisposition, 파일을 새로 만들어서 열지 기존의 파일을 열어서 수정할지 결정하는 옵션.
        0,                               // DWORD dwFlagsAndAttributes, 파일의 특성을 지정하는 플래그. 기존의 파일을 열때는 무시된다. 일반적으로 0
        NULL);                           // HANDLE hTemplateFile, 존재하는 파일을 열었을때 기존 파일의 핸들. 일반적으로 NULL

    // fHandle = [/]

    WriteFile(                           //BOOL WINAPI WriteFile();
        fHandle,                         // HANDLE hFile, 만들어둔 파일의 핸들.
        _T("abcdefghijklmnopqrs"),                // LPCVOID lpBuffer, 핸들로 다루는 파일에 쓰고싶은 데이터
        sizeof(TCHAR) * 13,               // DWORD nNumberOfBytesToWrite, 얼마나 쓸 것인가
        NULL,//&result,                         // LPDWORD lpNumberOfBytesWritten, 얼마나 쓰여졌는가에 대한 결과값
        NULL                             // LPOVERLAPPED lpOverlapped, 어려운거. NULL을 일반적으로 넣어줌
    );

    // fHandle = [/abcdefghijklm]

    //_tprintf(_T("쓰여진 바이트 수 : %d\n"), result);
    
    curPtr.QuadPart = sizeof(TCHAR)*4;     // 파일 포인터 조정
    
    SetFilePointerEx(                    // BOOL WINAPI SetFilePointerEx(); 파일 내에서 이동할 포인터 조작 함수
        fHandle,                         // 핸들 
        curPtr,                          // 얼마나 이동할 것인가
        NULL,                            // 이동이 끝난 결과 값이 궁금할 때
        FILE_BEGIN                       // 어디서 시작해서 이동할 것인가 (FILE_BEGIN, FILE_END,FILE_CURRENT)
    );
    //     curPtr |--->|
    // fHandle = [ abcd efghijklm]

    _tprintf(_T("%s\n"), Strings);

    ReadFile(                            // BOOL WINAPI ReadFile()
        fHandle,                         // HANDLE hFile,
        Strings,                         // LPVOID lpBuffer,
        sizeof(TCHAR)*7,                 // DWORD nNumberOfBytesToRead,
        NULL,//&result,                         // LPDWORD lpNumberOfBytesRead,
        NULL                             // LPOVERLAPPED lpOverlapped,
    );
    //     curPtr      |------>|
    // fHandle = [ abcd efghijk lm]


    //_tprintf(_T("읽혀진 바이트 수 : %d\n현재 포인터 위치 : %d\n"), result, curPtr.QuadPart);

    _tprintf(_T("%s\n"), Strings);

    CloseHandle(fHandle);
}

 

 

반응형

댓글