1. 程式人生 > >物件的永續性和檔案I/O

物件的永續性和檔案I/O


內容提要
      理解物件的永續性* File Demo應用程式* 建立一個持久類* 直接讀寫檔案* 建立自己的CArchive物件* 理解註冊
關鍵詞
       
文章正文
   物件的永續性
       程式需要完成的一項最重要的事情是,當資料以某種方式改變之後,程式能夠儲存使用者的資料。沒有儲存被編輯過的資料的功能,使用者利用應用程式所做的工作只能在應用程式執行時存在,在使用者離開應用程式時就會消失。這不是一種好的工作方式!在很多情況下,尤其在使用AppWizard來建立應用程式時,Visual C++提供許多程式碼用於儲存和載入資料。然而,在一些情況下—特別是當你建立自己物件型別時,必須做一部分額外的工作來儲存使用者檔案的更新。
當讀者正在編寫一個應用程式時,要處理大量的不同型別物件。你的資料物件有些可以像整數和字元一樣簡單。其他物件可以是諸如CString類的字串或你的定製類所建立的物件。當在需要儲存和載入文件的應用程式中使用物件時,需要用某種方式儲存和載入這些物件的狀態,這樣才能重新建立它們,如同上一次任務結束時那樣。

一個物件所具有的儲存和載入其狀態的能力稱為“永續性”。幾乎所有的MFC類都是持久的,這是因為它們是直接或間接由MFC的CObject類派生的,該類提供了儲存和載入物件狀態的基本功能。

File Demo應用程式

你在使用Visual C++的AppWizard來建立程式時,將得到一個可以使用文件和檢視類來組織、編輯和顯示資料的應用程式。由CDocument類派生的文件物件,負責在會話期間儲存應用程式的資料和在不同時期儲存和載入資料,使得文件從一個會話過程到另一個會話過程時得以保持。

建立一個File Demo應用程式,演示儲存和載入由CDocument派生的物件的資料的基本技術。File Demo的文件是包含一個短訊息的單一字串。

有三個選單項與該應用程式相關。當程式初次執行時,訊息自動設定字串為Default Message。使用者可以選擇Edit|Change Message來改變這條訊息。File|Save選單項可以儲存文件,File|Open選單項則從磁碟載入文件。

☆  文件類的回顧

任何編寫過程式的人都儲存和開啟過檔案,從使用者的觀點看這就是物件的永續性。我們將可以學到永續性工作的原理。

當使用Visual C++的AppWizard建立程式時,必須完成以下幾步使得文件能夠儲存和載入其狀態。這些步驟是:

1) 定義儲存文件資料的成員變數。

2) 在文件類的OnNewDocument( )函式中初始化該成員變數。

3) 在檢視類的成員函式OnDraw( )中顯示當前文件。

4) 在檢視類中提供成員函式以使得使用者能夠編輯文件。

5) 在文件類的成員函式Serialize( )中增加儲存和載入構成該文件的資料所需要的程式碼。

當應用程式處理多文件時,你需要做一點額外的工作來保證使用、修改或儲存了正確的文件。所幸的是大部分工作都由MFC和AppWizard完成了。

☆ 建立File Demo應用程式

要想建立File Demo應用程式,首先利用AppWizard建立一個SDI應用程式。因為要使AppWizard中的所有選項都保持為其預設值,所以可以在第1步選擇了SDI並使Document/View被選中之後就單擊Finish按鈕,以加快速度。

在ClassView中雙擊CfileDemoDoc編輯文件類的標頭檔案。在Attributes部分新增一個CString成員變數,變數名為m_message,使得Attributes部分如下所示:

// Attributes

public:

    CString  m_message;

在此例中,儲存的文件只不過是單一字串物件。通常,儲存的文件要更加複雜。然而,用單字串演示文件永續性的基本方法已經足夠。對於MFC程式設計師來說,在文件中使用公有變數而不用帶有公共訪問函式的私有變數是很常見的。這使得編寫訪問文件變數的檢視類的程式碼簡單了一些。不過,這會使以後加強這個程式的功能時要做的工作增加。

像所有的文件資料一樣,這個變數必須要初始化。這個工作在OnNewDocument( )函式中進行。在ClassView中擴充套件CFileDemoDoc,雙擊OnNewDocument( )編輯它。在其中新增一行初始化串的程式碼,如程式清單1所示。在此之前要把TODO註釋刪除掉。

