65.9K
CodeProject 正在变化。 阅读更多。
Home

IniWorker

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.86/5 (6投票s)

2011年2月22日

CPOL

6分钟阅读

viewsIcon

45315

downloadIcon

507

另一个但稍有不同的 INI 文件解析器。

pic

IniWorker 类简介

这是我的 INI 文件解析类。好的,等等,好吗?在你说:“我的天,又一个 INI 解析器,我给一票”之前先等等。嗯,无论如何,如果你想这么做,请便。在我开始之前再说几句。

  1. 我给你一个 C++ 类。但是,类本身没有使用任何 C++ 函数。
  2. 有一个理由称之为 C++

    class IniWorker
      {
      protected:
      private:
      public:
      IniWorker(){}
      ~IniWorker(){}
      };

    对我来说——它就是 C,只有 C(带类)。五行代码并不能定义一个东西它不是什么。它就是什么。

  3. 你可以轻松地将整个东西变成常规的 C,没有任何问题。
  4. 我知道市面上已经有很多(或略多于一些)INI 解析器了,而且我知道微软建议不要再使用 INI 文件,而是将或多或少重要的东西存储在 Windows 注册表中。但即便如此,我还是偏爱 INI 文件。嗯,主要原因在于:在 Vista 及更高版本中,如果你的应用程序想对注册表进行一些更改,它应该被允许,这样用户就会收到这个弹窗。我知道很多人只是关闭了 UAC,但是。例如,我不关。我喜欢知道什么东西在什么时候试图访问我的系统,我喜欢随时关注一切,或者更确切地说,大部分时间关注一切。所以我在我的应用程序中经常使用 ini 文件。

无论如何,这里有一些来自 MSDN 的评论,说明了为什么使用 INI 文件而不是注册表更好。

