콘텐츠로 건너뛰기

HANDLE을 FILE*로 변환하는 방법

윈도우에서 파일을 처리할 때 Win32 API와 C 런타임 라이브러리(C Runtime Library, CRT)를 함께 사용하는 경우가 종종 있습니다. 이를 위해서는 Win32 API에서 생성한 HANDLE을 C 런타임의 FILE*로 변환하는 방법이 필요합니다. 이 포스팅에서 HANDLE을 FILE*로 변환하는 방법과 그 과정에서 발생할 수 있는 오류 및 주의사항을 다루고자 합니다.

HANDLEFILE*로 변환하기

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*는 각각 다른 유형의 파일 객체를 나타내며, 이에 따라 각각의 종료 함수도 다릅니다.

파일 객체 종료 함수 표

파일 생성 함수객체 유형종료 함수
CreateFileFile HandleCloseHandle
_open_osfhandleFile Descriptor_close
_fdopenFILE*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*로 변환하는 방법에 대해서 알아보았습니다. 변환하는 방법은 그리 어렵지 않으나 리소스 누수 현상이 발생할 수 있으니, 각 단계에서 리소스 생성과 종료 함수를 잘 맞게 사용해야 합니다.

참고 자료

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

목차 보기