程式清單1   初始化文件的資料

BOOL CFileDemoDoc::OnNewDocument()

{

    if (!CDocument::OnNewDocument())

        return FALSE;

    m_message = 褼efault Message*;

    return TRUE;

}

經過對文件類的資料成員m_message進行初始化,應用程式可以在檢視視窗中顯示該資料。你只須編輯檢視類的OnDraw( )函式(參見程式清單2)。在ClassView中擴充套件CFileDemoView,並雙擊OnDraw( )函式編輯它。在這裡也只要刪除TODO註釋,新增一行程式碼。

程式清單2   顯示文件的資料

void CFileDemoView::OnDraw(CDC* pDC)

{

    CFileDoc* pDoc = GetDocument();

    ASSERT_VALID(pDoc);

    pDC->TextOut(20, 20, pDoc->m_message);

}

現在建立File Demo應用程式,確保沒有錯誤,執行這個程式。預設的訊息就顯示在螢幕中。

現在,增加程式的功能允許程式設計師通過修改串值來編輯應用程式的文件。理論上說應用程式應該顯示一個對話方塊,讓使用者輸入字串。在這兒,我們利用Edit|Change Message選單項來自動賦給訊息串一個不同的值。

單擊Resource選項卡切換到ResourceView,擴充套件resources,擴充套件Menus,雙擊IDR_MAINFRAME,編輯它。單擊選單中的Edit項,下拉出一個子選單列表。單擊列表末尾的空白項並輸入Change &Message,向選單中添加了另外一項。

選擇View|ClassWizard使得這個選單項與程式碼相關聯。先使ID_EDIT_ CHANGEMESSAGE 被中,再從右上角的下拉框中選擇CFileDemoView 。在左下角的框中單擊COMMAND,然後單擊Add Function按鈕。單擊對話方塊的OK按鈕,接受所推薦的函式名稱-OnEditChangeMessage( )。單擊Edit Code,在編輯器中開啟這個新函式,按程式清單3編輯它。

程式清單3   修改文件的資料

void CFileDemoView::OnEditChangemessage()

{

   CTime now = CTime::GetCurrentTime();

   CString changetime = now.Format(褻hanged at %B %d %H:%M:%S*);

   GetDocument()->m_message = changetime;

   GetDocument()->SetModifiedFlag();

   Invalidate();

}

  此函式響應應用程式的Edit|Message命令,利用當前的日期和時間建立一個串,並將它傳送給文件資料成員。對文件類函式SetModifiedFlag( )的呼叫通知物件其內容已經改變。在使用者可能修改資料的地方呼叫SetModifiedFlag( ),就會使程式在使用者試圖不儲存修改過的資料而退出時發出警告。最後,程式碼呼叫Invalidate( ),強迫螢幕重繪。

提示:如果m_message是文件類的私有成員變數,就應該用公共的SetMessage( )函式來呼叫SetModifiedFlag( )函式。這樣每次m-message改變時,都會呼叫SetModifiedFlag()函式。這是編寫真正的面向物件的程式的好處。

文件類的Serialize( )函式處理儲存和載入文件的資料。程式清單4顯示了由AppWizard生成的Serialize( )的空殼。

程式清單4   FILEVIEW.CPP—文件類的Serialize( )函式

void CFileDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        // TODO: add storing code here

}

    else

    {

        // TODO: add loading code here

}

}

由於Cstring類(m_message是其的一個物件)定義了>>和<<運算子用於向文件傳遞字串,因此儲存和載入文件類的資料就是一件簡單的事情。在提醒增加儲存程式碼的註釋的地方增加以下行:

ar<<m_message;

同樣,在載入程式碼的地方增加增加以下行:

ar>>m_message;

《運算子將CSTring m_message傳送給文件》運算子把文件賦給m_message。只要所有的文件成員變數都是如整數或字元那樣的簡單資料型別,利用象CString這樣已經定義了此運算子的MFC類,儲存和載入資料將非常簡單,此運算子是為下列這些簡單資料型別定義的:

* BYTE

* WORD

* int

* LONG

* DWORD

* float

* double

建立這個應用程式File Demo,並執行它。選擇Edit|Message命令,就可以看到螢幕上出現了新的串,選擇File|Save並輸入一個易記的檔名。再次修改訊息,選擇File|New,使用者會得到應先儲存當前修改過的資料的警告。選擇File|Open瀏覽檔案,或者在File選單的底部找到剛剛儲存的檔案的檔名,單擊它也可以開啟這個檔案。可以看到File Demo的確可以儲存和重新載入一個串。

