在 WinCe 平台读写 ini 文件

简介: 在上篇文章开发 windows mobile 上的今日插件时,我发现 wince 平台上不支持例如 GetPrivateProfileString 等相关 API 函数。在网络上我并没有找到令我满意的相应代码,因此我手工自己写了相应的方法。
      在上篇文章开发 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

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 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 \0 key2=val2 \0\0
    LPCTSTR lpFileName
    );

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

#endif
  (b) IniFile.cpp

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif 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 \0 key2=value2 \0 \0 "
//返回值表示写入缓冲区的字符数, 不包括结尾的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(
"\r\n"));

    
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(
"\r\n[%s]\r\n%s=%s\r\n"), 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\r\n"), 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 \0 key2=val2 \0\0
    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]\r\n"), lpAppName);
    
while(*pItem)
    {
        t_fprintf(stream, _T(
"%s\r\n"), 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 \0 key2 \0 \0 "
//返回值表示写入缓冲区的字符数, 不包括结尾的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 通常是因为默认设定,如果取消预编译头的选项,则可以不包含它。
  然后我们可以很方便对上面的代码进行测试:
img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif TestingCode
#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

目录
相关文章
|
9天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
7691 13
|
8天前
|
人工智能 测试技术 API
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
Jev是TypeSafe AI推出的“系统一模型”,不生成文本,专做毫秒级结构化决策:Choice(多选)、Score(打分)、Noul(是非概率)。响应快193倍、成本低444倍,适合工单路由、内容审核、测试定级等高频判断场景。
1651 4
最近全网爆火的 Jev 到底是什么?适合干什么、怎么用,一篇讲透!
|
4天前
|
人工智能 JavaScript 芯片
DeepSeek 官方偷偷上传 Harness 桌面端安装包,我已经用上了。。附最新下载地址
DeepSeek Harness 官方的桌面端安装包被网友扒出来了,2 分钟讲明白如何使用,体验如何,适合作为 AI 编程工具么?附最新 Windows 和 Mac 双端的下载地址
1420 1
|
8天前
|
人工智能 并行计算 PyTorch
秋叶 ComfyUI 2026 整合包 v3.2 完整部署教程:Python 3.13 + Torch 2.13 全栈升级
秋叶aaaki ComfyUI 2026年8月整合包v3.2正式发布!全面升级Python 3.13.11、PyTorch 2.13.0+cu130及ComfyUI v0.30.2,原生支持MiniMax H3、Wan 2.2、Qwen-Image-2.1等2026主流音视频/图像模型,解压即用,无需环境配置。
1210 10
|
21天前
|
人工智能 自然语言处理 安全
阿里云千问办公 QwenWork详细介绍:产品核心能力、典型场景、价格及常见问题解答
千问办公是阿里云推出的一站式AI办公平台,主打"不止于对话,更注重交付",依托通义千问旗舰大模型,用户一句话即可完成数据分析、PPT生成、视频剪辑等复杂任务,直接输出可用成果。产品深度打通钉钉生态与企业OA,覆盖桌面端、网页端,提供企业标准版198元/人/月等多档订阅方案,新用户注册即赠2000积分,适配工程师、HR、财务等多职业办公场景,成为能动手干活的"全能AI同事"。
3675 10
|
5天前
|
编解码 缓存 PyTorch
16G 显卡能跑 Qwen-Image 2.1 吗?
9月20日,阿里Qwen开源Qwen-Image-2.1:7B DiT图像模型+8B文本编码器+VAE,单模型支持文生图与图像编辑,原生输出2K PNG(含Alpha通道),支持10张参考图。在自建Qwen-Image-Bench达60.28分(开源模型第一),GenAI Showdown文生图排名7/15。16G显存可跑1024×1024(需INT8量化+ComfyUI优化),但2K需24G以上。注意其Qwen Research License限非商业用途。
618 1
|
6天前
|
人工智能 编解码 并行计算
MiniMax-H3 一键整合包技术文档:8G 显存运行 AI 漫剧制作 —— 角色替换 / 动作迁移 / 文图生视频部署与调参指南
MiniMax H3 是 MiniMax 开源的全模态视频生成模型,支持文/图/音/视多条件输入,输出最高2K、15秒带双声道音频视频。本文档详述其Int8量化版在8GB显存下的本地一键部署、三段式工作流(EDIT/REPLACE/CONTINUE)、参数调优及常见问题排查。(239字)
|
16天前
|
缓存 IDE Java
【保姆级】Android Studio下载、安装和汉化教程(2026最新)
Android Studio 是 Google 官方推出的免费 Android 应用开发集成环境,基于 IntelliJ IDEA,内置模拟器、调试器、性能分析及 Compose 界面工具,功能全面,文档丰富,是安卓开发首选工具。(239字)
1733 1

热门文章

最新文章