본문 바로가기
코딩일지/C, C++

[C++] ini 파일을 통한 설정 값 쓰기, 읽기

by 퇴근희망1일차 2023. 3. 5.
반응형

Table of Contents

    Introduction


    C++은 정말 빠른 프로그램이다.

    필자는 물리학과를 다니던 학부 시절 multi-body 운동에 대한 시뮬레이션을 학부 수업에서 파이썬으로 구현하는 것을 배운 기억이 있다.

    VPython이라는 라이브러리를 이용하여 행성들의 움직임을 3D로 구현하여 다양한 초기 조건에서 행성들의 궤도를 시뮬레이션 하는 것이 해당 프로그램의 내용이었다.

    그 당시까지만해도 나는 python이 정말 빠른 프로그램이라고 생각했다.

     

    하지만 이후 점점 복잡한 시스템에 대한 시뮬레이션을 배워가면서 느낀 점은 python은 계산을 수행하는데 있어 정말 느리다는 것이다.

    C++을 통하여 조잡하지만, 동일한 multi-body 시뮬레이션을 구현했을 때 충격에 빠질 수 밖에 없었다.

    예전의 기억이라 정확하진 않겠지만, python에서 200초 넘게 걸렸던 시뮬레이션이 C++ 기반 프로그램에서는 단 2초만에 프로그램이 끝나버린 것이었다.

     

    나는 그 이후 C++에 매료되어 이후 코딩을 필요로 하는 일이 있으면 대부분의 경우 C++로 해결하고자 한다.

    물론 요즘은 머신러닝 기반의 인공신경망과 관련된 연구를 하고 있어서 어쩔 수 없이 python을 내려놓지는 못하고 있지만...

     

    서두가 너무 길었는데 C++ 코드를 기반으로 시뮬레이션을 수행하다 보면 이런 생각이 들 것이다.

     

    빌드를 다시 하지 않고 시뮬레이션 조건만 바꿔서 다시 실행하고 싶은데...

     

    이러한 것을 하고 싶다면, C++ 프로그램이 특정 텍스트 파일에서 특정 변수에 대응되는 값을 참조하여 실행되도록 코드를 작성하면 된다. 

    하지만 C++은 그렇게 편리한 언어는 아닌 것 같다.

    이를 구현하기 위해서는 파일 R/W를 수행하는 코드를 작성해야 하고, read한 값을 어떤 변수에 어떻게 대응할지 구현하는 것도 벌써 머리가 아프다.

     

    이런 벌써부터 머리가 아파오는 복잡한 동작을 C++에서는 ini 파일을 통해서 간단하게 수행할 수 있다.

     

    ini 파일이란 무엇인가?


    ini 파일이란 무엇일까?

    ini 파일 자체는 큰 의미가 없을 수 있다. 단순히 확장자가 .ini인 파일이기 때문이다.

    그렇담 ini 파일은 도데체 무엇이길래 이렇게 앞에 장황한 설명을 해가면서까지 뭔가를 작성하려고 하는 것일까?

     

    ini 파일은 크게 "section"과 "key", "value"로 데이터가 구분이 된다.

    [section name1]
    key_name1 = value1
    key_name2 = value2

    [section name2]
    key_name1 = value1
    key_name2 = value2

    ini 파일 내부 값은 위의 예시와 같은 양식에 맞춰 작성된다.

     

    특정 "key" 값에 어떠한 "value"가 대응된다.

    이 때, "key" 값은 어떤 "section"에 속해있냐에 따라서 동일한 "key"를 갖지만(예시의 key_name1, key_name2) 서로 독립적인 다른 값을 나타내게 된다.

     

    즉, "key"는 "section"이라는 카테고리 안에 있는 항목들이라고 볼 수 있다.

     

    이렇게 section, key, value로 구분되는 ini파일, C++에서 도데체 어떻게 사용되는 것일까?

     

    사용법


    C++에서는 ini 파일을 편하게 읽고 쓸 수 있도록 함수가 존재한다.

    해당 함수들은 <Windows.h> 헤더 파일에 포함되어 있는 함수들이다.

     

    ini 파일을 쓸때는 아래의 함수를 사용한다.

    WritePrivateProfileString(
        LPCSTR lpAppName,  --- Section 이름
        LPCSTR lpKeyName,  --- Key이름
        LPCSTR lpString,        --- Value
        LPCSTR lpFileName   --- ini 파일 이름
    );

     

    ini 파일에 적힌 값을 읽을 때는 아래의 함수를 사용한다

    GetPrivateProfileString(
        LPCTSTR lpAppName,        --- Section 이름
        LPCTSTR lpKeyName,        --- Key이름
        LPCTSTR lpDefault,            --- Value 기본 값
        LPTSTR lpReturnedString,  --- ini에 적힌 값이 들어갈 버퍼
        DWORD nSize,                    --- 버퍼 사이즈
        LPCTSTR lpFileName          --- ini 파일 이름
    );

     

    이 함수들을 사용하여 ini 파일에 R/W를 수행할 수 있다.

     

     

    필자는 ini 파일 R/W를 수행하는 클래스를 만들어서 다른 클래스들에 상속시켜 사용하는 방식으로 사용한다.

    다음은 실제 사용하는 예시이다.

    매우 복잡한 코드는 아니기 때문에 이 글을 읽는 다른 사람들도 참고하여 도움을 얻었으면 한다.

     

    INI_mange.h

    class INI_manage
    {
    public:
        void Save(const char _section[], const char _key[], float _value, const char _file[]);
        float Load(const char _section[], const char _key[], float _def_value, const char _file[]);
    };

     

    INI_mange.cpp

    void INI_manage::Save(const char _section[], const char _key[], float _value, const char _file[])
    {
        char value_string[30] = "";
        sprintf_s(value_string, "%e", _value);
        WritePrivateProfileString(_section, _key, value_string, _file);
    }

    float INI_manage::Load(const char _section[], const char _key[], float _def_value, const char _file[])
    {
        char* cBuf = NULL;
        cBuf = (char*)malloc(sizeof(char) * 256);

        char value_string[30] = "";
        sprintf_s(value_string, "%f", _def_value);
        GetPrivateProfileString(_section, _key, value_string, cBuf, 256, _file);

        if (cBuf == NULL)   return -1;
        else                return atof(cBuf);
    }

    INI_mange 클래스의 Save()와 Load() 메서드를 이용하여 ini파일 R/W를 수행한다.

    Load() 메서드의 _def_value는 읽고자 하는 section의 key 값이 없을 경우 취하는 기본 값이다.

     

     

    만일 특정 클래스에서 위의 클래스를 상속받아 다음과 같이 메서드를 실행시킨다고 생각해보자.

    Save("Test Section1", "Test1", 0.5, "Test.ini");
    Save("Test Section1", "Test2", 0.7, "Test.ini");
    Save("Test Section2", "Test1", 1.2, "Test.ini");

    이 경우 "Test.ini"라는 파일이 생성되고, 해당 파일의 내용은 다음과 같이 작성될 것이다.

    [Test Section1]
    Test1= 5.000000e-1
    Test2= 7.000000e-1

    [Test Section2]
    Test1= 1.200000e-0

    이후, ini 파일에 저장된 값을 읽고 싶다면 INI_manage 클래스의 Load() 메서드를 이용하면 된다.

     

    사용 예시는 다음과 같다.

    float Section1_Test1_Value1 = Load("Test Section1", "Test1", 0, "Test.ini");
    float Section1_Test2_Value2 = Load("Test Section1", "Test2", 0, "Test.ini");
    float Section2_Test1_Value1 = Load("Test Section2", "Test1", 0, "Test.ini");

    이렇게 할 경우, 각 변수에 앞서 저장된 값들이 로드가 된다.

     

    이를 이용하여 즐거운 시뮬레이션 생활을 영위할 수 있다,,

    반응형

    댓글