File Demo用命令修改了串值

使用者不會無意丟失未儲存的資料了

建立一個持久類

但是如果讀者已經建立了自己的定製類用於儲存文件的元素該怎麼辦呢?如何使得此類的一個物件成為持久呢?

假設讀者現在想改進File Demo應用程式使其能夠在一個稱為CMessages的定製類中包含程式的資料。此時該成員變數稱為m_messages,它是CMessages的一個例項。此類包含三個CString物件,如果要使應用程式正常執行,必須對上述三個物件進行儲存和載入。儲存和載入它們的一種方法是分別儲存和載入每一個字串,程式碼如程式清單5所示:

程式清單5   儲存新類的字串的一種方注

void CFileDoc::Serialize(CArchive& ar)

{

    if (ar.IsStoring())

    {

        ar << m_messages.m_message1;

        ar << m_messages.m_message2;

        ar << m_messages.m_message3;

    }

    else

    {

        ar >> m_messages.m_message1;

        ar >> m_messages.m_message2;

        ar >> m_messages.m_message3;

     }

}

如果在CMessages類中定義這三個成員變數為public,而且知道類是如何實現的,讀者就可以編寫如清單5中所示的程式碼。之後,如果類以任何其他方式發生變化,此程式碼也必須改變。把存貯和載入工作交付給CMessages類自身去完成更具有面向物件的意義。這需要一些準備,建立一個能序列化的其成員變數的類的基本步驟如下:

1) 由CObject派生此類。

2) 在類定義部分放置DECLAR_SERIAL( )巨集。

3) 在類實現部分放置IMPLEMENT_SERIAL( )巨集。

4) 過載類中的Serialize( )函式。

5) 提供一個類的空的預設建構函式。

☆ File Demo 2 應用程式

下一個示例應用程式File Demo 2,演示建立一個可由此建立持久物件的類的步驟。它包含了可以修改三個串的Edit|Change Messages命令。與File Demo程式類似,當用戶選擇File|Save或File|Open時可以儲存或載入文件。

與建立File Demo程式類似,建立一個名為MultiString的SDI應用程式。新增一個成員變數到文件中,使得MultiStringDoc.h的Attributes部分如下所示:

//Attributes

public:

  CMessages  m_messages;

下一步就是編寫CMessage類。

☆  CMessages類一覽

在理解文件類如何成功實現儲存和載入其內容之前,需要了解CMessages類是如何工作的,文件類的m_messages資料成員是該類的一個物件。在檢視此類時,將看到上面提到的建立持久類所需的五個步驟是如何實現的。

要想建立CMessages類,首先選擇Insert|New Class。修改其類型別為普通類,並命名為CMessages。在螢幕的下部,選擇CObject作為基類名,並將As列設定為public,這樣將建立兩個檔案:messages.h作為標頭檔案,messages.cpp作為程式碼檔案。同時還有一些簡單的程式碼被新增到這些檔案中(這時候可能會有一條警告資訊,找不到CObject的標頭檔案,單擊OK忽略它。因為CObject像所有MFC檔案一樣,不需要包含額外的處理就是可用的)。

切換到Multistringdoc.h中,將下面的程式碼行新增到類定義部分:

#include "Messages.h"

這就保證了編輯器知道CMessages類的存在。現在切換回Messages.h,在其中新增下面的程式碼行:

DECLARE_SERIAL(CMessages)

protected:

   CString m_message1;

   CString m_message2;

   CString m_message3;

public:

   void SetMessage(UINT msgNum, CString msg);

   CString GetMessage(UINT msgNum);

   void Serialize(CArchive& ar);

DECLARE_SERIAL( )巨集提供了實現物件永續性所需的附加的函式和成員變數宣告。

下一步是類的資料成員,它們是Cstring類的三個物件。請注意它們現在是protected性質的成員變數。其後是public型別的成員函式。SetMessage( )函式的變元為需設定的字串的索引號和字串的新值,該函式使得程式能夠改變一個數據成員。GetMessage( )是一個實現函式,它使得程式能夠檢索任何字串的當前值。它的唯一參量是要檢索的字串的索引號。

最後,該類過載Serialize( )函式,在此所有的資料得以儲存和載入,Serialize( )函式是一個持久物件的核心,每個持久類以不同方式實現。程式清單6顯示了這些新成員函式的程式碼,將它新增到messages.cpp中。

