01
14

C언어에서는 처리할 파일을 File 구조체와 

파일을 여는 fopen(경로 및 파일명, 모드)

파일을 읽고 쓰는 fread(), fwrite() 

파일을 닫는 fclose()등 파일 입출력을 했었다.

 

WinApi에서도 파일 입출력 함수가 있는데, 

CreateFile(), ReadFile(), WriteFile(), CloseHandle()

위와 같은 역활을 한다. 다만, 윈도우 운영체제와 밀접하고

비동기 파일 입출력을 지원한다는 큰 강점이 있다.

소켓을 붙이면 소켓 입출력이된다.

 

비동기 파일 입출력의 필요성

 

유튜브 영상을 볼때, 영상을 모두 로드하고 영상을 시청할려하면,

영상을 로드하는 동안은, 영상을 볼 수가 없다. 사용자는 버근가 싶을 것이다.

그래서 비동기 I/O 사용한다. 데이터 수신과 영상 출력이 동시에 일어난다.

WINAPI의 파일입출력은 이러한 비동기 파일 입출력을 지원한다. 

 

CreateFile() 함수 원형

1
2
3
4
5
6
7
8
9
HANDLE CreateFile(
 [in]           LPCWSTR               lpFileName, //만들거나 열 파일 또는 파일의 경로 및 이름
  [in]           DWORD                 dwDesiredAccess, //원하는 접근 모드,  GENERIC_READ , GENERIC_WRITE
  [in]           DWORD                 dwShareMode, //다른 프로세스가 파일 또는 장치를 연 상태에서 공유 가능하게 할 것인지
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, //파일의 보안속성 지정함, 자식한테 상속 가능, 옵션값임
  [in]           DWORD                 dwCreationDisposition, //생성 관련 속성, 덮어쓰기나 새로만들기 등
  [in]           DWORD                 dwFlagsAndAttributes, //장치 속성 플래그, 읽기전용, 숨김파일, 비동기 I/O등 플래그 세움
  [in, optional] HANDLE                hTemplateFile //템플릿으로 동일한 특성 새파일 만들고 싶으면 쓰임, 일반적으로 NULL
);
cs

 

WriteFile() 함수 원형

1
2
3
4
5
6
7
8
BOOL WriteFile(
  [in]                HANDLE       hFile, //CreateFile에서 할당 해 줬던 핸들을 입력 해 준다.
  [in]                LPCVOID      lpBuffer, // write할 데이터가 들어 있는 버퍼 포인터 
  [in]                DWORD        nNumberOfBytesToWrite, // write할 데이터의 사이즈
  [out, optional]     LPDWORD      lpNumberOfBytesWritten, // 작성된 바이트 수를 수신하는 변수,
                                                           // 비동기에서 잠재적으로 잘못될수 있어 Null로 설정
  [in, out, optional] LPOVERLAPPED lpOverlapped // 비동기 Overlapped 구조 포인터 자리
);
cs

 

ReadFile() 함수 원형

1
2
3
4
5
6
7
BOOL ReadFile(
  [in]                HANDLE       hFile, // 읽고자 하는 파일의 핸들 
  [out]               LPVOID       lpBuffer, // 읽어올 저장 버퍼의 포인터
  [in]                DWORD        nNumberOfBytesToRead, // 실제 읽어오는 바이트 수
  [out, optional]     LPDWORD      lpNumberOfBytesRead, // 얼마나 읽고 쓰여졌는가 결과값이 저장되는 주소값
  [in, out, optional] LPOVERLAPPED lpOverlapped // 비동기 Overlapped 구조 포인터 자리
);
cs

 

파일에 읽고 쓸 파일 HANDLE을 만들어서 함수처리하면 된다.  

주요 함수에 대해서 알았으니 파일 입출력을 해보자. 

 

