在 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 \0 key2=val2 \0\0
    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 == 0continue;
        }
        
        
//尝试寻找到 section
        if(!bFindSection)
        {
            
if(line[0!= '['continue//本行是否是 [section]
            
//这里是我们想要的Section吗?
            
//检查这一行的宽度是否正好是section长度加2, [lpAppName]
            if(line[sectionLength + 1!= ']'continue;
            
if(t_strncmp(line+1, lpAppName, sectionLength) != 0continue;
            
//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)!=0continue;
        
//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 == 0continue;
        }
        
        
//尝试寻找到 section
        if(!bFindSection)
        {
            
if(line[0!= '['continue//本行是否是 [section]
            
//这里是我们想要的Section吗?
            
//检查这一行的宽度是否正好是section长度加2, [lpAppName]
            if(line[sectionLength + 1!= ']'continue;
            
if(t_strncmp(line+1, lpAppName, sectionLength) != 0continue;
            
//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)!=0continue;
        
//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 <= 0break;

        
//忽略注释行和空行
        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 == 0continue;
        }
        
        
//尝试寻找到 section
        if(!bFindSection)
        {
            
if(line[0!= '['continue//本行是否是 [section]
            
//这里是我们想要的Section吗?
            
//检查这一行的宽度是否正好是section长度加2, [lpAppName]
            if(line[sectionLength + 1!= ']'continue;
            
if(t_strncmp(line+1, lpAppName, sectionLength) != 0continue;
            
//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 <= 0break;

        
//忽略注释行和空行
        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 == 0continue;
        }

        
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 == 0continue;
        }
        
        
//尝试寻找到 section
        if(!bFindSection)
        {
            
if(line[0!= '['continue//本行是否是 [section]
            
//这里是我们想要的Section吗?
            
//检查这一行的宽度是否正好是section长度加2, [lpAppName]
            if(line[sectionLength + 1!= ']'continue;
            
if(t_strncmp(line+1, lpAppName, sectionLength) != 0continue;
            
//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) != 0continue;
            
//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 == 0continue;
        }
        
        
//尝试寻找到 section
        if(!bFindSection)
        {
            
if(line[0!= '['continue//本行是否是 [section]
            
//这里是我们想要的Section吗?
            
//检查这一行的宽度是否正好是section长度加2, [lpAppName]
            if(line[sectionLength + 1!= ']'continue;
            
if(t_strncmp(line+1, lpAppName, sectionLength) != 0continue;
            
//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 <= 0break;

        
//忽略注释行和空行
        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 == 0continue;
        }
        
        
//尝试寻找到 section
        if(!bFindSection)
        {
            
if(line[0!= '['continue//本行是否是 [section]
            
//这里是我们想要的Section吗?
            
//检查这一行的宽度是否正好是section长度加2, [lpAppName]
            if(line[sectionLength + 1!= ']'continue;
            
if(t_strncmp(line+1, lpAppName, sectionLength) != 0continue;
            
//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

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/435464.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL Server 兼容模式

近期一个老系统改用SQL Server 2005数据库, 从原来的2000改为2005, 默认情况下启动出错&#xff1a; Incorrect syntax near Index. If this is intended as a part of a table hint, A WITH keyword and parenthesis are now required. SQL 语句中的table hint 中缺少with 关键…

通过VisualSVN的POST-COMMIT钩子自动部署代码

这段时间我们一直规划LSGO Group的学习网络平台&#xff0c;需求部分已经规划完毕&#xff0c;说做就做&#xff0c;开始搭建环境&#xff0c;由于利用PHPMYSQL技术&#xff0c;在服务器端首先安装了WAMPServer&#xff0c;以便提供Apache服务与MYSQL服务&#xff01; 在代码的…

电脑安装系统多少钱_电脑系统安装教学

双十一刚过&#xff0c;双十一购买硬件的朋友等快递到了&#xff0c;马上就要进入装机环节了&#xff0c;最近会出一系列装机之后的文章&#xff0c;比如今天的装系统教学&#xff0c;之后还会出CPU、内存超频教学。至于装机教学&#xff0c;文章很难解释清楚&#xff0c;如果怕…

数据结构与算法(C#版)第二章 C#语言与面向对象技术(上)V1.0

由于本学期我给本科生上《数据结构与算法》这门课程&#xff0c;边上边把自己的教案进行总结&#xff0c;发到CSDN上面&#xff0c;希望大家多多指正。 第二章 C#语言与面向对象技术&#xff08;上&#xff09; 一、C#语言 1.数据类型 2.常量与变量 3.运算符与表达式 4.基本语句…

ie浏览器网页版进入_IE浏览器打开网页速度很慢的解决办法

IE浏览器网页打开速度很慢怎么办&#xff1f;最近有用户反映&#xff0c;使用IE浏览器上网时&#xff0c;打开网页非常慢&#xff0c;如何解决这个问题&#xff1f;请看下文。解决办法&#xff1a;1.登录到系统桌面&#xff0c;使用组合快捷键win键r键打开运行窗口&#xff0c;…

数据结构与算法(C#版)第二章 C#语言与面向对象技术(中)V1.0

由于本学期我给本科生上《数据结构与算法》这门课程&#xff0c;边上边把自己的教案进行总结&#xff0c;发到CSDN上面&#xff0c;希望大家多多指正。 第二章 C#语言与面向对象技术&#xff08;中&#xff09; 二、面向对象技术 1.类与对象 2.封装 3.继承 4.多态

判断深度学习模型的稳定性_问题引领构建数学模型,讲练结合促进深度学习

为了立足校本教研、提升教师专业素养、构建高效课堂、提升数学教学质量&#xff0c;我校数学组于11月12日开展了&#xff02;问题引领构建数学模型&#xff0c;讲练结合促进深度学习&#xff02;主题教研活动&#xff0c;区教研员钟文丽老师莅临指导。 教研活动第一环节为王…

数据结构与算法(C#版)第二章 C#语言与面向对象技术(下)V1.0

由于本学期我给本科生上《数据结构与算法》这门课程&#xff0c;边上边把自己的教案进行总结&#xff0c;发到CSDN上面&#xff0c;希望大家多多指正。 第二章 C#语言与面向对象技术&#xff08;下&#xff09; 二、面向对象技术 5.属性 6.索引器 7.接口 8.泛型 9.类与类之间的…

win10命令提示符怎么打开_Win10系统防火墙怎么打开?ARP防火墙启用步骤

ARP防火墙怎么打开&#xff1f;ARP防火墙能够提供多种系统防护功能&#xff0c;那么Win10系统该如何开启ARP防火墙呢&#xff1f;本文就给大家介绍Win10系统电脑ARP防火墙的启用方法。操作步骤&#xff1a;1、右键点击Win10 开始菜单&#xff0c;然后选择“运行”;或者使用快捷…

LSGO软件技术团队2015~2016学年第二周(0907~0912)总结

由于团队的技术博客正在构建当中&#xff0c;所以先借CSDN对团队本周的情况做一个总结。 1.团队成员签到统计 2、团队发表技术博客统计 从第三周开始&#xff0c;要求每组根据自身的情况&#xff0c;至少发表一篇技术类博客。 3、团队技术小组统计 到目前为止&#xff0c;Mat…

电脑上怎么做pdf文件_图片怎么输出为PDF文件?两种办法教你,手机电脑都能转...

我们经常遇到要将图片转换成pdf文件输出的情况&#xff0c;那么我们有什么办法能更快更好进行转换呢&#xff1f;小编今天给大家带来两个办法&#xff0c;一个是手机上操作的&#xff0c;另一个是电脑上的方法&#xff0c;实测好用&#xff01;手机端转换手机上小编一般会使用图…

LSGO软件技术团队2015~2016学年第三周(0914~0920)总结

很抱歉&#xff0c;由于我的原因&#xff0c;本学期对团队的数据没有及时统计和整理&#xff0c;在团队的管理中有些放松&#xff0c;近期我会补上&#xff0c;让大家在一个充满正能量的氛围中学习成长。简简单单做人&#xff0c;快快乐乐做事&#xff01; 本周签到情况统计&am…

利用div的定位制作复杂的页面布局

既然是复杂的布局自然用到较多的div以及并列与嵌套。这就要我们在为它们命名的时候遵从一定的规定&#xff0c;以便于可读性。下面先来看一张布局图&#xff1a;这是三九健康网一个频道的页面&#xff0c;原本是很长的页面&#xff0c;但为了说明问题&#xff0c;取了个完整布局…

LSGO软件技术团队2015~2016学年第四周(0921~0927)总结

我们是一个充满正能量的学习型团队&#xff01; 简简单单做人&#xff0c;快快乐乐做事&#xff01; 本周签到情况统计&#xff08;第四周0921至0927&#xff09;&#xff1a; 团队技术博客账号统计&#xff1a; 进入团队需要遵守的规则&#xff1a; 1.进入团队的同学需遵守“…

正则匹配括号里面的内容不包括括号_python正则表达式

是一个计算机科学概念用于使用单个字符串来描述&#xff0c;匹配某个规则的字符串常常用来检索&#xff0c;替换某些模式的文本正则的写法.(点号)&#xff1a;表示任意一个字符&#xff0c;除了[](中括号)&#xff0c;匹配括号中列举的范围&#xff0c;如[0-9]代表任意单个字数…

专业程序员必知必会的技巧:驯服复杂代码

你从入职第一天起就要应对复杂代码。 若是还未遇到过无法理解的程序&#xff0c;那说明你编程的年头还不够长。在行业里&#xff0c;要不了多久你就会碰到让人发懵的混乱代码&#xff1a;巨兽、面条工厂、来自地狱的遗留系统。我曾接手过一个程序&#xff0c;它的前任在听说要…

LSGO软件技术团队2015~2016学年第五周(0928~1004)总结

一个充满正能量的学习型团队&#xff01; 简简单单做人&#xff0c;快快乐乐做事&#xff01; 本周签到情况统计&#xff08;第五周0928至1004&#xff09;&#xff1a; 团队技术博客账号统计&#xff08;第五周0928至1004&#xff09;&#xff1a; 实验室工作台使用情况统计&…

香肠派对电脑版_《香肠派对》是不是除了《和平精英》最成功的吃鸡手游:靠恶搞火了?...

我们知道&#xff0c;自从《绝地求生》自国内外火了之后&#xff0c;国内陆续上线了大大小小不少于10款各种类型的吃鸡手游&#xff0c;从最早的《丛林法则》、到瞬间倒下的《荒野行动》&#xff0c;再到现在最火的《和平精英》和后面即将上线的武侠吃鸡《江湖求生》。国内的游…

C/C++程序员必读的十本书(上)

在Gmail TopLanguage Group 中看到一篇文章“C/C程序员必读的十本书&#xff08;上&#xff09;”&#xff0c;大家的讨论都很激励&#xff0c;大家都觉得这篇文章写得很棒&#xff0c;我想在Group里的朋友都能在Lookgirl的这篇文章里获益不少&#xff0c;为此斗胆在此转载该文…

LSGO软件技术团队2015~2016学年第六周(1005~1011)总结

LSGO软件技术团队成立于2010年10月&#xff0c;主要从事的应用方向为互联网与移动互联网&#xff08;UI设计&#xff0c;前端开发&#xff0c;后台开发&#xff09;&#xff0c;地理信息系统&#xff1b;研究方向为数据分析与计算机视觉。成立几年来为学校培养了一批优秀学生&a…