程式清單6   MESSAGE.CPP—CMessage類的實現檔案

void CMessages::SetMessage(UINT msgNum, CString msg)

{

     switch (msgNum)

     {

     case 1:

          m_message1 = msg;

          break;

     case 2:

        m_message2 = msg;

          break;

     case 3:

        m_message3 = msg;

          break;

     }

   SetModifiedFlag();

}

CString CMessages::GetMessage(UINT msgNum)

{

   switch (msgNum)

   {

      case 1:

         return m_message1;

      case 2:

         return m_message2;

      case 3:

         return m_message3;

      default:

         return  " ";

   }

}

void CMessages::Serialize(CArchive& ar)

{

    CObject::Serialize(ar);

    if (ar.IsStoring())

    {

        ar << m_message1 << m_message2 << m_message3;

    }

    else

    {

        ar >> m_message1 >> m_message2 >> m_message3;

    }

}

在SetMessage( )和GetMessage( )函式中沒有任何技巧,它們精確地完成所給予的任務。不過在Serialize( )函式中可能會產生一些問題。首先,注意函式體的第一行呼叫了基類的Serialize( )函式。這是一個標準的基類函式的過載函式。在本例中,對CObject::Serialize( )的函式呼叫沒做多少事情,這是因為CObject類的Serialize( )函式為空。儘管如此,呼叫基類的Serialize( )函式是一個需要養成的好習慣,因為你建立的持久類不總是直接由CObject類派生的。

在呼叫的此函式基類版本後,Serialize( )用與文件物件相同的方式進行資料的儲存和載入。因為必須序列化的資料成員是CString物件。程式可以使用>>和<<運算子將字串寫入磁碟。

在messages.cpp的頂部,include語句之後,新增下面的程式碼行:

IMPLEMENT_SERIAL(CMessages,CObject,0)

IMPLEMENT_SERIAL巨集與DECLARE_SLRIAL( )組成一對,它提供了使類具有持續能力的函式的實現。此巨集的三個變元是類名、直接基類名和方案號。方案號類似一個版本號。在多數情況下,可使用0或1作為方案號。

☆  在程式中使用CMessages類

現在CMessages已經被定義和實現了,MultiString的文件和檢視的成員函式也已經可以執行了。首先擴充套件CMultiStringDoc,雙擊OnNewDocument( )編輯它。將下面的程式碼新增到TODO註釋處:

m_messages.SetMessage(1, "Default Message 1");

m_messages.SetMessage(2, "Default Message 2");

m_messages.SetMessage(3, "Default Message 3");

由於文件類不能直接訪問資料物件的資料成員,它必須通過呼叫CMessages類的SetMessage( )成員函式來初始化每一個字串。

擴充套件CMultiStringView,雙擊OnDraw( )函式,按下面的程式碼行來編輯它:

void CMultiStringView::OnDraw(CDC* pDC)

{

        CMultiStringDoc* pDoc = GetDocument();

        ASSERT_VALID(pDoc);

    pDC->TextOut(20, 20, pDoc->m_messages.GetMessage(1));

    pDC->TextOut(20, 40, pDoc->m_messages.GetMessage(2));

    pDC->TextOut(20, 60, pDoc->m_messages.GetMessage(3));

}

與在建立File Demo時所作的類似,新增“Change Message”專案到Edit選單中。將它與名為OnEditChangemessages的函式相關聯。這個函式將會通過呼叫CMessages物件的成員函式來修改資料,如程式清單7所示。檢視類函式OnDraw( )同樣呼叫GetMessage( )成員函式,以訪問CMessages類的串。

程式清單7   編輯資料串

void CMultiStringView::OnEditChangemessages()

{

   CMultiStringDoc* pDoc = GetDocument();

   CTime now = CTime::GetCurrentTime();

   CString changetime = now.Format(褻hanged at %B %d %H:%M:%S*);

   pDoc->m_messages.SetMessage(1, CString(襍tring 1 *) + changetime);

   pDoc->m_messages.SetMessage(2, CString(襍tring 2 *) + changetime);

   pDoc->m_messages.SetMessage(3, CString(襍tring 3 *) + changetime);

   pDoc->SetModifiedFlag();

   Invalidate();

}

剩下的工作就是編寫文件類的Serialize( )函式,這個函式將m_message資料物件儲存到磁碟中。如程式清單8所示:

