在上篇文章開發 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
/***************************************
* 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"
#include <io.h> //for _sopen
#include <fcntl.h> //for _O_RDWT
#include <share.h> // for _SH_DENYRW
#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 //從結尾向前查找字元
//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,
DWORD CeGetPrivateProfileSection(
LPTSTR lpReturnedString,
DWORD nSize,
DWORD CeGetPrivateProfileSectionNames(
LPTSTR lpszReturnBuffer,
//在PC平台上可以調用_chsize函數調整檔案大小,但是在WINCE平台上
//由于不支援,是以必須注意當檔案尺寸應該縮小時,檔案尾部内容不确定!!!!
BOOL CeWritePrivateProfileString(
LPCTSTR lpKeyName, //要修改的KEY,如果為NULL,會删除整個Section
LPCTSTR lpString, //要寫入的值,如果為NULL,則會删除這個KEY
//重寫某個Section,注意和 PC API 的差別是,這裡不保證原子性操作
BOOL CeWritePrivateProfileSection(
LPCTSTR lpAppName, //section name
LPCTSTR lpString, //key1=val1 \0 key2=val2 \0\0
//==============================================
// 以下是我增加的函數(在API中沒有)
DWORD CeGetPrivateProfileKeyNames(
DWORD nSize, //緩沖區的字元數
(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 "IniFile.h"
//從appname(section)中讀取string類型key
LPTSTR lpReturnedString, //[out] 查找到的結果
DWORD nSize, //[in]lpReturnedString的字元數,注意機關不是位元組!
)
{
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值
long ret = nDefault; //傳回值
return nDefault;
TCHAR *pStopChar = NULL;
ret = t_strtol(line + keyLength + 1, &pStopChar, 10); //預設為10進制
//擷取某個Section下面的所有“key=value”形式的字元串集合,以0字元分割
//結尾使用兩個0字元
//緩沖區寫入:"key1=value1 \0 key2=value2 \0 \0 "
//傳回值表示寫入緩沖區的字元數, 不包括結尾的0字元。
//如果緩沖區不夠容納所有的鍵值對,則傳回值 = (nSize-2)
DWORD ret = 0; //傳回值,拷貝的字元數量
DWORD remainSize = nSize - 2; //緩沖區目前所能能夠接納的字元數量
DWORD copySize; //本次循環中需要拷貝的字元數量
bool bFindSection = false; //是否已經找到Section
TCHAR line[ LINESIZE ]; //行緩沖區
LPTSTR pItem; //指向目前鍵值對的寫入位址
size_t sectionLength, lineLength;
pItem = lpReturnedString; //指向緩沖區起始位址
//緩沖區是否還有剩餘空間?
if(remainSize <= 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);
if(bFindSection)
//再次對緩沖區追加一個0 字元
*pItem = 0;
//擷取一個ini檔案中所有section的name,拷貝到緩沖區
//注意和系統API的差別是,系統API的讀取是原子性的,即讀取時不允許修改ini檔案的内容
//而我們的函數未必保證這一點
DWORD ret = 0; //傳回值,拷貝的字元數量
DWORD remainSize = nSize - 2; //緩沖區目前所能能夠接納的字元數量
DWORD copySize; //本次循環中需要拷貝的字元數量
TCHAR line[ LINESIZE ]; //行緩沖區
TCHAR *pSectionEndChar; //']'字元指針
LPTSTR pItem; //指向目前鍵值對的寫入位址
FILE *stream; //流指針
size_t lineLength; //行字元長度
pItem = lpszReturnBuffer; //指向緩沖區起始位址
//注意:把LF(0xa)字元替換成0,這在 UNICODE 環境下可能出現結尾是LF)
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 = 0;
void *pVoid = NULL; //檔案的後半部分
bool bFindKey = false;
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+"));
return false;
//先取一次mode的預設值
if(lpKeyName == NULL)
mode = MODE_DELETE_SECTION;
else if(lpString == NULL)
mode = MODE_DELETE_KEY;
else
mode = MODE_OVERWRITE_KEY;
//每次讀行前,儲存檔案指針位置
while(nPos = ftell(stream), t_fgets(line, LINESIZE, stream) != NULL)
if(lpKeyName != NULL)
keyLength = t_strlen(lpKeyName);
nSectionBegin = nPos;
bFindSection = true;
//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)
fclose(stream);
return true;
nInsertPos = nSectionBegin;
nCopyPos = nNextSection;
if(mode == MODE_DELETE_KEY)
if(!bFindKey)
nInsertPos = nKeyBegin;
nCopyPos = nNextKey;
if(mode == MODE_OVERWRITE_KEY)
mode = MODE_APPEND_SECTION;
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);
//先把檔案的後半部分拷貝到記憶體
fseek(stream, 0, SEEK_END);
nFileEndPos = ftell(stream);
if(nCopyPos >= 0 && nCopyPos < nFileEndPos)
//配置設定記憶體作為緩沖區
pVoid = malloc(nFileEndPos - nCopyPos + 1);
if(pVoid == NULL)
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);
//如果檔案變小了,那麼我們需要更改檔案大小
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;
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)
if(!bFindSection)
nSectionBegin = ftell(stream);
//覆寫Section
if(nNextSection >= 0 && nNextSection < nFileEndPos)
pVoid = malloc(nFileEndPos - nNextSection + 1);
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)
nPos = ftell(stream); //目前檔案位置
//===========================================================
// 以下是我增加的函數(API中沒有)
//擷取某個section下的所有的Key名,
//擷取某個Section下面的所有“key形式的字元串集合,以0字元分割
//緩沖區寫入:"key1 \0 key2 \0 \0 "
//注意:此函數是在桌面 API 中也沒有的。而是我單獨添加的
bool bFindSection = false; //是否已經找到Section
TCHAR *pEqualChar; //等号字元的在行中的位置
//copy the keyname to buffer, 注意ncpy不會複制結尾的0字元
//LINE: "keyName =
// | |
// line pEqualChar
pEqualChar = t_strchr(line, '=');
if(pEqualChar != NULL)
copySize = min( remainSize, pEqualChar - line );
//本行中不存在‘=’字元,對于合法檔案來說不會出現此種情況
copySize = min( remainSize, lineLength );
//最佳一個0字元
在 CeWritePrivateProfileString 函數中,用到的幾個檔案指針的含義是:假設我們要查找的 Section 是“section2”,Key 是“key2”;
=============================
...
nSectionBegin -> [section2]
nKeyBegin -> key2=value2
nNextKey -> ...
nNextSection -> [otherSection]
其他檔案指針的含義是:nInsertPos - 新的KEY=Value開始寫入位置; nCopyPos - 檔案的後半部分在原始檔案中的位置(整體不需要改寫,但可能需要前移或後移),從這裡到檔案結尾的内容會在改寫ini檔案之前拷貝到記憶體,改寫KEY後,再寫回檔案并附加到檔案尾部。
上面的代碼中,包含 StdAfx.h 通常是因為預設設定,如果取消預編譯頭的選項,則可以不包含它。
然後我們可以很友善對上面的代碼進行測試:
TestingCode
#include <stdio.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