技术开发 频道

在 WinCe 平台读写 ini 文件

  【IT168 技术】由于wince 平台上不支持例如 GetPrivateProfileString 等相关 API 函数。在网络上并没有找到令我满意的相应代码,因此本人手工自己写了相应的方法。命名规则是,在 PC API 函数的名称前面加上 “Ce” 前缀,这是为了在 PC 平台上调试和使用时,不和系统的 API 函数发生冲突。值得注意的是,在写 CeWritePrivateProfileString 方法时,如果改写后的 ini 文件应该比改写前的文件小,文件尾部将会是一些不确定内容(来自于原来文件)。在 PC 上我们可以通过 中的 _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

  /***************************************

  * 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 //for _sopen

  #include //for _O_RDWT

  #include // 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

  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 通常是因为默认设定,如果取消预编译头的选项,则可以不包含它。

  然后我们可以很方便对上面的代码进行测试:

TestingCode

  #include

  #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

0
相关文章