<파일 I/O와 디렉터리 컨트롤>
[공부했던 것을 되짚어보며]
이전 글에서도 언급을 했었지만 이번 내용 역시 참고의 성격이 강합니다.
달리 말하면 부담갖지 말고 편하게 보면 될 내용들입니다.
책을 통해서 가볍게 보고 넘어가도 되고, 이 글에서 예시로 보여드리는 코드를 통해서 간단하게 이해만 하면 됩니다.
책의 저자분도 그랬듯이 저도 '이런 사용법이 있다' 정도로 설명하고 넘어갈 겁니다.
그리고 IPC에서 메일슬롯과 파이프를 다루면서 간략하게나마 파일 입출력에 대한 내용을 다뤘습니다.
그래서 함수에 대한 설명도 일일이 하지 않고, MS의 공식 문서를 링크로 대체하려고 합니다.
[ANSI 표준 파일 입출력 함수]
본 주제로 들어가기에 앞서서 이 주제는 C언어를 배우면서 파일 입출력에 대해 알고 있다는 전제하에 작성됩니다.
ANSI 표준 파일 입출력 함수에 대한 이야기를 조금만 다루고 넘어가도록 하겠습니다.
여러분들이 알고 있는 fopen, fclose, fputs, fgets 등등의 함수들이 ANSI 표준 파일 입출력 함수입니다.
이 함수들은 아시다시피 Windows, Linux, Unix와 같은 모든 운영체제에서 동일하게 사용이 가능한 함수입니다.
다시 말해서 OS에 의존적이지 않은 함수들이죠.
어떻게 보면 ANSI 표준 함수를 사용한다는 것은 이식성이 좋다는 장점이 있다고 볼 수 있겠습니다.
우리가 사용하는 이 ANSI 표준 함수를 통해 파일 입출력을 하게 되면 파일이 생성되고 하드디스크에 저장됩니다.
그리고 이 파일이 저장되는 방식은 OS의 파일 시스템에 의존적이게 됩니다.
다시 말해서 파일 시스템은 OS의 일부이며, OS에서 구현된 독립적인 하나의 시스템입니다.
이 내용을 아시는 분들은 아마 아시는 내용일겁니다.
Windows에서는 NTFS, Linux는 EXT시리즈 또는 XFS, Unix에서는 UFS라는 파일 시스템을 사용하죠.
그래서 잘 생각해보면 ANSI 표준 함수를 통한 파일 입출력을 하게 되더라도 실제 파일을 만드는 대상은 OS입니다.
실제로 ANSI 표준 함수를 호출하게 되면 OS별로 제공되는 시스템 함수를 실행하게 됩니다.
결국 ANSI 표준 파일 입출력 함수는 OS들이 제공하는 공통적인 기능을 묶어서 표준으로 만들고 제공한다는 것입니다.
ANSI 표준 파일 입출력 함수는 OS에 종속적이지 않기 때문에 이식성이 좋다는 장점이 있습니다.
또한 간편하게 사용할 수 있다는 장점도 있고요.
하지만 OS에 종속적이어야 하는 상황이거나 세밀하게 제어를 해야하는 경우도 있습니다.
이럴 때는 ANSI 표준 함수가 답이 될 수 없습니다.
그런 상황에서 사용하는 것이 OS의 시스템 함수라는 것을 알아두시면 좋을 것 같습니다.
[기본적인 파일 처리 함수들]
앞서 설명했듯이 ANSI 표준 파일 입출력 함수를 알고 있다는 가정을 두고 진행하는 내용입니다.
그리고 OS에 종속적이어야 하는 상황이거나 세밀하게 제어를 해야하는 경우에 시스템 함수를 사용한다고 했고요.
당연히 Windows에서는 파일과 관련된 시스템 함수들을 제공합니다.
ANSI 표준 함수에서 정의하는 것보다 많은 기능을 제공하기도 하고요.
그저 '단순 입출력만 수행한다'라고 하면 ANSI 표준 파일 입출력 함수를 사용하는 것이 오히려 나을 수 있습니다.
하지만 그런 상황이 아니라면, 그리고 Windows에서 개발을 하신다면 시스템 함수를 사용해야겠죠?
여기서는 이런 함수들이 있고, 어떻게 사용하는 지에 대한 예시를 들고 끝내려고 합니다.
[파일 열기 & 닫기]
파일을 열고 닫을 때는 CreateFile이라는 함수를 사용합니다.
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
"CreateFile 함수라면서 어떤건 A가 붙어있고 어떤건 W가 붙어있대요?"
저도 처음에는 검색하면서 찾아보니까 CreateFile 함수를 찾아보면 저렇게 따로따로 나옵니다.
그리고 대표적으로는 A라는 놈을 보여주더라고요.
여기서는 A라는 놈도 있고 W라는 놈도 있는데, 둘 다 가만히 놓고 보면 함수 인자는 똑같습니다.
A는 ASCII 코드 기반의 함수, W는 유니코드 기반의 함수를 사용할 때 호출하는 함수입니다.
실제로 우리가 프로그램 코드를 작성할 때는 그냥 CreateFile이라고만 써도 됩니다.
만약 ASCII 코드를 기반으로 코드를 작성했다고 가정해봅시다.
내부적으로는 ASCII 코드를 유니코드로 바꿔서 유니코드 함수를 호출합니다.
실제 흐름은 CreateFileA → ASCII를 유니코드로 변경 → CreateFileW가 됩니다.
그리고 반환형을 보시면 HANDLE이라고 되어있습니다.
앞서 말했던 것처럼 파일 역시 OS에서 관리하는 커널 오브젝트라고 볼 수 있습니다.
그래서 파일을 종료할 때에는 CloseHandle 함수를 통해서 종료하면 됩니다.
파일을 개방하면 커널 오브젝트가 생성이 되고, CreateFile 함수를 통해서 핸들이 반환이 됩니다.
그리고 개방한 파일의 커널 오브젝트에는 파일과 관련된 정보들이 가득 차있겠죠.
우리가 ANSI 표준 입출력 함수를 통해서 FILE*를 이용한 개방을 할 때도 똑같습니다.
파일 포인터에 파일에 대한 정보가 가득 차있는 것과 동일합니다.
[파일 읽기와 쓰기]
파일의 데이터를 읽을 때에는 ReadFile이라는 함수를 사용하게 됩니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-readfile
반대로 파일에 데이터를 저장할 때에는 WriteFile이라는 함수를 사용합니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-writefile
[파일을 열어서 읽고 쓰고 닫는 예제]
위의 세 함수를 실제로 사용한 예제 코드입니다.
[UNICODE_BASE_FILE_IO.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: UNICODE_BASE_FILE_IO.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 유니코드 기반의 파일 입출력 예제(1)
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("data.txt");
TCHAR fileData[] = TEXT("Test String");
HANDLE hFile = CreateFile(
fileName, // 개방(Open)할 파일 이름을 지정
GENERIC_WRITE, // 읽기/쓰기 모드를 지정. 여기서는 쓰기 모드. or(|) 연산으로 결합 가능
FILE_SHARE_WRITE, // 파일의 공유 모드를 지정
0, // 보안(상속) 속성 지정
CREATE_ALWAYS, // 파일이 생성 또는 읽을 때 사용하는 인자
FILE_ATTRIBUTE_NORMAL, // 파일의 특성 정보 지정. 일반 파일이라면 FILE_ATTRIBUTE_NORMAL을 통상적으로 사용
0 // 기존에 존재하는 파일과 동일한 특성을 가지는 새 파일을 만들 때 사용하는 인자. 일반적으로 NULL 전달.
);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Creation Failed\n"));
return -1;
}
DWORD numOfByteWritten = 0;
WriteFile(
hFile, // 데이터를 저장할 파일의 핸들을 지정
fileData, // 데이터를 저장하고 있는 버퍼의 주소를 지정
sizeof(fileData), // 파일에 저장하고자 하는 데이터 크기를 바이트 단위로 지정
&numOfByteWritten, // 실제로 저장된 데이터 크기를 얻기 위한 변수의 주소 지정
NULL // OverLapped 관련, 비동기 I/O와 관련
);
_tprintf(TEXT("Written data size: %u\n"), numOfByteWritten);
CloseHandle(hFile);
return 0;
}
[UNICODE_BASE_FILE_READ.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: UNICODE_BASE_FILE_READ.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 유니코드 기반의 파일 입출력 예제(2)
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STRING_LEN 100
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("data.txt");
TCHAR fileData[STRING_LEN];
HANDLE hFile = CreateFile(
fileName, // 개방(Open)할 파일 이름을 지정
GENERIC_READ, // 읽기/쓰기 모드를 지정. 여기서는 읽기 모드. or(|) 연산으로 결합 가능
FILE_SHARE_READ, // 파일의 공유 모드를 지정
0, // 보안(상속) 속성 지정
OPEN_EXISTING, // 파일이 생성 또는 읽을 때 사용하는 인자
FILE_ATTRIBUTE_NORMAL, // 파일의 특성 정보 지정. 일반 파일이라면 FILE_ATTRIBUTE_NORMAL을 통상적으로 사용
0 // 기존에 존재하는 파일과 동일한 특성을 가지는 새 파일을 만들 때 사용하는 인자. 일반적으로 NULL 전달.
);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Creation Failed\n"));
return -1;
}
DWORD numOfByteRead = 0;
ReadFile(
hFile, // 데이터를 저장할 파일의 핸들을 지정
fileData, // 데이터를 저장하고 있는 버퍼의 주소를 지정
sizeof(fileData), // 파일에 저장하고자 하는 데이터 크기를 바이트 단위로 지정
&numOfByteRead, // 실제로 저장된 데이터 크기를 얻기 위한 변수의 주소 지정
NULL // OverLapped 관련, 비동기 I/O와 관련
);
fileData[numOfByteRead / sizeof(TCHAR)] = '\0'; // 문자열로 읽어들이기 위해 마지막에 널 문자 입력
_tprintf(TEXT("Read data size: %u\n"), numOfByteRead);
_tprintf(TEXT("Read String: %s\n"), fileData);
CloseHandle(hFile);
return 0;
}
[파일의 시간 정보 얻어오기]
이번에는 파일의 정보 가운데 시간 정보를 얻어오는 함수입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfiletime
여기서 하나만 짚고 넘어갈 부분은, 시간은 UTC 기반으로 시간 정보를 돌려준다는 것입니다.
UTC는 세계 표준 시간입니다.
그래서 우리가 원하는 시간 정보를 알기 위해서는 적절한 정보 변경이 필요합니다.
예제 코드를 통해서 이를 확인해보겠습니다.
[FileTimeInformation.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: FileTimeInformation.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 파일의 시간 정보를 확인하는 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STRING_LEN 100
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("data.txt"); // 확인할 파일 이름
// 파일의 시간 정보를 담을 버퍼
TCHAR fileCreateTimeInfo[STRING_LEN]; // 생성 시간
TCHAR fileAccessTimeInfo[STRING_LEN]; // 파일에 마지막으로 접근한 시간
TCHAR fileWriteTimeInfo[STRING_LEN]; // 파일에 마지막으로 데이터를 갱신(덮어 쓰기 포함)한 시간
// 파일의 시간 정보를 담을 FILETIME 구조체(시간 정보를 나타내는 8바이트(DWORD * 2) 자료형) 선언
FILETIME ftCreate, ftAccess, ftWrite;
// FILETIME 구조체의 시간은 UTC를 기준으로 설정, 시스템의 기준 시각으로 맞추기 위해 변수 지정
SYSTEMTIME stCreateUTC, stCreateLocal;
SYSTEMTIME stAccessUTC, stAccessLocal;
SYSTEMTIME stWriteUTC, stWriteLocal;
HANDLE hFile = CreateFile(
fileName, // 개방(Open)할 파일 이름을 지정
GENERIC_READ, // 읽기/쓰기 모드를 지정. 여기서는 읽기 모드. or(|) 연산으로 결합 가능
FILE_SHARE_READ, // 파일의 공유 모드를 지정
0, // 보안(상속) 속성 지정
OPEN_EXISTING, // 파일이 생성 또는 읽을 때 사용하는 인자
FILE_ATTRIBUTE_NORMAL, // 파일의 특성 정보 지정. 일반 파일이라면 FILE_ATTRIBUTE_NORMAL을 통상적으로 사용
0 // 기존에 존재하는 파일과 동일한 특성을 가지는 새 파일을 만들 때 사용하는 인자. 일반적으로 NULL 전달.
);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Creation Failed\n"));
return -1;
}
// 파일의 시간 정보 추출
if (!GetFileTime(hFile, &ftCreate, &ftAccess, &ftWrite))
{
// 시간 정보 추출 실패 시
_tprintf(TEXT("GetFileTime Function call fault\n"));
return FALSE;
}
// 추출한 시간 정보를 변환
// 추출한 시간 정보를 SYSTEMTIME 구조체에 옮긴다.
// 그리고 SYSTEMTIME 구조체에 있는 정보를 현재 시스템(Local) 기준으로 바꾸는 과정
FileTimeToSystemTime(&ftCreate, &stCreateUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stCreateUTC, &stCreateLocal);
FileTimeToSystemTime(&ftAccess, &stAccessUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stAccessUTC, &stAccessLocal);
FileTimeToSystemTime(&ftWrite, &stWriteUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stWriteUTC, &stWriteLocal);
// 시간 정보 출력
// sprintf 함수 -> strcat과 유사하나, 형식에 맞춰서 문자열을 연결해준다.
// 첫 번째 인자에는 저장할 버퍼(배열)을 지정.
// 여기서는 _s가 붙은 것을 사용했는데, 두 번째 인자에는 버퍼의 크기(길이)를 지정
// 무심코 sizeof(버퍼이름)을 넣으면 예제 기준으로는 200이 잡힌다.
// 그래서 TCHAR만큼 나눠야 100이 나옴.
_stprintf_s(fileCreateTimeInfo, sizeof(fileCreateTimeInfo)/sizeof(TCHAR), TEXT("%02d/ %02d / %02d \t %02d:%02d:%02d"),
stCreateLocal.wMonth, stCreateLocal.wDay, stCreateLocal.wYear,
stCreateLocal.wHour, stCreateLocal.wMinute, stCreateLocal.wSecond);
_stprintf_s(fileAccessTimeInfo, sizeof(fileAccessTimeInfo) / sizeof(TCHAR), TEXT("%02d/ %02d / %02d \t %02d:%02d:%02d"),
stAccessLocal.wMonth, stAccessLocal.wDay, stAccessLocal.wYear,
stAccessLocal.wHour, stAccessLocal.wMinute, stAccessLocal.wSecond);
_stprintf_s(fileWriteTimeInfo, sizeof(fileWriteTimeInfo) / sizeof(TCHAR), TEXT("%02d/ %02d / %02d \t %02d:%02d:%02d"),
stWriteLocal.wMonth, stWriteLocal.wDay, stWriteLocal.wYear,
stWriteLocal.wHour, stWriteLocal.wMinute, stWriteLocal.wSecond);
_tprintf(TEXT("File Created Time: %s\n"), fileCreateTimeInfo);
_tprintf(TEXT("File Accessed Time: %s\n"), fileAccessTimeInfo);
_tprintf(TEXT("File Written Time: %s\n"), fileWriteTimeInfo);
CloseHandle(hFile);
return 0;
}
주석에 충분히 설명을 달아뒀습니다.
그리고 SYSTEMTIME이라는 구조체에 대한 정보는 여기서 확인하시면 됩니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/minwinbase/ns-minwinbase-systemtime
[파일 사이즈 얻어오기]
ANSI 표준 함수를 이용하여 파일의 크기를 얻어오는 방법은 아래와 같습니다.
FILE* fp = fopen("test.dat", "rb");
fseek(fp, 0, SEEK_END);
DWORD sizeOfFile = ftell(fp);
fseek 함수를 통해 파일 포인터를 끝으로 옮기고 ftell 함수를 통해 현재 위치 정보를 얻어오는 식이었죠.
Windows 시스템 함수는 이런 과정을 거칠 필요가 없이 파일 크기를 직접 계산해서 반환해줍니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfilesize
단, 위 함수는 4GB 이상의 파일에 대해서는 상위 4바이트와 하위 4바이트를 다른 경로를 통해서 얻어야 합니다.
그래서 위의 함수보다는 GetFileSizeEx라는 함수를 사용하는 것을 권장합니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfilesizeex
다음은 예제 코드를 예시로 들겠습니다.
가능하다면 파일 하나는 4GB 이상의 파일을 하나 읽어들여보시는 것을 권장드립니다.
[GetFileSize.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: GetFileSize.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 파일의 크기 정보를 확인하는 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STRING_LEN 100
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName1[] = TEXT("data.txt"); // 확인할 파일 이름
TCHAR fileName2[] = TEXT("Over4GB.mkv"); // 확인할 파일 이름, 4GB 이상
HANDLE hFile1 = CreateFile(
fileName1, // 개방(Open)할 파일 이름을 지정
GENERIC_READ, // 읽기/쓰기 모드를 지정. 여기서는 읽기 모드. or(|) 연산으로 결합 가능
FILE_SHARE_READ, // 파일의 공유 모드를 지정
0, // 보안(상속) 속성 지정
OPEN_EXISTING, // 파일이 생성 또는 읽을 때 사용하는 인자
FILE_ATTRIBUTE_NORMAL, // 파일의 특성 정보 지정. 일반 파일이라면 FILE_ATTRIBUTE_NORMAL을 통상적으로 사용
0 // 기존에 존재하는 파일과 동일한 특성을 가지는 새 파일을 만들 때 사용하는 인자. 일반적으로 NULL 전달.
);
HANDLE hFile2 = CreateFile(
fileName2, // 개방(Open)할 파일 이름을 지정
GENERIC_READ, // 읽기/쓰기 모드를 지정. 여기서는 읽기 모드. or(|) 연산으로 결합 가능
FILE_SHARE_READ, // 파일의 공유 모드를 지정
0, // 보안(상속) 속성 지정
OPEN_EXISTING, // 파일이 생성 또는 읽을 때 사용하는 인자
FILE_ATTRIBUTE_NORMAL, // 파일의 특성 정보 지정. 일반 파일이라면 FILE_ATTRIBUTE_NORMAL을 통상적으로 사용
0 // 기존에 존재하는 파일과 동일한 특성을 가지는 새 파일을 만들 때 사용하는 인자. 일반적으로 NULL 전달.
);
if (hFile1 == INVALID_HANDLE_VALUE && hFile2 == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Creation Failed\n"));
return -1;
}
// 32비트 환경 기준
// 파일 하나가 4GB 미만일 경우 (2^2 * 2^10 * 2^10 * 2^10)
// 4byte(32bit)만 가지고 표현이 가능 (0~2^32-1)
// 파일이 4GB 이상일 경우 상위 4바이트 정보가 GetFileSize의 2번째 인자로 전달
DWORD high4ByteFileSize = 0; // 사실상 NULL, 4GB 이상이면 여기에 상위 4바이트 정보가 전달된다
DWORD low4ByteFileSize = GetFileSize(hFile1, &high4ByteFileSize); // 4GB 미만의 경우 GetFileSize
_tprintf(TEXT("High 4byte file size: %u byte\n"), high4ByteFileSize);
_tprintf(TEXT("Low 4byte file size: %u byte\n"), low4ByteFileSize);
// 64비트 환경 기준
// 파일 하나가 4GB 이상일 경우
// 4byte(32bit)만 가지고는 표현이 불가능
// LARGE_INTER는 8byte 공용체(Union)
// 4GB 이상이면 8byte(64bit)를 사용하여 LowPart + HighPart까지 사용 (0~2^64-1)
// 4GB 미만까지는 LowPart, 4GB 이상부터는 HighPart를 사용
// 그래서 전체 용량은 QuadPart를 보면 알 수 있음
LARGE_INTEGER fileSize;
GetFileSizeEx(hFile2, &fileSize); // 4GB 이상일 경우 GetFileSizeEx
_tprintf(TEXT("Total file size: %llu byte\n"), fileSize.QuadPart);
CloseHandle(hFile1);
CloseHandle(hFile2);
return 0;
}
[파일의 특성(Attribute) 정보 얻어오기]
우리가 파일을 오른쪽 마우스로 클릭해서 속성을 보면 읽기 전용, 숨김, 보관 등과 같은 속성등이 있습니다.
이런 정보들을 파일의 특성이라고 하며 특성 정보를 얻어오는 데에 사용하는 함수는 GetFileAttributes입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfileattributesa
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfileattributesw
반대로 파일의 특성을 설정하게 되는 경우에는 SetFileAttributes 함수를 쓰면 됩니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-setfileattributesa
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
마찬가지로 A와 W가 따로 있습니다만, CreateFile에서 설명했던 것과 동일합니다.
예제 코드를 통해서 위의 두 함수를 사용하는 것을 보겠습니다.
[Set_Get_FileAttributes.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: Set_Get_FileAttributes.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 파일의 특성 정보 참조 및 변경하는 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
void ShowAttributes(DWORD attrib);
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("data.txt"); // 확인할 파일 이름
_tprintf(TEXT("Original file attributes\n"));
DWORD attrib = GetFileAttributes(fileName);
ShowAttributes(attrib);
// OR 연산(|)을 통해 파일의 속성을 변경
attrib |= (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN);
// 변경된 속성을 적용
SetFileAttributes(fileName, attrib);
_tprintf(TEXT("Changed file attributes\n"));
attrib = GetFileAttributes(fileName);
ShowAttributes(attrib);
return 0;
}
void ShowAttributes(DWORD attrib)
{
// AND(&) 연산을 통해 파일의 속성을 비교한다.
// 일반 파일이라면 다른 특성이 없는 파일을 뜻한다.
// OS에서는 기본적으로 파일을 FILE_ATTRIBUTE_NORMAL로 생성해도 FILE_ATTRIBUTE_ARCHIVE 속성을 부여한다.
// 그래서 아래 속성은 나오지 않는다.
if (attrib & FILE_ATTRIBUTE_NORMAL)
_tprintf(TEXT("FILE_ATTRIBUTE_NORMAL OR FILE_ATTRIBUTE_ARCHIVE\n"));
else
{
// 읽기 전용 속성이 있을 경우
if (attrib & FILE_ATTRIBUTE_READONLY)
_tprintf(TEXT("FILE_ATTRIBUTE_READONLY\n"));
// 숨김 파일 속성이 있을 경우
if (attrib & FILE_ATTRIBUTE_HIDDEN)
_tprintf(TEXT("FILE_ATTRIBUTE_HIDDEN\n"));
}
_tprintf(TEXT("\n"));
}
조금 독특한 부분이 있다면 위 함수는 파일의 특성 정보를 핸들이 아닌 파일의 이름을 사용했다는 점입니다.
[파일의 특성(Attribute) 정보 핸들로부터 얻어오기 + α]
우리가 실제로 시스템 프로그래밍을 공부하면서 주로 다뤘던 것은 핸들입니다.
그래서 핸들을 통해서도 파일의 특성 정보를 얻어올 수 있지 않을까 하는 생각이 들 수도 있습니다.
당연히 가능합니다.
함수의 이름도 굉장히 정직하고요.
GetFileInformationByHandle이라는 함수입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfileinformationbyhandle
여기서 BY_HANDLE_FILE_INFORMATION이라는 구조체가 있습니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/ns-fileapi-by_handle_file_information
GetFileInformationByHandle이라는 함수를 통해 위의 구조체가 채워지게 됩니다.
그리고 구조체를 보면 아시겠지만 특성 정보 외에도 다양한 정보들이 채워집니다.
핸들을 사용해서 파일의 정보를 얻어오게 된다면 이 함수 하나면 거의 다 해결될 수 있다고 보면 됩니다.
[GetFileInformationByHandle.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: GetFileInformationByHandle.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 핸들을 이용한 파일의 정보 참조 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STRING_LEN 100
void ShowAttributes(DWORD attrib);
void ShowFileTime(FILETIME t);
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("data.txt"); // 확인할 파일 이름
BY_HANDLE_FILE_INFORMATION fileInfo;
HANDLE hFile = CreateFile(
fileName,
GENERIC_READ,
NULL,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Open Failed\n"));
return -1;
}
GetFileInformationByHandle(hFile, &fileInfo);
ShowAttributes(fileInfo.dwFileAttributes);
// 특성 이외의 추가로 볼 수 있는 정보
// 4GB 미만 파일
_tprintf(TEXT("File Size: %u\n"), fileInfo.nFileSizeLow);
_tprintf(TEXT("File Created Time: "));
ShowFileTime(fileInfo.ftCreationTime);
_tprintf(TEXT("File Accessed Time: "));
ShowFileTime(fileInfo.ftLastAccessTime);
_tprintf(TEXT("File Written Time: "));
ShowFileTime(fileInfo.ftLastWriteTime);
CloseHandle(hFile);
return 0;
}
void ShowAttributes(DWORD attrib)
{
// AND(&) 연산을 통해 파일의 속성을 비교한다.
// 일반 파일이라면 다른 특성이 없는 파일을 뜻한다.
// OS에서는 기본적으로 파일을 FILE_ATTRIBUTE_NORMAL로 생성해도 FILE_ATTRIBUTE_ARCHIVE 속성을 부여한다.
// 그래서 아래 속성은 나오지 않는다.
if (attrib & FILE_ATTRIBUTE_NORMAL)
_tprintf(TEXT("FILE_ATTRIBUTE_NORMAL OR FILE_ATTRIBUTE_ARCHIVE\n"));
else
{
// 읽기 전용 속성이 있을 경우
if (attrib & FILE_ATTRIBUTE_READONLY)
_tprintf(TEXT("FILE_ATTRIBUTE_READONLY\n"));
// 숨김 파일 속성이 있을 경우
if (attrib & FILE_ATTRIBUTE_HIDDEN)
_tprintf(TEXT("FILE_ATTRIBUTE_HIDDEN\n"));
}
_tprintf(TEXT("\n"));
}
void ShowFileTime(FILETIME t)
{
TCHAR fileTimeInfo[STRING_LEN];
FILETIME ft = t;
SYSTEMTIME stUTC, stLocal;
FileTimeToSystemTime(&ft, &stUTC);
SystemTimeToTzSpecificLocalTime(NULL, &stUTC, &stLocal);
_stprintf_s(fileTimeInfo, sizeof(fileTimeInfo) / sizeof(TCHAR), TEXT("%02d/ %02d / %02d \t %02d:%02d:%02d"),
stLocal.wMonth, stLocal.wDay, stLocal.wYear,
stLocal.wHour, stLocal.wMinute, stLocal.wSecond);
_tprintf(TEXT("%s\n"), fileTimeInfo);
}
[파일의 경로(Path) 정보 얻어오기]
이제 파일 관련된 함수에 대한 내용이 거의 끝자락에 왔습니다.
사실 설명할 내용도 거의 없다보니 저도 예제만 쭉 나열하는 느낌이긴 합니다.
이번에는 파일의 경로 정보를 얻어야 할 때 사용하는 함수입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-getfullpathnamea
https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getfullpathnamew
[GetFullPathName.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: GetFullPathName.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 파일이름을 기반으로 완전경로를 확인하는 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#include <locale.h>
int _tmain(int argc, TCHAR* argv[])
{
_wsetlocale(LC_ALL, L"korean");
TCHAR fileName[] = TEXT("data.txt");
TCHAR fileFullPathName[MAX_PATH];
LPTSTR filePtr;
GetFullPathName(
fileName,
MAX_PATH,
fileFullPathName,
&filePtr
);
_tprintf(TEXT("%s\n"), fileFullPathName);
_tprintf(TEXT("%s\n"), filePtr);
return 0;
}
[파일 포인터의 이동 - 32비트 기반]
이번에 다루는 내용만 조금 신경을 써서 정리를 하려고 합니다.
fseek 함수와 같이 파일의 포인터를 이동하는 함수인 SetFilePointer라는 함수입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-setfilepointer
여기서 32비트와 64비트를 따로 갈라놓게 되었는데, 4GB가 넘지 않는 파일에 한해서는 위 함수를 사용하면 됩니다.
[SetFilePointer_32BIT_VERSION.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: SetFilePointer_32BIT_VERSION.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 파일 포인터의 이동 - 32비트 기반
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STRING_LEN 100
TCHAR fileData[] = TEXT("abcdefghijklmnopqrstuvwxyz");
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("abcd.dat");
TCHAR readBuf[STRING_LEN];
HANDLE hFile;
DWORD numOfByteWritten = 0;
DWORD dwPtr = 0;
// File Write
hFile = CreateFile(fileName, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, NULL, NULL);
WriteFile(hFile, fileData, sizeof(fileData), &numOfByteWritten, NULL);
CloseHandle(hFile);
// File Read
hFile = CreateFile(fileName, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
ReadFile(hFile, readBuf, sizeof(readBuf), &numOfByteWritten, NULL);
_tprintf(TEXT("%s\n"), readBuf);
// 파일 포인터를 맨 앞으로 이동
dwPtr = SetFilePointer(hFile, sizeof(TCHAR) * 4, NULL, FILE_BEGIN);
if (dwPtr == INVALID_SET_FILE_POINTER)
{
_tprintf(TEXT("SetFilePointer Error\n"));
return -1;
}
ReadFile(hFile, readBuf, sizeof(readBuf), &numOfByteWritten, NULL);
_tprintf(TEXT("%s\n"), readBuf);
// 파일 포인터를 맨 뒤로 이동
dwPtr = SetFilePointer(hFile, sizeof(TCHAR) * -4, NULL, FILE_END);
if (dwPtr == INVALID_SET_FILE_POINTER)
{
_tprintf(TEXT("SetFilePointer Error\n"));
return -1;
}
ReadFile(hFile, readBuf, sizeof(readBuf), &numOfByteWritten, NULL);
_tprintf(TEXT("%s\n"), readBuf);
CloseHandle(hFile);
return 0;
}
32비트 Windows 환경에서 파일의 크기는 최대 (4GB-2)바이트를 가질 수 있습니다.
원래 32비트면 표현할 수 있는 값은 $0 ~ 2^{32}-1$ 까지입니다.
그래서 파일 하나에 (4GB -1)바이트까지 표현할 수 있어야 하는게 맞지 않느냐 할 수 있습니다.
그런데 INVALID_SET_FILE_POINTER라는 놈이 가지고 있는 값이 -1입니다.
이 -1을 32비트로 표현하면 0xFFFFFFFF이 됩니다.
(2의 보수 개념을 모르면 이 개념이 어려울 수 있습니다.)
이 값이 $2^{32}-1$이 되죠.
그래서 파일의 끝으로 이동했을 때 0xFFFFFFFF를 가리키게 되면?
-1이 되기 때문에 반환되는 값과 INVALID_SET_FILE_POINTER와 구분할 방법이 없습니다.
32비트 Windows에서는 크기가 정확히 4GB이상인 파일부터 대용량 파일로 간주하게 됩니다.
그렇기 때문에 32비트 환경에서는 파일 하나의 크기를 최대 (4GB-2)바이트로 한 것입니다.
[파일 포인터의 이동 - 64비트 기반]
이번에는 64비트 기반의 파일 포인터 이동을 예로 들어보겠습니다.
[SetFilePointer_64BIT_VERSION.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: SetFilePointer_64BIT_VERSION.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 파일 포인터의 이동 - 64비트 기반
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STRING_LEN 100
TCHAR fileData[] = TEXT("abcdefghijklmnopqrstuvwxyz");
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("abcd.dat");
TCHAR readBuf[STRING_LEN];
HANDLE hFile;
DWORD numOfByteWritten = 0;
DWORD dwPtrLow = 0;
LONG lDistanceLow = sizeof(TCHAR) * 4;
LONG lDistanceHigh = 0;
// File Write
hFile = CreateFile(fileName, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, NULL, NULL);
WriteFile(hFile, fileData, sizeof(fileData), &numOfByteWritten, NULL);
CloseHandle(hFile);
// File Read
hFile = CreateFile(fileName, GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
ReadFile(hFile, readBuf, sizeof(readBuf), &numOfByteWritten, NULL);
_tprintf(TEXT("%s\n"), readBuf);
// 파일 포인터를 맨 앞으로 이동
// 64비트 기준 추가 내용
// SetFilePointer의 함수 호출 성공 시 파일 포인터의 하위 4바이트 정보는 반환값으로
// 상위 4바이트 정보는 세 번째 전달 인자가 가리키는 변수를 통해서 반환하게 된다
// 실제로 이 부분이 쓰일지는 모르겠으나, 텍스트 데이터로 4기가가 넘어가는 경우를 다룰 일이 있을까는 의문이다.
dwPtrLow = SetFilePointer(hFile, lDistanceLow, &lDistanceHigh, FILE_BEGIN);
if (dwPtrLow == INVALID_SET_FILE_POINTER && (GetLastError() != NO_ERROR))
{
_tprintf(TEXT("SetFilePointer Error\n"));
return -1;
}
ReadFile(hFile, readBuf, sizeof(readBuf), &numOfByteWritten, NULL);
_tprintf(TEXT("%s\n"), readBuf);
CloseHandle(hFile);
return 0;
}
여기서 주석을 길게 달아놓은 부분이 포인트입니다.
4GB 이상의 대용량 파일을 대상으로 위치 정보를 표현하기 위해서는 4바이트가 아닌 8바이트가 필요합니다.
여기서 SetFilePointer 함수를 통해 하위 4바이트 정보만 반환값으로 받게 됩니다.
그리고 세 번째 전달 인자를 통해서 상위 4바이트 정보를 받습니다.
함수에 전달되는 인자를 보시면 두 번째 하위 바이트에 대한 정보는 전달만 하는 역할을 수행합니다.
하지만 세 번째 인자는 주소값을 인자로 전달함과 동시에 값을 획득하는데 사용됩니다.
64비트 환경에서는 다음과 같이 파일 포인터 이동을 하게 되며, 꽤 번거로운 일이 됩니다.
그리고 또 하나 추가적으로 보셔야 하는 부분이 이어지는 조건문입니다.
32비트 환경에서는 GetLastError를 사용하지 않았지만, 여기서는 사용하게 되었습니다.
그 이유는 4GB 이상의 대용량 파일을 다루기 때문에 파일의 위치가 $2^{32}-1$의 위치에 놓일 수도 있습니다.
다시 말해서 정확히 딱 4GB-1바이트의 위치인 0xFFFFFFFF에 있을 때 이것을 에러로 처리하면 안됩니다.
그래서 두 가지의 조건식을 비교하여 에러인지 아닌지의 유무를 판별해야 합니다.
+ 추가 사항
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-setfilepointerex
굉장히 예전에 쓰여진 책이기에 해당 함수를 다루지 않고 넘어간 부분이 있어서 이 함수까지는 소개를 하려고 합니다.
이 함수는 위에 있는 SetFilePointer와 같이 상위 4바이트, 하위 4바이트를 나누는 번거로운 작업을 필요로 하지 않습니다.
그리고 위처럼 4GB 이상일 때의 번거로운 조건식을 따질 필요도 없게 됩니다.
개인적으로는 이 함수를 사용하는 것이 오히려 낫겠다고 생각하여 따로 소개를 해봤습니다.
가능하다면 이 함수를 사용해서 예제 코드를 바꿔보는 것도 좋을 것이라 생각합니다.
[디렉터리 관련 함수 및 그밖의 함수들]
파일 관련된 함수 정리하는 데에만 꽤 시간이 걸린 것 같습니다.
이제 디렉터리와 관련된 함수와 그 밖의 함수들에 대한 예제를 소개하고 마무리를 지어보려고 합니다.
[디렉터리의 생성과 소멸]
디렉터리의 생성과 소멸에 사용되는 함수들은 다음과 같습니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/winbase/nf-winbase-createdirectory
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-removedirectorya
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-removedirectoryw
다음은 위 함수를 사용한 예제 코드입니다.
[CreateDeleteDirectory.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: CreateDeleteDirectory.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 디렉터리 생성 및 소멸 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
// 1이면 생성, 0이면 삭제
#define CREATE_DIRECTORY 1
int _tmain(int argc, TCHAR* argv[])
{
// 상대 경로와 절대 경로
TCHAR dirRelativePath[] = TEXT("RelativePathDirectory");
TCHAR dirFullPath[] = TEXT("D:\\FullPathDirectory");
// 매크로를 이용하여 조건부 컴파일
#if CREATE_DIRECTORY
CreateDirectory(dirRelativePath, NULL);
CreateDirectory(dirFullPath, NULL);
#else
RemoveDirectory(dirRelativePath);
RemoveDirectory(dirFullPath);
#endif
return 0;
}
굉장히 단순한 코드입니다.
추가로 더 설명할 부분은 없습니다.
[현재 디렉터리, 시스템 디렉터리 그리고 Windows 디렉터리]
현재 디렉터리(Current Directory)에 대한 개념은 '프로세스의 생성과 소멸' 챕터에서 다뤘던 내용입니다.
그리고 이 개념과 더불어서 표준 검색 경로에 대한 것도 알고 있습니다.
마찬가지로 시스템 디렉터리와 Windows 디렉터리를 확인하는 예제도 보여드린 적이 있었고요.
여기서는 그걸 다시 짚어본다는 생각을 하면 될 것 같습니다.
현재 디렉터리라는 것은 단순하게 '프로그램이 로드(Load)된 디렉터리(실행파일이 존재하는 디렉터리'가 아닙니다.
정확히는 다음과 같이 알고 있으셔야 합니다.
'초기에는 프로그램이 로드(Load)된 디렉터리로 설정되며, 이후 변경할 수 있다.'
이 정도 외에는 딱히 설명을 더 할 것이 없을 것 같습니다.
현재 디렉터리를 참조하고 설정하는 함수는 다음과 같습니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/winbase/nf-winbase-getcurrentdirectory
https://learn.microsoft.com/ko-kr/windows/win32/api/winbase/nf-winbase-setcurrentdirectory
위 함수를 사용한 예제 코드는 다음과 같습니다.
[CurrentDirectory.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: CurrentDirectory.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 현재 디렉터리에 대한 개념 이해
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
#define STR_LEN 100
TCHAR fileData[] = TEXT("abcdefghijklmnopqrstuvwxyz");
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[] = TEXT("abcd.dat");
TCHAR readBuf[STR_LEN] = { 0, };
HANDLE hFileWrite = NULL;
HANDLE hFileRead = NULL;
DWORD numOfByte = 0;
// file Write
hFileWrite = CreateFile(fileName, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFileWrite == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Create Error\n"));
return -1;
}
WriteFile(hFileWrite, fileData, sizeof(fileData), &numOfByte, NULL);
CloseHandle(hFileWrite);
// 현재 디렉터리를 변경
SetCurrentDirectory(TEXT("D:\\"));
// file read
hFileRead = CreateFile(fileName, GENERIC_READ, NULL, NULL, OPEN_EXISTING, NULL, NULL);
if (hFileRead == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("File Open Error\n"));
return -1;
}
ReadFile(hFileRead, readBuf, sizeof(readBuf), &numOfByte, NULL);
_tprintf(TEXT("%s"), readBuf);
CloseHandle(hFileRead);
return 0;
}
그리고 시스템 디렉터리와 Windows 디렉터리를 참조하는 함수는 다음과 같습니다.
https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemdirectorya
https://learn.microsoft.com/ko-kr/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemdirectoryw
https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getwindowsdirectorya
https://learn.microsoft.com/ko-kr/windows/win32/api/sysinfoapi/nf-sysinfoapi-getwindowsdirectoryw
예제코드는 다음과 같습니다.
[System_Windows_Dir.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: System_Windows_Dir.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 시스템과 Windows 디렉터리 확인 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
int _tmain(int argc, TCHAR* argv[])
{
TCHAR sysPathBuf[MAX_PATH];
TCHAR winPathBuf[MAX_PATH];
GetSystemDirectory(sysPathBuf, MAX_PATH);
GetWindowsDirectory(winPathBuf, MAX_PATH);
_tprintf(TEXT("System dir: %s\n"), sysPathBuf);
_tprintf(TEXT("Windows dir: %s\n"), winPathBuf);
return 0;
}
[디렉터리에서 파일 찾기]
이제 마지막으로 소개할 내용은 디렉터리에서 파일을 찾는 것입니다.
먼저 파일의 위치 정보를 얻고자 할 때 사용하는 함수입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/processenv/nf-processenv-searchpatha
https://learn.microsoft.com/ko-kr/windows/win32/api/processenv/nf-processenv-searchpathw
위 함수는 파일 이름을 인자로 해서 해당 파일이 저장되어 있는 완전경로 정보를 얻을 때 사용합니다.
예제 코드는 다음과 같습니다.
[SearchPath.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: SearchPath.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: SearchPath 사용 예제
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
int _tmain(int argc, TCHAR* argv[])
{
TCHAR fileName[MAX_PATH];
TCHAR bufFilePath[MAX_PATH];
LPTSTR lpFilePart;
DWORD result;
_tprintf(TEXT("Insert name of the file to find: "));
_tscanf_s(TEXT("%s"), fileName, _countof(fileName)); // _countof(filename) == sizeof(fileName)/sizeof(TCHAR)
result = SearchPath(NULL, fileName, NULL, MAX_PATH, bufFilePath, &lpFilePart);
if (result == 0)
_tprintf(TEXT("File not found!\n"));
else
{
_tprintf(TEXT("File path: %s\n"), bufFilePath);
_tprintf(TEXT("File name: %s\n"), lpFilePart);
}
return 0;
}
다음은 디렉터리 내에 존재하는 파일의 목록을 출력하기 위해 사용하는 함수입니다.
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-findfirstfilea
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-findnextfilea
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-findnextfilew
https://learn.microsoft.com/ko-kr/windows/win32/api/fileapi/nf-fileapi-findclose
연결 리스트를 알고 계신다면 아마 이 함수를 사용하는 것에 큰 어려움은 없을 것이라고 생각됩니다.
그리고 반환형이 HANDLE이므로 커널 오브젝트가 생성되게 됩니다.
그래서 FindClose 함수를 통해 리소스의 반환을 할 필요가 있습니다.
마지막으로 위의 세 함수를 이용한 예제코드입니다.
[ListDirectoryFileList.cpp]
/*
* Windows System Programming - 파일 I/O와 디렉터리 컨트롤
* 파일명: ListDirectoryFileList.cpp
* 파일 버전: 0.1
* 작성자: Sevenshards
* 작성 일자: 2023-12-12
* 이전 버전 작성 일자:
* 버전 내용: 디렉터리 내에 존재하는 파일 리스트 출력
* 이전 버전 내용:
*/
#include <stdio.h>
#include <tchar.h>
#include <Windows.h>
int _tmain(int argc, TCHAR* argv[])
{
WIN32_FIND_DATA FindFileData;
HANDLE hFind = INVALID_HANDLE_VALUE;
TCHAR DirSpec[MAX_PATH];
_tprintf(TEXT("Insert target Directory: "));
_tscanf_s(TEXT("%s"), DirSpec, _countof(DirSpec));
_tcsncat_s(DirSpec, TEXT("\\*"), 3);
hFind = FindFirstFile(DirSpec, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("Invalid File Value\n"));
return -1;
}
else
{
_tprintf(TEXT("First file name is %s\n"), FindFileData.cFileName);
while (FindNextFile(hFind, &FindFileData) != 0)
{
_tprintf(TEXT("Next file name is %s\n"), FindFileData.cFileName);
}
FindClose(hFind);
}
return 0;
}