对象的持久性和文件I/O

发表于:2007-07-14来源:作者:点击数: 标签:
内容提要 理解对象的持久性* File Demo应用程序* 创建一个持久类* 直接读写文件* 创建自己的CArchive对象* 理解注册 关键词 文章正文 对象的持久性 程序需要完成的一项最重要的事情是,当数据以某种方式改变之后,程序能够保存用户的数据。没有保存被编辑过
内容提要
      理解对象的持久性* 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列表中显示一个条目。

原文转自:http://www.ltesting.net