• 在 WinCe 平台读写 ini 文件


    在上篇文章开发 windows mobile 上的今日插件时,我发现 wince 平台上不支持例如 GetPrivateProfileString 等相关 API 函数。在网络上我并没有找到令我满意的相应代码,因此我手工自己写了相应的方法。命名规则是,在 PC API 函数的名称前面加上 “Ce” 前缀,这是为了在 PC 平台上调试和使用时,不和系统的 API 函数发生冲突。值得注意的是,在写 CeWritePrivateProfileString 方法时,如果改写后的 ini 文件应该比改写前的文件小,文件尾部将会是一些不确定内容(来自于原来文件)。在 PC 上我们可以通过 <io.h> 中的 _chsize 函数重新设置文件大小,但是很遗憾的是,这些底层的文件操作函数在 wince 平台上依然不被支持,但是幸运的是,可以使用 coredll.dll 中提供的SetEndOfFile 函数去完成相同功能(感谢88上的 kghost 的提示)。
      另外我额外提供了一个函数:CeGetPrivateProfileKeyNames,用于读取某个 section 的所有 key 名称。
      当然,如果是在 PC 平台,我们就没有必要使用这里我所提供的代码,因为有系统 API 可以调用。
      需要注意的是,我提供的代码和 PC 端 API 相比,基本功能,参数意义完全相同,但具有以下一些额外要求:
      (1)大小写敏感。(当然也可以通过修改代码,令其大小写不敏感)
      (2)每一行,section, key, value, “=” 的前后不允许有空格。
      (3)注释行用英文分号“;"起始。允许存在空行。
      (4)每一行的字符数不能超过 260 字符(取决于代码中的宏定义)。
      (5)函数代码同时适用 unicode 和多字节字符串 的环境。
      (6)由于采用标准文件操作函数,因此 CeGetPrivateProfileSectionNames 函数并不保证原子性。(这一点和 PC API 不同)

      下面是相关函数代码:
      (a) IniFile.h


    /***************************************
     * IniFile.h
     * 说明:在WinCe平台读写 INI 文件
     * by hoodlum1980
     * 2009.08.03 
     ***************************************/
    #ifndef __INIFILE_H_BY_HOODLUM1980
    #define __INIFILE_H_BY_HOODLUM1980

    //是否在WINCE平台上
    #ifndef WINCE
    #define WINCE
    #endif

    #include "StdAfx.h"
    #ifndef WINCE
        #include <io.h>        //for _sopen
        #include <fcntl.h>    //for _O_RDWT
        #include <share.h>    // for _SH_DENYRW
    #endif

    #ifdef  UNICODE   // r_winnt
        #define t_sopen            _wsopen        //注意WinCe上不支持!
        #define t_fopen         _wfopen
        #define t_fgets            fgetws
        #define t_fprintf        fwprintf    //文件格式化写入
        #define t_sprintf        swprintf    //格式化文本
        #define t_strcpy        wcscpy
        #define t_strncpy        wcsncpy        //拷贝指定个数的字符
        #define t_strcat        wcscat        //append a string
        #define t_strtol        wcstol
        #define t_strlen        wcslen
        #define t_strcmp        wcscmp
        #define t_stricmp        _wcsicmp    //忽略大小写的字符串比较
        #define t_strncmp        wcsncmp        //比较n个字符
        #define t_strchr        wcschr        //find a character in a string
        #define t_strrchr        wcsrchr        //从结尾向前查找字符

    #else  //ASCII CODE

        #define t_sopen            _sopen        //注意WinCe上不支持!
        #define t_fopen         fopen
        #define t_fgets            fgets        //读取一行文本
        #define t_fprintf        fprintf        //文件格式化写入
        #define t_sprintf        sprintf        //格式化文本
        #define t_strcpy        strcpy
        #define t_strncpy        strncpy        //拷贝指定个数的字符
        #define t_strcat        strcat        //append a string
        #define t_strtol        strtol        //把字符串转换成long(int32)
        #define t_strlen        strlen
        #define t_strcmp        strcmp        //比较字符串
        #define t_stricmp        _stricmp    //忽略大小写的字符串比较
        #define t_strncmp        strncmp        //比较n个字符
        #define t_strchr        strchr        //查找字符
        #define t_strrchr        strrchr        //从结尾向前查找字符

    #endif

    //CeWritePrivateProfileString 方法用到的辅助标记
    #define MODE_DELETE_SECTION        11
    #define MODE_OVERWRITE_SECTION    12
    #define MODE_APPEND_SECTION        13
    #define MODE_DELETE_KEY            21
    #define MODE_OVERWRITE_KEY        22
    #define MODE_APPEND_KEY            23

    #define LINESIZE                260    //行缓冲区大小

    DWORD CeGetPrivateProfileString(
        LPCTSTR lpAppName,                //section name: [lpAppName]
        LPCTSTR lpKeyName,                //lpKeyName=lpReturnedString
        LPCTSTR lpDefault,                //未找到时的默认值
        LPTSTR lpReturnedString,        //[out] 查找到的结果
        DWORD nSize,                    //[in]lpReturnedString的字符数,注意单位不是字节!
        LPCTSTR lpFileName
        );

    UINT CeGetPrivateProfileInt(
        LPCTSTR lpAppName,
        LPCTSTR lpKeyName,
        int nDefault,
        LPCTSTR lpFileName
        );

    DWORD CeGetPrivateProfileSection(
        LPCTSTR lpAppName,
        LPTSTR lpReturnedString,
        DWORD nSize,
        LPCTSTR lpFileName
        );

    DWORD CeGetPrivateProfileSectionNames(
        LPTSTR lpszReturnBuffer,
        DWORD nSize,
        LPCTSTR lpFileName
        );

    //在PC平台上可以调用_chsize函数调整文件大小,但是在WINCE平台上
    //由于不支持,所以必须注意当文件尺寸应该缩小时,文件尾部内容不确定!!!!
    BOOL CeWritePrivateProfileString(
        LPCTSTR lpAppName,
        LPCTSTR lpKeyName,    //要修改的KEY,如果为NULL,会删除整个Section
        LPCTSTR lpString,    //要写入的值,如果为NULL,则会删除这个KEY
        LPCTSTR lpFileName
        );

    //重写某个Section,注意和 PC API 的区别是,这里不保证原子性操作
    BOOL CeWritePrivateProfileSection(
        LPCTSTR lpAppName,    //section name
        LPCTSTR lpString,    //key1=val1  key2=val2 
        LPCTSTR lpFileName
        );

    //==============================================
    //        以下是我增加的函数(在API中没有)
    //==============================================
    DWORD CeGetPrivateProfileKeyNames(
        LPCTSTR lpAppName,
        LPTSTR lpReturnedString,
        DWORD nSize,                //缓冲区的字符数
        LPCTSTR lpFileName
        );

    #endif

      (b) IniFile.cpp


    //适用于 char* 和 UNICODE,
    //所有字符串必须使用 TEXT("aa") 或者 _T("aa") 的格式(自动适应 char* 或 UNICODE)
    //所有相关函数加t_前缀
    //IniFile: 读取INI FILE的简单解析!所谓简单,也就是解析代码简单,但对文件格式要求更高
    //[1]任何字符串前后不要有空格(使解析代码可以不考虑前后的trim)
    //            例如允许"Key1=Val", 而不允许" Key1 = Val "
    //[2]允许有注释,第一个字符必须是英文分号';'
    //
    #include "StdAfx.h"
    #include "IniFile.h"

    //从appname(section)中读取string类型key
    DWORD CeGetPrivateProfileString(
        LPCTSTR lpAppName,                //section name: [lpAppName]
        LPCTSTR lpKeyName,                //lpKeyName=lpReturnedString
        LPCTSTR lpDefault,                //未找到时的默认值
        LPTSTR lpReturnedString,    //[out] 查找到的结果
        DWORD nSize,                            //[in]lpReturnedString的字符数,注意单位不是字节!
        LPCTSTR lpFileName
        )
    {
        DWORD ret = 0;
        FILE *stream;
        bool bFindVal = false;
        bool bFindSection = false;
        TCHAR line[ LINESIZE ];
        size_t sectionLength, keyLength, lineLength;
        
        stream = t_fopen(lpFileName, _T("r"));
        if(stream == NULL)
        {
            //设置默认值
            t_strcpy(lpReturnedString, lpDefault);
            ret = t_strlen(lpReturnedString); 
            return ret;
        }
        
        sectionLength = t_strlen(lpAppName);
        
        while(t_fgets(line, LINESIZE, stream) != NULL)
        {
            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }
            
            //尝试寻找到 section
            if(!bFindSection)
            {
                if(line[0] != '[') continue; //本行是否是 [section]
                //这里是我们想要的Section吗?
                //检查这一行的宽度是否正好是section长度加2, [lpAppName]
                if(line[sectionLength + 1] != ']') continue;
                if(t_strncmp(line+1, lpAppName, sectionLength) != 0) continue;
                //Now Section will appear on next line 
                //读取section前求出 Key 的长度
                keyLength = t_strlen(lpKeyName);
                bFindSection = true;            
                continue;
            }
            
            //查找Key, Section End?
            if(line[0]=='[') break; //遇到了下一个
                
            if(lineLength < keyLength+1 || line[keyLength] != '=') continue; //"KeyName="
            if(t_strncmp(line, lpKeyName, keyLength)!=0) continue;
            //Now We Get the Key! 
            t_strcpy(lpReturnedString, line + keyLength + 1);
            //Now It's done.
            bFindVal = true;
            break;
        }
        
        fclose(stream);
        if(!bFindVal)
        {
            //设置默认值
            t_strcpy(lpReturnedString, lpDefault); 
        }
        ret = t_strlen(lpReturnedString); 
        return ret;
    }


    //读取一个int值
    UINT CeGetPrivateProfileInt(
        LPCTSTR lpAppName,
        LPCTSTR lpKeyName,
        int nDefault,
        LPCTSTR lpFileName
        )
    {
        long ret = nDefault; //返回值
        FILE *stream;
        bool bFindVal = false;
        bool bFindSection = false;
        TCHAR line[ LINESIZE ];
        size_t sectionLength, keyLength, lineLength;
        
        stream = t_fopen(lpFileName, _T("r"));
        if(stream == NULL)
        {
            //设置默认值
            return nDefault;
        }
        
        sectionLength = t_strlen(lpAppName);
        
        while(t_fgets(line, LINESIZE, stream) != NULL)
        {
            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }
            
            //尝试寻找到 section
            if(!bFindSection)
            {
                if(line[0] != '[') continue; //本行是否是 [section]
                //这里是我们想要的Section吗?
                //检查这一行的宽度是否正好是section长度加2, [lpAppName]
                if(line[sectionLength + 1] != ']') continue;
                if(t_strncmp(line+1, lpAppName, sectionLength) != 0) continue;
                //Now Section will appear on next line 
                //读取section前求出 Key 的长度
                keyLength = t_strlen(lpKeyName);
                bFindSection = true;            
                continue;
            }
            
            //查找Key, Section End?
            if(line[0]=='[') break; //遇到了下一个
                
            if(lineLength < keyLength+1 || line[keyLength] != '=') continue; //"KeyName="
            if(t_strncmp(line, lpKeyName, keyLength)!=0) continue;
            //Now We Get the Key! 
            TCHAR *pStopChar = NULL;
            ret = t_strtol(line + keyLength + 1, &pStopChar, 10); //默认为10进制
            //Now It's done.
            bFindVal = true;
            break;
        }
        
        fclose(stream);
        return ret;
    }


    //获取某个Section下面的所有“key=value”形式的字符串集合,以0字符分割
    //结尾使用两个0字符
    //缓冲区写入:"key1=value1  key2=value2   "
    //返回值表示写入缓冲区的字符数, 不包括结尾的0字符。
    //如果缓冲区不够容纳所有的键值对,则返回值 = (nSize-2)
    DWORD CeGetPrivateProfileSection(
        LPCTSTR lpAppName,
        LPTSTR lpReturnedString,
        DWORD nSize,                //缓冲区的字符数
        LPCTSTR lpFileName
        )
    {
        DWORD ret = 0; //返回值,拷贝的字符数量
        DWORD remainSize = nSize - 2; //缓冲区当前所能能够接纳的字符数量
        DWORD copySize;                //本次循环中需要拷贝的字符数量
        FILE *stream;
        bool bFindSection = false; //是否已经找到Section
        TCHAR line[ LINESIZE ];   //行缓冲区
        LPTSTR pItem; //指向当前键值对的写入地址
        size_t sectionLength, lineLength;
        

        pItem = lpReturnedString; //指向缓冲区起始地址

        stream = t_fopen(lpFileName, _T("r"));
        if(stream == NULL)
        {
            return ret;
        }
        
        sectionLength = t_strlen(lpAppName);
        
        while(t_fgets(line, LINESIZE, stream) != NULL)
        {
            //缓冲区是否还有剩余空间?
            if(remainSize <= 0) break;

            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }
            
            //尝试寻找到 section
            if(!bFindSection)
            {
                if(line[0] != '[') continue; //本行是否是 [section]
                //这里是我们想要的Section吗?
                //检查这一行的宽度是否正好是section长度加2, [lpAppName]
                if(line[sectionLength + 1] != ']') continue;
                if(t_strncmp(line+1, lpAppName, sectionLength) != 0) continue;
                //Now Section will appear on next line 
                bFindSection = true;            
                continue;
            }
            
            //查找Key, Section End?
            if(line[0]=='[') break; //遇到了下一个

            //copy the line to buffer, 注意ncpy不会复制结尾的0字符
            copySize = min( remainSize, lineLength );
            t_strncpy(pItem, line, copySize);
            //追加一个0字符
            pItem[copySize] = 0;

            //缩小缓冲区剩余字符数量remainSize,和当前写入位置pItem
            ret += (copySize + 1); //加1是为了统计结尾的0字符
            remainSize -= (copySize + 1);
            pItem += (copySize + 1);
        }
        
        fclose(stream);

        if(bFindSection)
        {
            //再次对缓冲区追加一个0 字符
            *pItem = 0;
        }
        return ret;
    }


    //获取一个ini文件中所有section的name,拷贝到缓冲区
    //注意和系统API的区别是,系统API的读取是原子性的,即读取时不允许修改ini文件的内容
    //而我们的函数未必保证这一点
    DWORD CeGetPrivateProfileSectionNames(
        LPTSTR lpszReturnBuffer,
        DWORD nSize,
        LPCTSTR lpFileName
        )
    {
        DWORD ret = 0;                    //返回值,拷贝的字符数量
        DWORD remainSize = nSize - 2;    //缓冲区当前所能能够接纳的字符数量
        DWORD copySize;                    //本次循环中需要拷贝的字符数量
        TCHAR line[ LINESIZE ];            //行缓冲区
        TCHAR *pSectionEndChar;            //']'字符指针
        LPTSTR pItem;                    //指向当前键值对的写入地址
        FILE *stream;                    //流指针
        size_t lineLength;                //行字符长度
        
        pItem = lpszReturnBuffer; //指向缓冲区起始地址

        stream = t_fopen(lpFileName, _T("r"));
        if(stream == NULL)
        {
            return ret;
        }
        
        while(t_fgets(line, LINESIZE, stream) != NULL)
        {
            //缓冲区是否还有剩余空间?
            if(remainSize <= 0) break;

            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在 UNICODE 环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }

            if(line[0] != '[') continue; //本行是否是 [section]
            //找到了一个Section,开始拷贝
            //copy the section name to buffer, 注意ncpy不会复制结尾的0字符

            //LINE: "[sectionName]"
            //       |           |
            //     line      pSectionEndChar

            //找出‘=’字符的位置
            pSectionEndChar = t_strchr(line, ']');
            if(pSectionEndChar != NULL)
            {
                //找到了‘=’字符,(pEqualChar - line)是key的长度
                copySize = min( remainSize,  pSectionEndChar - line - 1 );
            }
            else
            {
                //本行中不存在‘]’字符,对于合法文件来说不会出现此种情况
                copySize = min( remainSize, lineLength - 1 );
            }

            t_strncpy(pItem, line+1, copySize);

            //追加一个0字符
            pItem[copySize] = 0;

            //缩小缓冲区剩余字符数量remainSize,和当前写入位置pItem
            ret += (copySize + 1); //加1是为了统计结尾的0字符
            remainSize -= (copySize + 1);
            pItem += (copySize + 1);
        }

        fclose(stream);
        //再次对缓冲区追加一个0 字符
        *pItem = 0;
        return ret;
    }

    //
    BOOL CeWritePrivateProfileString(
        LPCTSTR lpAppName,
        LPCTSTR lpKeyName,    //要修改的KEY,如果为NULL,会删除整个Section
        LPCTSTR lpString,    //要写入的值,如果为NULL,则会删除这个KEY
        LPCTSTR lpFileName
        )
    {
        FILE *stream;
        void *pVoid = NULL; //文件的后半部分
        bool bFindKey = false;
        bool bFindSection = false;
        TCHAR line[ LINESIZE ];
        size_t sectionLength, keyLength, lineLength, nBytesRead = 0;
        LONG nInsertPos = -1, nCopyPos = -1, nFileEndPos, nPos; //文件指针位置
        LONG nSectionBegin = -1, nKeyBegin = -1, nNextKey = -1, nNextSection = -1;
        BYTE mode = 0;
        
        //如果 sectionName 为NULL,返回成功
        if(lpAppName == NULL)
            return true;

        //r+: Opens for both reading and writing. (The file must exist.)
        stream = t_fopen(lpFileName, _T("r+"));
        if(stream == NULL)
        {
            return false;
        }

        //先取一次mode的默认值
        if(lpKeyName == NULL)
            mode = MODE_DELETE_SECTION;
        else if(lpString == NULL)
            mode = MODE_DELETE_KEY;
        else
            mode = MODE_OVERWRITE_KEY;

        sectionLength = t_strlen(lpAppName);
        
        //每次读行前,保存文件指针位置
        while(nPos = ftell(stream), t_fgets(line, LINESIZE, stream) != NULL)
        {
            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }
            
            //尝试寻找到 section
            if(!bFindSection)
            {
                if(line[0] != '[') continue; //本行是否是 [section]
                //这里是我们想要的Section吗?
                //检查这一行的宽度是否正好是section长度加2, [lpAppName]
                if(line[sectionLength + 1] != ']') continue;
                if(t_strncmp(line+1, lpAppName, sectionLength) != 0) continue;
                //Now Section will appear on next line 
                //读取section前求出 Key 的长度
                if(lpKeyName != NULL)
                    keyLength = t_strlen(lpKeyName);
                
                nSectionBegin = nPos;

                bFindSection = true;        
                continue;
            }
            
            //Section找到了,
            //Section End ?
            if(line[0]=='[') 
            {
                nNextSection = nPos;
                break; //遇到了下一个
            }

            //是否需要查找KEY?
            if(lpKeyName != NULL)
            {            
                if(lineLength < keyLength+1 || line[keyLength] != '=') continue; //"KeyName="
                if(t_strncmp(line, lpKeyName, keyLength) != 0) continue;
                //Now We Get the Key! 
                nKeyBegin = nPos;
                nNextKey = ftell(stream); //要拷贝的起始位置
                //Now It's done.
                bFindKey = true;
                break;
            }
        }

        //如果已经到达文件尾部,则追加换行
        if(feof(stream))
            t_fprintf(stream, _T(" "));

        if(nNextSection < 0) nNextSection = ftell(stream);
        if(nNextKey < 0) nNextKey = ftell(stream);

        //遍历后再次更新mode值
        if(mode == MODE_DELETE_SECTION)
        {
            if(!bFindSection)
            {
                fclose(stream);
                return true;
            }
            else
            {
                nInsertPos = nSectionBegin;
                nCopyPos = nNextSection;
            }
        }
        if(mode == MODE_DELETE_KEY)
        {
            if(!bFindKey)
            {
                fclose(stream);
                return true;
            }
            else
            {
                nInsertPos = nKeyBegin;
                nCopyPos = nNextKey;
            }
        }
        if(mode == MODE_OVERWRITE_KEY)
        {
            if(!bFindSection)
            {
                mode = MODE_APPEND_SECTION;
            }
            else
            {
                if(bFindKey)
                {
                    nInsertPos = nKeyBegin;
                    nCopyPos = nNextKey;
                }
                else
                {
                    mode = MODE_APPEND_KEY;
                    nInsertPos = nNextSection;
                    nCopyPos = nNextSection;
                }
            }
        }

        //追加一个新的Section
        if(mode == MODE_APPEND_SECTION)
        {
            t_fprintf(stream, _T(" [%s] %s=%s "), lpAppName, lpKeyName, lpString);
            fclose(stream);
            return true;
        }

        //先把文件的后半部分拷贝到内存
        fseek(stream, 0, SEEK_END);
        nFileEndPos = ftell(stream);
        if(nCopyPos >= 0 && nCopyPos < nFileEndPos)
        {
            //分配内存作为缓冲区
            pVoid = malloc(nFileEndPos - nCopyPos + 1);

            if(pVoid == NULL)
            {
                fclose(stream);
                return false; //堆内存不足
            }
            fseek(stream, nCopyPos, SEEK_SET);
            nBytesRead = fread(pVoid, 1, nFileEndPos - nCopyPos + 1, stream);
        }

        //写入新的value值
        fseek(stream, nInsertPos, SEEK_SET);
        if(lpKeyName != NULL && lpString != NULL)
            t_fprintf(stream, _T("%s=%s "), lpKeyName, lpString);

        //现在把文件的后半部分写回文件中
        if(pVoid != NULL && nBytesRead > 0)
        {
            fwrite(pVoid, 1, nBytesRead, stream);
            free(pVoid);
        }

        //此时结尾可能还有一些内容,属于原来的ini文件
        //我们把它写成注释
        nPos = ftell(stream);
        fclose(stream);

        //如果文件变小了,那么我们需要更改文件大小
        if(nPos < nFileEndPos)
        {
    #ifdef WINCE    //WINCE平台
            HANDLE handle = CreateFile(
                lpFileName, //LPCTSTR lpFileName
                GENERIC_WRITE, //DOWRD dwDesiredAccess,
                0, //DWORD dwShareMode, 非共享模式
                NULL, //LPSECURITY_ATTRIBUTES lpSecurityAttributes, ignored
                OPEN_EXISTING, //DWORD dwCreationDispostion, 
                FILE_ATTRIBUTE_NORMAL, //DWORD dwFlagsAndAttributes, 
                NULL//HANDLE hTemplateFile, ignored
                ); 

            if(handle != NULL)
            {
                //移动文件指针
                SetFilePointer(handle, nPos, NULL, FILE_BEGIN);
                //设置EOF
                SetEndOfFile(handle);
                //关闭
                CloseHandle(handle);
            }

    #else            //PC 平台
            int handle = t_sopen(lpFileName, _O_RDWR, _SH_DENYRW);
            if(handle > 0)
            {
                //修改文件大小
                _chsize(handle, nPos);
                //关闭文件
                _close(handle);
            }
    #endif //
        }
        return TRUE;
    }


    //重写某个Section,注意和 PC API 的区别是,这里不保证原子性操作
    BOOL CeWritePrivateProfileSection(
        LPCTSTR lpAppName,    //section name
        LPCTSTR lpString,    //key1=val1  key2=val2 
        LPCTSTR lpFileName
        )
    {
        FILE *stream;
        void *pVoid = NULL; //文件的后半部分
        bool bFindSection = false;
        TCHAR line[ LINESIZE ]; //行缓冲区
        LPCTSTR pItem = lpString;
        size_t sectionLength, lineLength, nBytesRead = 0;
        LONG nFileEndPos, nPos; //文件指针位置
        LONG nSectionBegin = -1, nNextSection = -1;
        
        //如果 sectionName 为NULL,返回失败
        if(lpAppName == NULL || lpString == NULL)
            return false;

        //r+: Opens for both reading and writing. (The file must exist.)
        stream = t_fopen(lpFileName, _T("r+"));
        if(stream == NULL)
        {
            return false;
        }

        sectionLength = t_strlen(lpAppName);
        
        //每次读行前,保存文件指针位置
        while(nPos = ftell(stream), t_fgets(line, LINESIZE, stream) != NULL)
        {
            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }
            
            //尝试寻找到 section
            if(!bFindSection)
            {
                if(line[0] != '[') continue; //本行是否是 [section]
                //这里是我们想要的Section吗?
                //检查这一行的宽度是否正好是section长度加2, [lpAppName]
                if(line[sectionLength + 1] != ']') continue;
                if(t_strncmp(line+1, lpAppName, sectionLength) != 0) continue;
                //Now Section will appear on next line 
                nSectionBegin = nPos;
                bFindSection = true;        
                continue;
            }
            
            //Section找到了,
            //Section End ?
            if(line[0]=='[') 
            {
                nNextSection = nPos;
                break; //遇到了下一个
            }
        }

        //如果已经到达文件尾部,则追加换行
        if(nNextSection < 0) nNextSection = ftell(stream);

        //追加一个新的Section
        if(!bFindSection)
        {
            nSectionBegin = ftell(stream);
        }

        //覆写Section
        //先把文件的后半部分拷贝到内存
        fseek(stream, 0, SEEK_END);
        nFileEndPos = ftell(stream);
        if(nNextSection >= 0 && nNextSection < nFileEndPos)
        {
            //分配内存作为缓冲区
            pVoid = malloc(nFileEndPos - nNextSection + 1);

            if(pVoid == NULL)
            {
                fclose(stream);
                return false; //堆内存不足
            }
            fseek(stream, nNextSection, SEEK_SET);
            nBytesRead = fread(pVoid, 1, nFileEndPos - nNextSection + 1, stream);
        }
        
        //逐行写入key = val
        fseek(stream, nSectionBegin, SEEK_SET);
        //再次写入[section],如果不存在就会追加
        t_fprintf(stream, _T("[%s] "), lpAppName);
        while(*pItem)
        {
            t_fprintf(stream, _T("%s "), pItem);
            pItem += t_strlen(pItem) + 1; //移动到下一行
        }

        //现在把文件的后半部分写回文件中
        if(pVoid != NULL)
        {
            fwrite(pVoid, 1, nBytesRead, stream);
            free(pVoid);
        }

        //此时结尾可能还有一些内容,属于原来的ini文件
        //我们把它写成注释
        nPos = ftell(stream); //当前文件位置
        fclose(stream);

        //如果文件变小了,那么我们需要更改文件大小
        if(nPos < nFileEndPos)
        {
    #ifdef WINCE    //WINCE平台
            HANDLE handle = CreateFile(
                lpFileName, //LPCTSTR lpFileName
                GENERIC_WRITE, //DOWRD dwDesiredAccess,
                0, //DWORD dwShareMode, 非共享模式
                NULL, //LPSECURITY_ATTRIBUTES lpSecurityAttributes, ignored
                OPEN_EXISTING, //DWORD dwCreationDispostion, 
                FILE_ATTRIBUTE_NORMAL, //DWORD dwFlagsAndAttributes, 
                NULL//HANDLE hTemplateFile, ignored
                ); 

            if(handle != NULL)
            {
                //移动文件指针
                SetFilePointer(handle, nPos, NULL, FILE_BEGIN);
                //设置EOF
                SetEndOfFile(handle);
                //关闭
                CloseHandle(handle);
            }

    #else            //PC 平台
            int handle = t_sopen(lpFileName, _O_RDWR, _SH_DENYRW);
            if(handle > 0)
            {
                //修改文件大小
                _chsize(handle, nPos);
                //关闭文件
                _close(handle);
            }
    #endif //
        }
        return TRUE;
    }


    //===========================================================
    //        以下是我增加的函数(API中没有)
    //===========================================================

    //
    //获取某个section下的所有的Key名,
    //获取某个Section下面的所有“key形式的字符串集合,以0字符分割
    //结尾使用两个0字符
    //缓冲区写入:"key1  key2   "
    //返回值表示写入缓冲区的字符数, 不包括结尾的0字符。
    //如果缓冲区不够容纳所有的键值对,则返回值 = (nSize-2)

    //注意:此函数是在桌面 API 中也没有的。而是我单独添加的
    //
    DWORD CeGetPrivateProfileKeyNames(
        LPCTSTR lpAppName,
        LPTSTR lpReturnedString,
        DWORD nSize,                //缓冲区的字符数
        LPCTSTR lpFileName
        )
    {
        DWORD ret = 0;                    //返回值,拷贝的字符数量
        DWORD remainSize = nSize - 2;    //缓冲区当前所能能够接纳的字符数量
        DWORD copySize;                    //本次循环中需要拷贝的字符数量
        bool bFindSection = false;        //是否已经找到Section
        TCHAR line[ LINESIZE ];            //行缓冲区
        LPTSTR pItem;                    //指向当前键值对的写入地址
        TCHAR *pEqualChar;                //等号字符的在行中的位置
        FILE *stream;                    //流指针
        size_t sectionLength, lineLength;

        
        pItem = lpReturnedString; //指向缓冲区起始地址

        stream = t_fopen(lpFileName, _T("r"));
        if(stream == NULL)
        {
            return ret;
        }
        
        sectionLength = t_strlen(lpAppName);
        
        while(t_fgets(line, LINESIZE, stream) != NULL)
        {
            //缓冲区是否还有剩余空间?
            if(remainSize <= 0) break;

            //忽略注释行和空行
            if(line[0] == 0 || line[0] == ';') continue;
            lineLength = t_strlen(line);
            //注意:把LF(0xa)字符替换成0,这在UNICODE环境下可能出现结尾是LF)
            if(line[ lineLength - 1 ] == 0x0a)
            {
                line[ lineLength - 1 ] = 0;
                lineLength--;
                //注意此时可能会成为空字符串
                if(lineLength == 0) continue;
            }
            
            //尝试寻找到 section
            if(!bFindSection)
            {
                if(line[0] != '[') continue; //本行是否是 [section]
                //这里是我们想要的Section吗?
                //检查这一行的宽度是否正好是section长度加2, [lpAppName]
                if(line[sectionLength + 1] != ']') continue;
                if(t_strncmp(line+1, lpAppName, sectionLength) != 0) continue;
                //Now Section will appear on next line 
                bFindSection = true;            
                continue;
            }
            
            //查找Key, Section End?
            if(line[0]=='[') break; //遇到了下一个


            //copy the keyname to buffer, 注意ncpy不会复制结尾的0字符

            //LINE: "keyName = "
            //       |       |
            //     line   pEqualChar        

            //找出‘=’字符的位置
            pEqualChar = t_strchr(line, '=');
            if(pEqualChar != NULL)
            {
                //找到了‘=’字符,(pEqualChar - line)是key的长度
                copySize = min( remainSize,  pEqualChar - line );
            }
            else
            {
                //本行中不存在‘=’字符,对于合法文件来说不会出现此种情况
                copySize = min( remainSize, lineLength );
            }

            t_strncpy(pItem, line, copySize);
            //最佳一个0字符
            pItem[copySize] = 0;

            //缩小缓冲区剩余字符数量remainSize,和当前写入位置pItem
            ret += (copySize + 1); //加1是为了统计结尾的0字符
            remainSize -= (copySize + 1);
            pItem += (copySize + 1);
        }
        
        fclose(stream);

        if(bFindSection)
        {
            //再次对缓冲区追加一个0 字符
            *pItem = 0;
        }
        return ret;
    }

          在 CeWritePrivateProfileString 函数中,用到的几个文件指针的含义是:假设我们要查找的 Section 是“section2”,Key 是“key2”;
          =============================
                                        ...
          nSectionBegin ->      [section2]
                                        ...
          nKeyBegin      ->      key2=value2
          nNextKey       ->      ...
                                        ...
          nNextSection  ->      [otherSection]
                                        ...
          =============================
          其他文件指针的含义是:nInsertPos - 新的KEY=Value开始写入位置; nCopyPos - 文件的后半部分在原始文件中的位置(整体不需要改写,但可能需要前移或后移),从这里到文件结尾的内容会在改写ini文件之前拷贝到内存,改写KEY后,再写回文件并附加到文件尾部。


      上面的代码中,包含 StdAfx.h 通常是因为默认设定,如果取消预编译头的选项,则可以不包含它。
      然后我们可以很方便对上面的代码进行测试:


    #include <stdio.h>
    #include "IniFile.h"

    int _tmain(int argc, _TCHAR* argv[])
    {
        TCHAR buffer[128];
        int age;

        CeGetPrivateProfileString(_T("section2"), _T("name"), _T("defaultValue"), buffer, t_strlen(buffer), _T("c:\test.ini"));

        age = CeGetPrivateProfileInt(_T("section2"), _T("age"), -1, _T("c:\test.ini"));

        //get section
        //CeGetPrivateProfileSection(_T("section2"), buffer, 128, _T("c:\test.ini"));

        //key names
        CeGetPrivateProfileKeyNames(_T("section2"), buffer, 128, _T("c:\test.ini"));

        //section names
        GetPrivateProfileSectionNames(buffer, 128, _T("c:\test.ini"));

        CeWritePrivateProfileString(_T("section2"), _T("key2"), _T("testValue"), _T("c:\test.ini"));

        wprintf(buffer);
        getchar();
        return 0;
    }

    //假设 C:\test.ini 的内容是:
    //;testing ini file
    //[section1]
    //key1=aa
    //[section2]
    //name=myname
    //age=20
    //[section3]
    //key1=bb
  • 相关阅读:
    Anniversary party
    1358. 分割树
    我在 impress.js 中学到的小套路
    我对 impress.js 源码的理解
    CSS transition 过渡 详解
    CSS 2D转换 matrix() 详解
    JS 动画基础
    JS 瀑布流布局
    JS 下拉菜单
    JS Resizable Panel 练习
  • 原文地址:https://www.cnblogs.com/qingchen1984/p/4531424.html
Copyright © 2020-2023  润新知