(来自 http://msdn.microsoft.com/en-us/library/ms724353(v=vs.85).aspx)

INI 文件是简单的纯文本,易于阅读,并且没有损坏其他程序的风险(只要你坚持使用你程序目录中的 INI 文件)。

  1. 如果用户需要编辑 INI 文件,用户可以使用任何可以编辑文本文件的应用程序(记事本)来完成,而无需编辑注册表(危险),或加载应用程序本身(这会造成鸡生蛋还是蛋生鸡的困境)。
  2. 如果我需要将我的应用程序迁移到另一个平台,我不能保证注册表可用,但我很有信心可以读取和写入文本文件。
  3. 将应用程序移动到另一台机器更容易。在某些情况下,甚至可能像复制文件夹一样简单。
  4. 将应用程序设置存储在 INI 文件中允许在同一台机器上安装应用程序的多个版本。而使用注册表,这可能会变得困难,甚至几乎不可能。

正如我之前提到的,有很多 INI 解析器,但也许那只是初看起来,我没有找到任何,比如说,“完整的”。并不是我在寻找解决方案,只是出于好奇。我拥有这个类已经有一段时间了,并且在我的许多项目中使用过它。所以这里分享一下。

背景

要处理 INI 文件,我们有几个可用的 API,比如

  • GetPrivateProfileInt
  • GetPrivateProfileSection
  • GetPrivateProfileSectionNames
  • GetPrivateProfileString
  • GetPrivateProfileStruct
  • GetProfileInt
  • GetProfileSection
  • GetProfileString
  • WritePrivateProfileSection
  • WritePrivateProfileString
  • WritePrivateProfileStruct
  • WriteProfileSection
  • WriteProfileString

从这个列表中,我们实际上只需要那些包含“Private”关键字的。其他的,例如 WriteProfileString,只处理 Win.ini 文件——我们不会碰这个文件,更重要的是,我在我的系统上找不到这个文件,所以它就像,可能已经过时很久了。所以缩小这个列表,我们得到

  • GetPrivateProfileInt
  • GetPrivateProfileSection
  • GetPrivateProfileSectionNames
  • GetPrivateProfileString
  • GetPrivateProfileStruct
  • WritePrivateProfileSection
  • WritePrivateProfileString
  • WritePrivateProfileStruct

现在,让我们简单回顾一下工具集。我们需要进行一些数据完整性检查吗?一些校验和之类的?嗯,我从未需要过任何东西,所以我跳过了以下两个 API:GetPrivateProfileStructWritePrivateProfileStruct。现在,让我们考虑 WritePrivateProfileSection。来自 MSDN

替换初始化文件中指定节的键和值。

所以如果你在 INI 文件中有这样的数据

[Section]
key = value

WritePrivateProfileSection 会用你提供的新键和值替换它。我们需要这个吗?我不需要,所以我跳过了这个 API。现在让我们看看 GetPrivateProfileSectionGetPrivateProfileSectionNames。你可以在 MSDN 上再次阅读它们的描述。但是让我告诉你,这些 API 需要一个静态分配的缓冲区,根据我的经验,它们不会接受任何其他东西,也不会返回所需分配的正确大小。基本上,它们的实现方式是错误的。更重要的是,它们在一个字符串中返回所有值,如下所示:something + '\0' + next something + '\0' + whatever + '\0\0'。嗯,这太错了。这就是为什么我也跳过了它们,并编写了自己的替代品。所以再次缩小我们的列表,我们现在有了

  • GetPrivateProfileInt
  • GetPrivateProfileString
  • WritePrivateProfileString

IniWorker 基于这三个函数。那么,让我们继续前进。

使用 IniWorker 类

首先,让我提到 IniWorker 的主要功能

  • 1.a. 类为你管理文件指针
  • 1.b. 你可以维护一个文件指针,这样你就可以在你自己打开的 INI 文件上调用类方法,随便什么
  • 2.a. 方法的 Unicode 版本
  • 2.b. 方法的 ANSI 版本*
  • 3. MS API 默认未提供的附加功能
  • 垃圾回收引擎,所以你不必担心内存泄漏**

* 我会说实话,并非所有实现为 Unicode 的方法都有其 ANSI 替代方案。我只是不在我的应用程序中使用 ANSI 字符串,这是我的个人规则。所以你已经被告知了。如果你偏好 ANSI 并且不需要 Unicode,你可以做一些简单的转换。这只是一个替换 wchar_tcharwcslenstrlen 等)的问题。

** 嗯,实际上,代码会泄漏一些内存(尽管非常少),所以它不是那么专业。不过,你可以直接从类中移除 GC 并自己释放所有分配的内存。这也不是什么大事。好吧,我在我的应用程序中访问 INI,每次运行最多访问 10 次,所以这段“脏”代码符合我的需求。

好了,我说得太多了,故事结束,句号。让我们看看这个类。

pic

你可以看到这里那里有一些疯狂的方法。让我们只关注最重要的事情。

/*
    Constructor.
    IniPath - is just INI file path
    RelativeToCurrent - if we got our file in the same folder with 
                        our application, or in some subfolder, 
                        then we set this to true.
    UsingCollector - set this to TRUE, if you want the collector to do memory management.
    Usage:
        IniWorker *ini = new IniWorker(L"Settings\\my_app.ini", TRUE, FALSE);
*/
IniWorker(IN wchar_t* IniPath, 
          IN BOOL RelativeToCurrent = FALSE,
          IN BOOL UsingCollector = FALSE)

/*
    Delete section in Ini file. UNICODE and ANSI
    We can provide our own ini file path (wchar_t *iniPath)
    Usage: DeleteSection(L"VistaGlass");
           DeleteSection(L"VistaGlass", L"C:\\my_app.ini");
*/
BOOL DeleteSection(IN wchar_t* Section, IN OPT wchar_t *iniPath = NULL);