程式清單8   序列化資料物件

void CMultiStringDoc::Serialize(CArchive& ar)

{

    m_messages.Serialize(ar);

        if (ar.IsStoring())

        {

        }

        else

        {

        }

}

讀者可以看出,在序列化m_messages資料物件後,在文件類的Serialize( )函式中已經沒什麼需要做的了。請注意對m_messages的呼叫。Serialize( )把檔案物件作為唯一的參量進行了傳遞。現在建立MultiString並測試它,它將會完成所期望的功能。

直接讀寫檔案

儘管使用MFC內建的序列化功能是儲存和載入資料的便捷方式,但是某些時候需要對檔案處理過程有更多的控制權。例如,可能需要對使用者的檔案進行非連續的操作,某些需要I/O流、而Serialize( )函式和它相關的CArchive物件又不能進行處理的操作。在這種情況下,你可以幾乎與在非Windows程式中完全一樣來處理檔案,直接對檔案進行建立、讀取和寫入。即使需要深入到這個水平來進行檔案處理,MFC也提供了幫助,尤其是可以使用CFile類和其派生類來直接處理檔案。

CFile類

MFC的CFile類封裝了所有在處理任意型別的檔案時所需的函式。無論是希望進行通常的順序的資料儲存和載入還是希望構造一個隨機訪問檔案,CFile都將支援。使用CFi1e類和老式的C方式的檔案處理非常相似,但該類隱藏了一些繁雜工作的細節以使得此項工作快捷而又方便。例如,僅需一行程式碼就可以建立一個檔案用作讀取。表1列出了CFile類的成員函式和對它們的說明。

表1  CFile類的成員函式

  函    數                說      明

Constructor    建立CFile物件。如果傳遞的是一個檔名,就開啟該檔案

Destructor    釋放超出範圍的CFile物件。如果檔案已開啟,則關閉它

Abort( )        立即關閉檔案而不管是否出錯

Close( )        關閉檔案

Duplicate( )    建立一個可複製的檔案物件

Flush( )        清除流中資料

GetFileName( )    獲取檔名

GetFilePath( )    獲取檔案的全路徑

GetFileTitle( )    獲取檔案標題(不帶副檔名的檔名)

GetLength( )    獲取檔案長度

GetPosition( )    獲取檔案當前的位置

GetStatus( )    獲取檔案的狀態

LockRange( )    鎖定檔案的一部分

Open( ) 開啟檔案

Read( ) 從檔案中讀取資料

Remove( )    刪除檔案

Rename( )    重新命名檔案

Seek( )          設定檔案中位置

SeekToBegin( )    設定至檔案頭的位置

SeekToEnd( )    設定至檔案尾的位置

SetFilePath( )    設定檔案路徑

SetLength( )    設定檔案長度

SetStatus( )    設定檔案狀態

UnlockRange( )    對檔案的一部分進行解鎖

Write( )         向檔案中寫資料

讀者可以從表中看出,CFile類提供大量的檔案處理能力。下面演示瞭如何呼叫CFile類的一些成員函式。然而,其他函式的絕大部分也是非常容易使用的。

下面是一個簡單的程式碼片段,它建立和開啟一個檔案,向檔案中寫入一個串,然後收集一些關於該檔案的資訊:

    // Create the file.

    CFile file(襎ESTFILE.TXT*, CFile::modeCreate || CFile::modeWrite);

    // Write data to the file.

    CString message(襀ello file!*);

    int length = message.GetLength();

    file.Write((LPCTSTR)message, length);

    // Obtain information about the file.

    CString filePath = file.GetFilePath();

    Int fileLength = file.GetLength();

注意,在給Constructor函式(其引數是檔名和開啟狀態標誌)傳遞檔名時,並不需要顯式地開啟檔案。一次可以使用多個狀態標誌,可以簡單地將它們“或”在一起。這些狀態標誌描述瞭如何開啟檔案、何種操作是有效的。它們作為CFile類的一部分來定義,其含義如下表:

表2  檔案狀態標誌

    標    記              說      明

CFile::modeCreate    建立新的檔案或把已存在的檔案長度截為0

CFile::modeNoInherit 不允許子過程繼承

CFile::modeNoTruncate    建立檔案時,如果檔案已經存在則不對檔案截斷

CFile::modeRead  只允許讀操作

CFile::modeReadWrite 允許讀和寫操作

