天天看點

在 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

在 WinCe 平台讀寫 ini 檔案
在 WinCe 平台讀寫 ini 檔案

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

在 WinCe 平台讀寫 ini 檔案
在 WinCe 平台讀寫 ini 檔案

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

在 WinCe 平台讀寫 ini 檔案

            //讀取section前求出 Key 的長度

            keyLength = t_strlen(lpKeyName);

            bFindSection = true;            

            continue;

        //查找Key, Section End?

        if(line[0]=='[') break; //遇到了下一個

        if(lineLength < keyLength+1 || line[keyLength] != '=') continue; //"KeyName=

在 WinCe 平台讀寫 ini 檔案

"

        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;

在 WinCe 平台讀寫 ini 檔案
在 WinCe 平台讀寫 ini 檔案

        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;

在 WinCe 平台讀寫 ini 檔案

        //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)

在 WinCe 平台讀寫 ini 檔案

            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=

在 WinCe 平台讀寫 ini 檔案

            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)

在 WinCe 平台讀寫 ini 檔案

    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;                //等号字元的在行中的位置

在 WinCe 平台讀寫 ini 檔案

        //copy the keyname to buffer, 注意ncpy不會複制結尾的0字元

        //LINE: "keyName = 

在 WinCe 平台讀寫 ini 檔案

        //       |       |

        //     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 通常是因為預設設定,如果取消預編譯頭的選項,則可以不包含它。

  然後我們可以很友善對上面的代碼進行測試:

在 WinCe 平台讀寫 ini 檔案
在 WinCe 平台讀寫 ini 檔案

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

繼續閱讀