/*
    If we just need to set simple option, like: "use vista glass = TRUE"
    Usage: SetSimpleOptionBool(L"VistaGlass", TRUE);
            SetSimpleOptionBool(L"VistaGlass", TRUE, L"C:\\my_app.ini");
    UNICODE and ANSI
*/
BOOL SetSimpleOptionBool(IN wchar_t *Section, 
                         IN BOOL Value,
                         IN OPT wchar_t *iniPath = NULL);


/*
    If we just need to check if some option is true.
    Usage: 
        BOOL useGlass = GetSimpleOptionBool(L"VistaGlass");
        BOOL useGlass = GetSimpleOptionBool(L"VistaGlass", L"C:\\my_app.ini");
    UNICODE and ANSI
*/
BOOL GetSimpleOptionBool(IN wchar_t *Section, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    Usage:
        SetExtendedOptionBool(L"VistaGlass", L"DrawTextOnGlass", TRUE);
        SetExtendedOptionBool(L"VistaGlass", L"DrawTextOnGlass", TRUE, L"C:\\my_app.ini");
*/
BOOL SetExtendedOptionBool(IN wchar_t *Section, 
                           IN wchar_t *Val, 
                           IN BOOL Value,
                           IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    Usage:
        BOOL drawText = GetExtendedOptionBool(L"VistaGlass", L"DrawTextOnGlass");
        BOOL drawText = GetExtendedOptionBool(L"VistaGlass", 
                        L"DrawTextOnGlass", L"C:\\my_app.ini");
*/
BOOL GetExtendedOptionBool(IN wchar_t *Section, 
                           IN wchar_t *Val,
                           IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE AND ANSI.
    If we need to set some simple INT option, like, trial timeout, whatever.
    Usage: 
        SetSimpleOptionInt(L"Timeout", 10);
        SetSimpleOptionInt(L"Timeout", 10, L"C:\\my_app.ini");
*/
BOOL SetSimpleOptionInt(IN wchar_t *Section, IN int Value, 
                        IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE AND ANSI
    Simply, gets an iteger value associated with option.
    Usage:
        int myInt = GetSimpleOptionInt(L"Section");
        int myInt = GetSimpleOptionInt(L"Section", L"C:\\my_app.ini");
*/
unsigned int GetSimpleOptionInt(IN wchar_t *Section, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE AND ANSI.
    Almost like SetSimpleOptionInt. Here a section may contain multiple keys.
    Usage:
        SetExtendedOptionInt(L"Timer", L"TrialTimeout", 10);
        SetExtendedOptionInt(L"Timer", L"TrialTimeout", 10, L"C:\\my_app.ini");
*/
BOOL SetExtendedOptionInt(IN wchar_t *Section, 
                          IN wchar_t *Value,
                          IN int Val,
                          IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    Simply get integer value of some key in INI file.
    Usage:
        int myInt = GetExtendedOptionInt(L"mySection", L"myKey");
        int myInt = GetExtendedOptionInt(L"mySection", L"myKey", L"C:\\my_app.ini");
*/
unsigned int GetExtendedOptionInt(IN wchar_t *Section, 
                                  IN wchar_t *Value,
                                  IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    This is just regular call of WritePrivateProfileString.
    That is what most wrappers offer, so do mine.
    Usage:
        SetExtendedOption(L"VistaGlass", L"DrawTextOnGlass", L"OnlyFor10Seconds");
        SetExtendedOption(L"VistaGlass", L"DrawTextOnGlass", 
                            L"OnlyFor10Seconds", L"C:\\my_app.ini");
*/
BOOL SetExtendedOption(    IN wchar_t* Section, 
                        IN wchar_t* Value, 
                        IN wchar_t* Option,
                        IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI and POINTER COLLECTION (optional)
    This is just regular call of GetPrivateProfileString.
    That is what most wrappers offer, so do mine.
    Usage:
        wchar_t *op = GetExtendedOption(L"VistaGlass", L"DrawTextOnGlass");
        wchar_t *op = GetExtendedOption(L"VistaGlass", 
                          L"DrawTextOnGlass", L"C:\\my_app.ini");
*/
wchar_t * GetExtendedOption(IN wchar_t* Section, 
                            IN wchar_t* Value,
                            IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and Array will be freed on delete
    This one here provides some extra functionality. This function will 
    fetch every section name and return all of them inside an array. Plus
    it will return count of sections.
    Usage:
        int count = 0;
        wchar_t **Sections = GetSections(count);
        wchar_t **Sections = GetSections(count, L"C:\\my_app.ini");
        for(int i = 0; i < count; i++)
        {
            wprintf(L"Found section: %s\n", Sections[i]);
        }
*/
wchar_t ** GetSections(IN OUT int &elems, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and Array will be freed on delete
    This one here provides some extra functionality. Basically, this function 
    gets all section keys. Like we got:
    [section]
    key = something
    anotherkey = nothing
    Function returns string arrays with section keys and its count.
    Usage:
        int COunt = 0;
        wchar_t **ARray = GetSectionElements(L"section", COunt);
        wchar_t **ARray = GetSectionElements(L"section", COunt, 
                                             L"C:\\my_app.ini");
*/
wchar_t **GetSectionElements(IN wchar_t* Section, 
          IN OUT int &elems, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and POINTER COLLECTION (optional)
    This one here provides some extra functionality. So what is value and what is
    option and what is section? Consider this as ini file content:
    [section]
    value = option

    So you got a point. You provide "option" or "value" and get "section".
    Usage:
        wchar_t *secName = GetSectionNameByOptionValue(L"option");
        wchar_t *secName = GetSectionNameByOptionValue(L"value", 
                           L"C:\\my_app.ini");
        The downside of this is you need to have everything unique in  ini file.
        This function simply returns the first find.
*/
wchar_t *GetSectionNameByOptionValue(IN wchar_t *OptionValue, 
        IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE ONLY and POINTER COLLECTION (optional)
    This one here provides some extra functionality. So what is value and what is
    option? Consider this as ini file content:
    [something]
    value = option
    So you got a point. You provide "option" and get "value".
    Usage:
        wchar_t *valName = GetValueNameByOption(L"option");
        wchar_t *valName = GetValueNameByOption(L"option", L"C:\\my_app.ini");
        The downside of this is you need to have everything unique in ini file.
        This function simply returns the first find.
*/
wchar_t *GetValueNameByOption(IN wchar_t *Option, IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI and POINTER COLLECTION (optional)
    Some helper function here. Just get our ini file stats, like:
    1. Size; 2. creation date; 3. last access date; 4. Last write date and time
    Usage:
        wchar_t *crDate = NULL, *acDate = NULL, *wrDate = NULL;
        int fileSIze = Stats(crDate, acDate, wrDate);
*/
unsigned int Stats(OUT wchar_t *&CreationDate, 
                   OUT wchar_t *&AccessDate,
                   OUT wchar_t *&WriteDate,
                   IN OPT wchar_t *iniPath = NULL);


/*
    UNICODE and ANSI
    if we need to protect our file.
    Well, thats a silly protection, however, will prevent 80% of users from
    messing with our file. 
    Usage:
        Protect(TRUE);
        Protect(TRUE, L"C:\\my_app.ini");
    This function will set or unset the following 
    protection: system file, readonly file, hidden file.
*/
BOOL Protect(IN BOOL Mode, IN OPT wchar_t *iniPath = NULL)

所以这里没有什么特别的。基本上,这就是我个人需要的,只是我的一点分享。好了,这是一个测试函数。

VOID myFree(LPVOID ptr)
{
    if(ptr != NULL)
    {
        HeapFree(GetProcessHeap(), 0, ptr);
        ptr = NULL;
    }
}

void IniWorkerTest(int loops, BOOL useCollector)
{
    for (int i = 0; i <= loops; i++)
    {
        wprintf(L"LOOP NR: %d\n", i);
        
        IniWorker *ini = new IniWorker(L"ini.ini", TRUE, useCollector);
        
        ini->SetSimpleOptionBool(L"VistaGlass", TRUE);
        ini->SetExtendedOptionBool(L"GlassEffect", L"DisplayText", TRUE);
        ini->SetExtendedOption(L"Section1", L"value1", L"option1");
        ini->SetExtendedOption(L"Section1", L"value2", L"option2");
        ini->SetExtendedOption("Section1", "value3", "option3");
    
        wchar_t *crDate = NULL, *acDate = NULL, *wrDate = NULL;
        int fSize = ini->Stats(crDate, acDate, wrDate);
        wprintf(L"Ini file size: %d bytes\r\nCreation date: " 
                L"%s\r\nAccess Date: %s\r\nAnd last write date: %s\r\n", 
            fSize, crDate, acDate, wrDate);
        if(!useCollector)
        {
            myFree(crDate); 
            myFree(acDate); 
            myFree(wrDate);
        }

        BOOL isVistaGlass = ini->GetSimpleOptionBool(L"VistaGlass");
        if(isVistaGlass) wprintf(L"VIsta glass is enabled!\n");
        else wprintf(L"vista glass is disabled!\n");

        BOOL isGlassEffect_DisplayText = 
            ini->GetExtendedOptionBool(L"GlassEffect", L"DisplayText");
        if(isGlassEffect_DisplayText)
            wprintf(L"We should display text on glass!\n");
        else
            wprintf(L"We shouldnt display text on glass!\n");

        wchar_t *ExOptionTest = ini->GetExtendedOption(L"Section1", L"value2");
        wprintf(L"Our extended option is %s\n", ExOptionTest);
        if(!useCollector)myFree(ExOptionTest);
        
        wchar_t *valName = ini->GetValueNameByOption(L"option2");
        wprintf(L"Key = %s\r\n", valName);
        if(!useCollector)myFree(valName);

        ini->SetSimpleOptionInt(L"TrialTimeout", 100);
        ini->SetSimpleOptionInt("AppRuns", 1000);

        int TrialTimeout = ini->GetSimpleOptionInt("TrialTimeout");
        wprintf(L"TrialTimeout is %d\n", TrialTimeout);

        ini->SetSimpleOptionInt("Something", 22);
        int Something = ini->GetSimpleOptionInt("Something");
        printf("Here is something: %d\n", Something);

        ini->SetExtendedOptionInt(L"ExtendedInt", L"runfor", 55);
        ini->SetExtendedOptionInt("ExtendedInt", "flushafter", 90);

        int Exo = ini->GetExtendedOptionInt(L"ExtendedInt", L"flushafter");
        wprintf(L"flushafter = %d\n", Exo);

        int Some= ini->GetExtendedOptionInt("ExtendedInt", "runfor");
        printf("runfor = %d\n", Some);
    
        int elems = 0;
        wchar_t **Array = ini->GetSections(elems);
        for (int i = 0; i < elems; i++)
        {
            wprintf(L"Found section: [%s]\r\n", Array[i]);
        }
        elems = 0;
        
        wchar_t **Arr = ini->GetSectionElements(L"Section1", elems);
        for (int i = 0; i < elems; i++)
        {
            wprintf(L"Found Value: %s\n", Arr[i]);
        }
        delete ini;
    }
}

就是这样。为了更好地理解——只需查看类本身。如果能听到一些改进建议,或者你发现了什么 bug 之类的,那就太好了。

现在给“新一代程序员”一个小小提醒:如果你想建议我使用 std::string,使用 bool 而不是 BOOL,让别人为我管理内存,把我的命运交给上天之类的——省省吧,我原则上不使用 C++,只有在我在这个星球上的生存依赖于它时才会使用它;除非不是这样,C(带类)才是武士的唯一之道。

祝好!

© . All rights reserved.