CFile::modeWrite 只允許寫操作

CFile::shareCompat   允許其他過程開啟檔案

CFile::shareDenyNone 允許其他過程對檔案進行讀寫

CFile::shareDenyRead 不允許其他過程對文件進行讀操作

CFile::shareDenyWrite    不允許其他過程對文件進行寫操作

CFile::shareExclusive    不允許其他過程訪問

CFile::typeBinary    設定檔案為二進位制模式

CFile::typeText  設定檔案為文字模式

CFile::Write( )需要一個指向存放資料的緩衝區的指標和要寫的位元組數。注意在呼叫Write ( )時,LPCTSTR是強制轉換運算子。它是由CString類定義的,它提取類中的字串。

此外,這裡沒有呼叫Close( ),當檔案超出範圍之外時,CFile的解構函式自動關閉檔案。

從檔案中讀並不比向檔案中寫更難:

    // Open the file.

    CFile file(襎ESTFILE.TXT*, CFile::modeRead);

    // Read data from the file.

    char s[81];

    int bytesRead = file.Read(s, 80);

    s[bytesRead] = 0;

    CString message = s;

當帶著CFile::modeRead狀態標記開啟檔案時,這種方式開啟的檔案僅用於讀操作。其後,程式建立一個字元緩衝區並呼叫檔案物件的Read( )成員函式來向緩衝區讀入資料。Read( )函式的兩個參量為緩衝區的地址和讀入位元組的個數。此函式返回實際讀入的位元組個數,在該例中它幾乎總是小於80。使用所讀取的位元組個數,程式可在字元資料的末尾加0來建立一個標準的C型別字串,該字串可用於設定CString變數。

上面的程式碼片段用了個比較難的檔名。要想讓你的使用者提供檔名,在聯機幫助中檢視MFC類CFileDialog,你可以很容易增加這個功能。要給程式新增一些特色是非常簡單的。

建立自己的CArchive物件

儘管可以使用CFile物件處理檔案,但可以更進一步並且建立自己的CArchive物件,就像在Serialize( )函式中使用的CArchive物件一樣,讀者完全可以使用自己建立的CArchive物件。這使得讀者可以利用為其他物件編寫的Serialize( )函式,向它們傳遞對自己的檔案物件的引用。

為建立一個檔案檔案,需建立一個CFile物件並將其傳遞給CArchive的建構函式。例如,如果想將物件通過檔案檔案寫入到一檔案中去,可建立如下的檔案檔案:

CFile file("FILENAME.EXT", CFile::modeWrite);

CArchive ar(&file, CArchive::store);

在建立檔案檔案物件後,可以如MFC所提供的檔案檔案物件一樣使用該檔案檔案。由於採用CArchive::store標記進行檔案檔案物件的建立,對IsStoring( )的呼叫返回TRUE,而且把物件轉儲到檔案檔案的程式碼開始執行。當完成檔案檔案物件後,可按如下方式關閉檔案檔案:

ar.Close( );

file.Close( );

在讀者已經完成它們後,如果物件超出應用範圍,可以安全地忽略對Close( )的呼叫,這是因為CArchive和CFile已經在解構函式中呼叫了Close( )。

理解註冊

在Windows程式設計的早期,應用程式在初始化檔案(.ini檔案)中儲存設定和選項。使用巨大的WIN.INI檔案或無數的私有INI檔案的日子已經過去了—當一個應用程式希望儲存私有資訊時,它使用集中的系統“註冊”。而且,儘管“註冊”很容易使得程序間共享資訊,但對於程式設計師而言,則使情況更加令人迷惑。下面,將揭開“註冊”的神祕面紗和學習如何在你的應用程式中控制它。

註冊是如何設定的

不同於可以用任何文字編輯器進行編輯的純文字檔案的INI檔案,“註冊(Registry)”包含的二進位制和AscII資訊只能用Registry Editor進行編輯,或者是用為管理Registry專門建立的特殊API函式呼叫來進行編輯。如果讀者曾經使用過Registry Editor瀏覽系統的註冊,就會知道它包含了大量的組織成樹形結構的資訊。首次執行Registry Editor時的註冊,可以在Windows資料夾中找到稱為REGEDIT.EXE的Registry Editor,或者可以在Start選單的Run命令中鍵入regedit然後單擊OK。

