윈도우에서 파일을 처리할 때 Win32 API와 C 런타임 라이브러리(C Runtime Library, CRT)를 함께 사용하는 경우가 종종 있습니다. 이를 위해서는 Win32 API에서 생성한 HANDLE을 C 런타임의 FILE*로 변환하는 방법이 필요합니다. 이 포스팅에서 HANDLE을 FILE*로 변환하는 방법과 그 과정에서 발생할 수 있는 오류 및 주의사항을 다루고자 합니다.
HANDLE
을 FILE*
로 변환하기
Win32 API의 CreateFile
함수로 파일 핸들을 얻고, 이를 C 표준 입출력 함수에서 사용하려면 C 런타임에서 사용 가능한 파일 디스크립터(File Descriptor)로 변환해야 합니다. 파일 디스크립터로 변환하기 위해 _open_osfhandle
함수를 사용하여 파일 디스크립터를 구하고, 그 결과를 FILE*로 연결하기 위해 _fdopen
을 사용합니다.
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <iostream>
#include <string>
int main()
{
std::wstring file_path = L"C:\\Test\\Test.txt";
// 1. CreateFile을 이용해 Win32 API의 HANDLE 생성
HANDLE file_handle = CreateFileW(file_path.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL, nullptr);
if (file_handle == INVALID_HANDLE_VALUE)
{
std::cout << "CreateFileW Failed : " << GetLastError() << "\n";
return -1;
}
// 2. HANDLE을 파일 디스크립터로 변환
int fd = _open_osfhandle((long)file_handle, _O_BINARY);
if (fd == -1)
{
std::cout << "_open_osfhandle Failed : \n";
CloseHandle(file_handle);
return -1;
}
// 3. 파일 디스크립터를 FILE*로 변환
FILE* fp = _fdopen(fd, "wb");
if (nullptr == fp)
{
std::cout << "_fdopen Failed : " << errno << "\n";
_close(fd);
return -1;
}
// 4. 파일에 데이터 쓰기
fwrite("whalec.io", 9, 1, fp);
// 5. FILE* 닫기
fclose(fp);
return 0;
}
위 코드는 Win32 API를 사용해 파일 핸들을 생성하고, 그 핸들을 _open_osfhandle
로 파일 디스크립터로 변환한 후, _fdopen
을 통해 FILE*로 변환하여 데이터를 쓰는 예제입니다.
주의사항
파일 객체를 생성하고 닫는 방식은 사용하는 함수에 따라 달라집니다. HANDLE, 파일 디스크립터, 그리고 FILE*는 각각 다른 유형의 파일 객체를 나타내며, 이에 따라 각각의 종료 함수도 다릅니다.
파일 객체 종료 함수 표
파일 생성 함수 | 객체 유형 | 종료 함수 |
---|---|---|
CreateFile | File Handle | CloseHandle |
_open_osfhandle | File Descriptor | _close |
_fdopen | FILE* | fclose |
파일을 처리할 때, 파일 객체가 어떤 방식으로 생성 되었는지에 따라 적절한 종료 함수를 사용해야 합니다. 이 과정을 제대로 처리하지 않으면 파일 리소스를 올바르게 해제하지 못해 메모리 누수나 파일 리소스 부족 오류가 발생할 수 있습니다.
잘못된 종료 함수 사용 시 발생하는 문제
아래 코드는 위와 같은 방식으로 파일을 1000번 생성하는 루프를 돌리는 예제입니다. 여기서 종료 함수의 사용에 따른 문제점을 살펴보겠습니다.
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#include <iostream>
#include <string>
int main()
{
for (int i = 0; i < 1000; i++)
{
std::cout << "Loop #" << i << "\n";
std::wstring file_path = L"C:\\Test\\Test" + std::to_wstring(i) + L".txt";
HANDLE file_handle = CreateFileW(file_path.c_str(),
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file_handle == INVALID_HANDLE_VALUE)
{
std::cout << "CreateFileW Failed : " << GetLastError() << "\n";
return -1;
}
int fd = _open_osfhandle((long)file_handle, _O_BINARY);
if (fd == -1)
{
std::cout << "_open_osfhandle Failed : \n";
CloseHandle(file_handle);
return -1;
}
FILE* fp = _fdopen(fd, "wb");
if (NULL == fp)
{
std::cout << "_fdopen Failed : " << errno << "\n";
_close(fd);
return -1;
}
fwrite("whalec.io", 9, 1, fp);
// 잘못된 종료 방식 - fclose(fp)로 해야 함
_close(fd); // 오류 발생 가능성
std::cout << "Loop #" << i << " Success \n";
}
return 0;
}
위 코드에서 50번째 라인에서 fclose
대신 _close
를 사용하면 파일 객체가 정상적으로 닫히지 않아서 errno 24 (“Too many open files”) 오류가 발생할 수 있습니다. 그 이유는 _fdopen
으로 변환된 FILE*는 파일 스트림으로 관리되며, 스트림 객체를 닫기 위해서는 반드시 fclose
를 사용해야 하기 때문입니다.
_close(fd)
는 파일 디스크립터만 닫을 뿐, FILE* 객체는 여전히 열려 있으므로 리소스가 제대로 해제되지 않습니다. 따라서, 파일을 닫을 때는 반드시 fclose(fp)
를 사용해야 합니다.
마치며
HANDLE을 FILE*로 변환하는 방법에 대해서 알아보았습니다. 변환하는 방법은 그리 어렵지 않으나 리소스 누수 현상이 발생할 수 있으니, 각 단계에서 리소스 생성과 종료 함수를 잘 맞게 사용해야 합니다.