동기 파일 입출력 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int main()
{
    setlocale(LC_ALL, ""); // Korean해도 되지만 공백은 시스템 지역설정으로 따르게 한다.
    HANDLE hFile = CreateFile(L"test.txt", GENERIC_WRITE
        , 0NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    //CREATE_ALWAYS : 파일 덮어씀
    if (hFile != NULL)
    {
        CHAR buffer[] = "유니코드든 싱글바이트든 해석할때가 중요하다.";
        DWORD dLength = sizeof(buffer);
        DWORD dWritten;
        bool bRet = WriteFile(hFile, buffer, dLength, &dWritten, NULL);
 
        if (bRet == true)
        {
            std::wcout << L"출력성공" << std::endl;
        }
    }
    CloseHandle(hFile);
 
 
    HANDLE hFile_Read = CreateFile(L"test.txt", GENERIC_READ
        , 0NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile_Read != NULL)
    {
        WCHAR buffer[512= { 0, };
        DWORD dLength = sizeof(buffer);
        DWORD dWritten;
        bool bRet = ReadFile(hFile_Read, buffer, dLength, &dWritten, NULL);
 
        if (bRet == true)
        {
            std::wcout << L"읽어오기 성공" << std::endl;
        }
    }
    CloseHandle(hFile_Read);
}
cs

 

1. 쓰기용 핸들을 만든다. CreateFile()

 

2. 쓰기용 핸들에 파일에 쓴다. 버퍼를 넣는다. WriteFile()

 

3. 핸들을 닫는다. 읽기 ReadFile()도 유사하다.

 

4. 결과 

 

이러한 ReadFile(), WriteFile() 입출력 함수는 일반적으로

입출력이 끝날때까지 Blocked 상태를 유지한다. 

 

만약 만화책을 보고 그림을 따라 그린다고 상상을 해보자.

만화책 한번보고, 기억하고, 도화지에 그림을 그리고

다시 만화책 한번보고(Blocked) 기억하고(CPU 사용 코드) 도화지에 그림을 그리고(Blocked)....

반복하는 작업이 있다고 생각해보자.

 

 

도화지에 만화책을 중첩시켜 그림을 그리는 작업은 어떨까!

그게 바로 Overlapped(중첩) 입출력이다. 훨씬 속도가 빠르다.

스레드를 쓰지 않고도 스레드를 쓰는 효과가 난다.

 

비동기 파일 입출력 Overlapped 입출력 예시

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#define BlockSizes 2048
#include <windows.h>
#include <iostream>
 
void main()
{
    setlocale(LC_ALL, "");
    WCHAR* pwbuffer = NULL;
    LARGE_INTEGER fileSize;
    OVERLAPPED ol_Read = { 0, };
    OVERLAPPED ol_Write = { 0, };
 
    //핸들 반환 FILE_FLAG_OVERLAPPED으로 비동기 파일 입출력 
    //기존에 있는 파일을 읽어옴
    HANDLE hFile = CreateFile(L"test.JPG", GENERIC_READ | GENERIC_WRITE, 0,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    //실패하면 초기값이 잡힘
    if (hFile == INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFile);
        return;
    }
    // 핸들 반환 FILE_FLAG_OVERLAPPED으로 비동기 파일 입출력
    // 복사하는 핸들
    HANDLE hFileCopy = CreateFile(L"test_copy.JPG", GENERIC_WRITE, 0
        NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
    //실패하면 초기값이 잡힘
    if (hFileCopy == INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFileCopy);
        return;
    }
 
    //읽어온 파일 사이즈를 체크
    GetFileSizeEx(hFile, &fileSize);
 
    DWORD dwRead;
    DWORD dwWritten;
    DWORD dwTotalWrite = 0;
 
    //sector의 크기 배수 일반적으로 512
    pwbuffer = new WCHAR[BlockSizes];
    int i = 0;
    while(1)
    {
        //Temp 버퍼
        ZeroMemory(pwbuffer, BlockSizes);
        
        //비동기 로드
        //Overlapped 구조체 오프셋
        ol_Read.Offset = i * BlockSizes;
        ol_Read.OffsetHigh = 0;
 
        BOOL ret = ReadFile(hFile, pwbuffer, BlockSizes, &dwRead, &ol_Read);
        BOOL bPending = FALSE;
        if (ret == FALSE)
        {
            DWORD dwError = GetLastError();
            if (dwError == ERROR_IO_PENDING)
            {
                std::wcout << L"읽기 중" << std::endl;
            }
            else
            {
 
            }
        }
        BOOL bReturn = GetOverlappedResult(hFile, &ol_Read, &dwRead, FALSE);
        if (bReturn == TRUE)
        {
            std::wcout << L"읽기 완료" << std::endl;
            bPending = FALSE;
        }
        else
        {
            DWORD dwError = GetLastError();
            if (dwError == ERROR_IO_INCOMPLETE)
            {
                std::wcout << L".." << std::endl;
            }
            else
            {
                std::wcout << L"읽기 완료2" << std::endl;
                bPending = FALSE;
            }
        }
        //비동기 출력
        //Overlapped 구조체 오프셋
        ol_Write.Offset = i * BlockSizes;
        ol_Write.OffsetHigh = 0;
 
        ret = WriteFile(hFileCopy, pwbuffer, dwRead, &dwWritten, &ol_Write);
        if (ret == FALSE)
        {
            DWORD dwError = GetLastError();
            if (dwError == ERROR_IO_PENDING)
            {
                std::wcout << L"쓰기 중" << std::endl;
            }
            else
            {
 
            }
        }
        bReturn = GetOverlappedResult(hFileCopy, &ol_Write, &dwWritten, FALSE);
        if (bReturn == TRUE)
        {
            std::wcout << L"쓰기 완료" << std::endl;
            bPending = FALSE;
        }
        else
        {
            DWORD dwError = GetLastError();
            if (dwError == ERROR_IO_INCOMPLETE)
            {
                std::wcout << L"..";
            }
            else
            {
                std::wcout << L"쓰기 완료2" << std::endl;
                bPending = FALSE;
            }
        }
        dwTotalWrite += dwWritten;
 
        if (fileSize.LowPart == dwTotalWrite)
        {
            break;
        }
        std::wcout << (fileSize.LowPart- dwTotalWrite) << std::endl;
        i++;
    }
    CloseHandle(hFile);
    CloseHandle(hFileCopy);
}
 
cs

 

위 코드는 블록단위로 파일을 복사하는 작업을 비동기로 처리한다. 

블록단위로 읽고 복사(쓰고)를 반복한다.. 다 복사가 될때까지. 

Offset을 이용해, 블록단위로 끊어 읽고, dwRead 읽은 만큼만 Write하는 구조이다.

 

 

Overlapped 구조체를 이용하여 비동기적으로 하나 이상의 I/O 작업을 수행

할 수 있다. Overlapped 구조체 내용은 파일 포인터의 Offset과 이벤트가 있다.

 

 

오버랩 플래그를 달아준다. FILE_FLAG_OVERLAPPED

 

 

WriteFIle, ReadFile의 마지막 옵션값, 오버랩 구조체 자리에 구조체를 넣고

GetOverlappedResult의 결과를 받는다. 

 

1
2
3
4
5
6
BOOL GetOverlappedResult(
  [in]  HANDLE       hFile, // 파일 핸들
  [in]  LPOVERLAPPED lpOverlapped, // Overlapped 구조체 
  [out] LPDWORD      lpNumberOfBytesTransferred, // 전송 양
  [in]  BOOL         bWait // 기다릴지의 유무
);
cs

 

GetOverlappedResult의 마지막 인자의 이름은 bWait로 

True이면 동기작업을 하게 되고 FALSE경우 비동기 작업이된다.

비동기처리니까 당연히 예외처리를 해줘야한다. 

 

결과

 

COMMENT