在左邊的視窗中列出了Registry的預定義鍵。在鍵旁的加號表示可以開啟此鍵來檢視更多與之相關的資訊。鍵可以有子鍵,子鍵可以有自己的子鍵。一個鍵或子鍵可以有也可以沒有與之相關聯的一個值。如果在層次中探尋得比較深入的活,可以看見在右邊視窗中的一列數值。要想親眼看到這些值,可以從HKEY_CURRENT_USER到Control Panel到Appearance到Schemes進行瀏覽,可以看到在系統上所安裝的桌面系統。

預定義的鍵

為了知道在Registry中所儲存的的東西在哪裡,就需要知道預定義鍵和它們所代表的意思。

* HKEY_CLASSES_ROOT

* HKEY_CURRENT_USER

* HKEY_LOCAL_MACHINE

* KEY_USERS

* KEY_CURRENT_CONFIG

* KEY_DYN_DATA

HKEY_CLASSES_ROOT鍵儲存了文件型別和屬性,以及有關安裝在機器上的各種應用程式的分類資訊。例如,如果在系統中單擊此鍵,可能可以找到副檔名為.DOC的一個條目,在其下可以發現能處理此類文件的應用程式的條目。

HKEY_CURRENT_USER鍵包含當前使用者的所有系統設定,包括配色方案、印表機和程式組。HKEY_LOCAL_MACHINE鍵儲存了計算機的狀態資訊,KEY_USERS鍵組織了系統中的每一個使用者的資訊及系統的預設設定,KEY_CURRENT_CONFIG鍵儲存了硬體配置資訊。KEY_DYN_DATA鍵儲存了有關動態註冊的資料資訊,這些資料是會頻繁變動的。(在讀者的系統中,不總是可以看這個鍵。)

在MFC應用程式中使用“註冊”

現在讀者瞭解了一些關於註冊的知識,但完整的解釋如何訪問和使用註冊需要一本書來進行闡述。正如讀者可以想象的那樣,Win32 API有許多操作註冊的函式。但是,如果想使用這些函式,必須有把握!無效的註冊設定可能破壞系統,使得它不能啟動,也可能會使你不得不重新安裝Windows。然而,通過MFC應用程式可以很容易的使用註冊來儲存讀者的應用程式不同部分所需的資訊。為了儘可能簡化,MFC提供了帶有SetRegistrykey ( )成員函式的CWinApp類,它在註冊中建立(或開啟)一個應用程式的按鍵條目,所需做的僅是提供一個鍵名(通常是一個公司名)給函式使用,如下所示:

SetRegistryKey("MyCoolCompany");

應該在應用程式類的InitInstance( )成員函式中呼叫SetRegistrykey( ),該成員函式將在程式啟動時立即呼叫。

在呼叫過SetRegistrykey( )之後,讀者的應用程式可以通過呼叫兩個函式中的一個來建立一個子鍵及其所需數值,WriteProfileString( )函式往註冊中增加一個字串值,WriteProfileInt( )函式註冊中增加一個整數值。要想從註冊取得資料,可以使用GetProfileString( )和GetProfileInt( )函式(也可以使用RegSetValueEx( ) 和RegQueryValueEx( )來設定和檢索註冊中的值)。

注意   當第一次寫入時,WriteProfileString( )、WriteProfileInt( )以及GetProfileString( )和GetProfileInt( )函式向(和從)INI檔案中進行資料傳遞。單獨使用時,它們仍然起作用。但是,當首先呼叫SetRegistrykey( )時,MFC重新調整這些函式對註冊的訪問,這樣使用註冊成了一個簡單的過程。

簡單的應用程式

讀者已經建立了一個使用註冊的應用程式。這兒有一段從CMultiStringApp :: InitInstance( )中摘錄的程式碼,它是由AppWizard生成的,它同樣也存在於CFileDemoApp: : InitInstance( )之中。

// Change the registry key under which our settings are stored.

// You should modify this string to be something appropriate

// such as the name of your company or organization.

SetRegistryKey(_T(襆ocal AppWizard-Generated Applications*));

LoadStdProfileSettings();  // Load standard INI file options (including MRU)

MRU代表Most Recently Used(最近使用),指的是用應用程式開啟檔案後出現在File選單中的檔案列表。Registry Editor顯示儲存此資訊的鍵:HKEY_CURRENT_USER\Software\Local AppWizard-Generated Applications\MultiString\Recent File List。在前臺,MultiString的File選單在MRU列表中顯示一個條目。