MFC文件操作、序列化机制

一 MFC的文件操作

   1 相关类

  CFile类-封装了文件句柄以及操作文件的API函数。

  CFileFind类-封装了文件搜索功能。

   2CFile类的使用

  2.1 文件读写

      2.1.1 创建或者打开文件

            CFile::Create

      2.1.2 文件读写

            CFile::Read/Write

      2.1.3 关闭文件

            CFile::Close

      注意:1 文件读写需要异常处理

            2 注意文件的指针位置

  2.2 文件属性的获取和设置

      2.2.1 CFile::GetStatus

      2.2.2 CFile::SetStatus

   3CFileFind类的使用

    3.1 开始查找(指定查找的目录)

        CFileFind::FindFile

    3.2 查找下一个(获取当前文件信息,返回下一个文件是否存在)

        CFileFind::FindNextFile

    3.3 获取/判断文件信息

        CFileFind::GetXXX/IsXXX

    3.4 结束查找

        CFileFind::Close

测试Cfile类编写测试程序:

新建一个Win32    Console Application 注意选择MFC库的支持

编写如下测试代码:

/*****************************************
    测试CFile
*******************************************/
void CFileTest ()
{
        // 定义CFile对象
	CFile file;
	if (! file.Open ("D:/test.txt", CFile::modeCreate | CFile::modeReadWrite))
	{// 打开文件,如果文件不存在就创建文件,并且以读和写的权限打开文件
		AfxMessageBox ("open file failed!");
		return;
	}
	try
	{
	// 写文件
	file.Write ("This is a test txt file!", strlen ("This is a test txt file!"));

	// 读文件
	char szBuf [256] = {0};
	// 注意:上面写完文件后文件指针位于文件末尾
	// 移动文件指针到文件头
	file.SeekToBegin ();
	file.Read (szBuf, 256);
	cout << szBuf << endl;
	}
	catch (CFileException* e)
	{
		// 异常处理...
		file.Close ();
	}
    
	// 关闭文件
	file.Close ();
}
/**************************************************
    测试CFile::GetStatus、CFile::SetStatus
	修改当前文件的创建日期
***************************************************/
void  FileStatusTest ()
{
	CFileStatus status;// 保存文件状态信息的结构体
	CFile::GetStatus ("D:/test.txt", status);
	CTimeSpan span (7,0, 0, 0);// CTimeSpan 时间间隔类
        status.m_ctime -=  span;
	CFile::SetStatus ("D:/test.txt", status);
}
/*****************************************************
   测试CFileFind, 查找指定路径下的所有文件
******************************************************/
void CFileFindTest (CString strPath)
{
	// 查找strPath路径下的所有(*.*)文件
	strPath += "/*.*";
	CFileFind find;
	// 开始查找
	BOOL bRet = find.FindFile (strPath);
	while (bRet)
	{
		// 获取当前文件信息并查找下一个文件
		bRet = find.FindNextFile ();
		if (! bRet)
			return;
		if (! find.IsDots())
		{// 点目录不处理,防止死循环
			if (find.IsDirectory ())
			{// 目录则继续进入查找
				// cout 无法输出CString类型
				//cout << "[" << find.GetFileName () << "]" << endl;
				printf ("[%s]\n", find.GetFileName ());
				CFileFindTest (find.GetFilePath ());
			}
			else
			{// 输出普通文件
				//cout << find.GetFileName() << endl;
				printf ("%s\n", find.GetFileName ());
			}
		}
	}
	find.Close ();
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
        //CFileTest ();
	//FileStatusTest ();
	CFileFindTest ("D:");
	return 0;
}

注意:

文件同时具备读写权限时要写成:CFile::modeReadWrite而不是分开写读和写权限。

CtimeSpan 为是时间间隔类,可以用来对现有时间做加减运算

Cout不能输出Cstring类型,应该使用printf

使用Cfile操作文件时使用异常处理



序列化
   这里之所以把文件操作和序列化放在一起,是因为序列化主要是用来方便文件操作的
   1 概念-将数据以二进制流的方式依次写入到文件或者从文件中读取的过程。
   2 相关类
     CArchive类-功能是完成具体的数据读写。(代替CFile类的Read/Write函数)。
   3 使用
     3.1 创建或者打开文件
             CFile::Create
     3.2 文件读写
         3.2.1 构造CArchive对象
         3.2.2 数据读写
               >>  读操作
               <<  写操作
         3.2.3 关闭CArchive对象
               CArchive::Close    
     3.3 关闭文件
             CFile::Close

 新建一个Win32 Console Application, 选择MFC库的支持

 编写如下测试代码:

// 存储数据的过程(写操作)
void Store()
{
	// 打开或者新建文件
	CFile file;
	BOOL bRet=file.Open("c:/serial.dat",
		CFile::modeCreate|CFile::modeWrite);
	if (!bRet)
	{
		printf("文件打开失败!");
		return;
	}
	// 构造CArchive对象
	CArchive ar(&file,CArchive::store);
	// 写数据
	ar<<100<<12.25<<'A';
	// 关闭CArchive对象
	ar.Close();
	// 关闭文件
	file.Close();

}
// 加载数据的过程(读操作)
void Load()
{
	// 打开文件
	CFile file;
	BOOL bRet=file.Open("c://serial.dat",CFile::modeRead);
	if (!bRet)return;
    // 构造CArchive对象
	CArchive ar(&file,CArchive::load);
	// 读数据
	int iValue=0;
	double dValue=0.0;
	char cValue;
	ar>>iValue>>dValue>>cValue;
	// 关闭对象
	ar.Close();
	// 关闭文件
	file.Close();
	// 输出
	printf("iValue=%d\n",iValue);
	printf("dValue=%f\n",dValue);
	printf("cValue=%c\n",cValue);


}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	Store();
	Load();
	return 0;
}

注意:

         在Win C下文件路径写成C:/serial.dat 永远要比 C:\\serial.dat 要好,一个反斜杠“/”不仅写起来简单而且具有更好的通用性,不易出错。

         文件与CArchive的模式要一致,写<<   CArchive::store,  读 >> CArchive::load
         数据读写的顺序要一致

        一般如果我们为类添加了有参构造函数,那么建议再写一个无参的默认构造函数以便系统在初始化的时候调用,这是一种习惯

对象的序列化:

  1 概念
     序列化对象-将对象的类的信息以及对象的成员变量以二进制流的方式依次写入到文件的过程。
     反序列化对象-从文件中首先读取类的信息创建对象,然后读取成员变量赋值给新建的对象的过程。
   2 定义支持序列化的类
     2.1 派生自CObject类
     2.2 在类的定义中添加序列化的声明宏 DECLARE_SERIAL(className)
     2.3 在类的实现中添加序列化的实现宏 IMPLEMENT_SERIAL(...)
     2.4 重写CObject::Serialize()函数,在函数中,完成成员变量的序列化。
   3 使用
     在读写对象时,参数是对象的指针。

   新建一个Win32 Console Application, 选择MFC库的支持

  编写如下测试代码: 其中定义了一个支持序列化的类CPerson ,且CPerson中包含了一个普通类的成员m_course, 把该成员当普通变量用即可

  

// 支持序列化的课程类
class Course
{
public:
	int m_iNO;
	CString m_strName;
};
// 定义一个支持序列化的类
class CPerson : public CObject
{
public:
	void Show ()
	{
		printf ("Name:%s,Age:%d\niNO:%d, CourseName:%s\n", m_strName, m_iAge, m_course.m_iNO ,m_course.m_strName);
	}
	CPerson (){};
	CPerson (int age,  CString strName, int iNO, CString strCourName) : m_iAge (age), m_strName (strName)
	{
		m_course.m_iNO  = iNO;
		m_course.m_strName = strCourName;
	}
	virtual void Serialize (CArchive &ar);
private:
	int m_iAge;
	CString m_strName;
	Course m_course;
    // 序列化声明宏
	DECLARE_SERIAL (CPerson)
};
// 序列化实现宏
IMPLEMENT_SERIAL (CPerson, CObject, 1)
void CPerson::Serialize (CArchive &ar)
{
	// 如果父类成员需要序列化时,首先调用父类相关函数
	CObject::Serialize (ar);
	if (ar.IsStoring ())// 写
		ar << m_iAge << m_strName << m_course.m_iNO << m_course.m_strName;
	else// 读
		ar >> m_iAge >> m_strName >> m_course.m_iNO >> m_course.m_strName;
}
// 写对象
void ObjectStore (CPerson* pPerson)
{
	CFile file;
	BOOL bRet = file.Open ("E:/Serial.dat", CFile::modeCreate | CFile::modeWrite);
    if (! bRet)
		return;
	CArchive ar(&file, CArchive::store);
	ar << pPerson;

	ar.Close();
	file.Close ();
}
// 读对象
void ObjectLoad ()
{
	CFile file;
	BOOL bRet = file.Open ("E:/Serial.dat", CFile::modeRead);
	if (! bRet)
		return;
	CArchive ar (&file, CArchive::load);
	// 注意这里是指针,对象不用我们去定义
	CPerson *pPerson = NULL;
	ar >> pPerson;
	if (pPerson)
	{
	        pPerson->Show ();
		delete pPerson;
		pPerson = NULL;
	}

	ar.Close();
	file.Close ();
}
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
	CPerson person (24, "关羽", 1, "武术");
	ObjectStore (&person);
	ObjectLoad ();
	return 0;
}

实现原理:

先展开两个宏:

DECLARE_SERIAL(CPerson)

        _DECLARE_DYNCREATE(CPerson) 
	AFX_API friend CArchive& AFXAPI 
	operator>>(CArchive& ar, CPerson* &pOb);

IMPLEMENT_SERIAL(CPerson,CObject,1)

CObject* PASCAL CPerson::CreateObject() 
{ 
	return new CPerson; 
}  
_IMPLEMENT_RUNTIMECLASS(CPerson, CObject, 1, CPerson::CreateObject) 
AFX_CLASSINIT _init_CPerson(RUNTIME_CLASS(CPerson));
CArchive& AFXAPI operator>>(CArchive& ar, CPerson* &pOb)
{ 
	pOb = (CPerson*) ar.ReadObject(RUNTIME_CLASS(CPerson)); 
			return ar; 
} 

很容易看到序列化的申明和实现宏张开后明显包含MFC的: 动态创建机制 和 运行时类信息机制

那么就不难理解为什么CArchive 只需要对象指针就可以操作一个对象了(运行式动态创建对象)

首先展开宏后观察几个成员的作用:

相关结构体

struct AFX_CLASSINIT
{ 
          AFX_CLASSINIT(CRuntimeClass* pNewClass) 
          { 
             AfxClassInit(pNewClass);
             {
               pModuleState->m_classList.AddHead(pNewClass);
             } 
           } 
};

该结构体就包含一个函数:把运行时类信息的地址加入到当前程序模块状态信息的一个链表成员变量m_classList中


operator>>,友元函数,读对象的函数。设置友元的目的是得到当前类的私有成员。


_init_CPerson,全局的结构体变量。作用是将当前类的运行时类信息的地址保存到模块状态信息的一个链表m_classList中。


写对象的过程跟踪:


ar<<pPerson

跟进:

_AFX_INLINE CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ 
	ar.WriteObject(pOb);
	return ar;
}
ar.WriteObject(pOb)

跟进:

void CArchive::WriteObject(const CObject* pOb)
{// pOb === pPerson
    ..........................................
	// write class of object first
	// 获取当前类的运行时类信息
	CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
	// 将类的信息写入到文件
	WriteClass(pClassRef);

	.......................................

	// cause the object to serialize itself
	// 然后写入类成员变量到文件,由于虚函数机制,这里调用我们重写的函数
	((CObject*)pOb)->Serialize(*this);
}
WriteObject-->WriteClass(pClassRef)

跟进:

void CArchive::WriteClass(const CRuntimeClass* pClassRef)
{
       .......................................................
		// store new class
	    // 一次将类的版本、长度、类名称写入到文件
		pClassRef->Store(*this);
       ......................................
}
WriteObject-->WriteClass-->Store(*this)

跟进:

void CRuntimeClass::Store(CArchive& ar) const
	// stores a runtime class description
{
	// m_lpszClassName 类名称
	WORD nLen = (WORD)lstrlenA(m_lpszClassName);
	// 写入类名称、版本
	ar << (WORD)m_wSchema << nLen;
	ar.Write(m_lpszClassName, nLen*sizeof(char));
}

WriteObject-->Serialize(*this)

跟进:

void CPerson::Serialize( CArchive& ar )
{// 我们重写的函数,完成类成员变量的写入

	//如果父类成员变量需要序列化时,首先调用父类相关函数
	CObject::Serialize(ar);
	if (ar.IsStoring())//写
	{
		ar<<m_strName<<m_iAge;
	}
	...............
}
总结以上流程如下:

  ar.WriteObject(pOb);
   {
      //1 获取当前类的运行时类信息
      pOb->GetRuntimeClass();
      //2 将类的信息写入到文件 
      WriteClass(pClassRef);
      {
        //依次将类的版本、类名称长度以及类名称写入到文件
        pClassRef->Store(*this);
        {
          WORD nLen = (WORD)lstrlenA(m_lpszClassName);
	  ar << (WORD)m_wSchema << nLen;
	  ar.Write(m_lpszClassName, nLen*sizeof(char));
        }
      }
      //3 由于虚函数机制,调用CPerson::Serialize()函数,
      //  在函数中,将成员变量写入到文件
      ((CObject*)pOb)->Serialize(*this);
      {
        if (ar.IsStoring())//写
	{
		ar<<m_strName<<m_iAge;
	}
        ...
      } 

   } 


下面分析下读取数据的过程:

ar>>pPerson

跟进:

CArchive& AFXAPI operator>>(CArchive& ar, CPerson* &pOb)
{ 
	pOb = (CPerson*) ar.ReadObject(RUNTIME_CLASS(CPerson)); 
			return ar; 
}

ReadObject(RUNTIME_CLASS(CPerson))

跟进:

CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
	....................................................
    // 读取类的信息
	CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);

	// check to see if tag to already loaded object
	CObject* pOb;
    .................................................
	// allocate a new object based on the class just acquired
	// 调用运行时类信息的函数动态创建CPerson对象
	pOb = pClassRef->CreateObject();
	.....................................................
	// 调用我们重写的函数
	pOb->Serialize(*this);
	..........................................

	return pOb;
}

ReadObject-->ReadClass(pClassRefRequested, &nSchema, &obTag)

跟进:

CRuntimeClass* CArchive::ReadClass(const CRuntimeClass* pClassRefRequested,
	UINT* pSchema, DWORD* pObTag)
{
	........................................................
	// new object follows a new class id
	if ((pClassRef = CRuntimeClass::Load(*this, &nSchema)) == NULL)
			AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);
}

ReadObject-->ReadClass-->Load(*this, &nSchema)

跟进:

CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar, UINT* pwSchemaNum)
	// loads a runtime class description
{

	// 一次读取 类版本、类名称长度、类名称
	WORD nLen;
	char szClassName[64];
	CRuntimeClass* pClass;

	WORD wTemp;
	ar >> wTemp; 
	*pwSchemaNum = wTemp;
	ar >> nLen;

	if (nLen >= _countof(szClassName) ||
		ar.Read(szClassName, nLen*sizeof(char)) != nLen*sizeof(char))
	{
		return NULL;
	}
	// 这里就是为什么前面要保存类名称长度
	szClassName[nLen] = '\0';


	// search app specific classes
	AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
	AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
	for (pClass = pModuleState->m_classList; pClass != NULL;
		pClass = pClass->m_pNextClass)
	{// 根据类名称在链表中的到对应的运行时类信息地址
		if (lstrcmpA(szClassName, pClass->m_lpszClassName) == 0)
		{
			AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
			return pClass;
		}
	}
    .......................................
}

ReadObject-->Serialize(*this)

跟进:

void CPerson::Serialize( CArchive& ar )
{
    ......................
	else
	{// 读取
       ar >> m_strName >> m_iAge;
	}
}

注意:到这里我们要明白为什么在读取完数据之后要delete pPerson对象了,应为pPerson是CArchive帮我们动态创建的对象(new)

总结上面的流程如下:

   ar.ReadObject(RUNTIME_CLASS(CPerson)); 
   { 
      //1 读取类的信息
      ReadClass(pClassRefRequested, &nSchema, &obTag);
      {
         //1.1 依次读取版本,类名称长度,类名称
         CRuntimeClass::Load(*this,...)
         {
            ar >> wTemp;
            ar >> nLen;
            ar.Read(szClassName,...);
            //得到一个完整的类名
            szClassName[nLen] = '\0'; 
         } 
        //1.2 根据类名在链表中得到对应的运行时类信息的地址
       	for (pClass = pModuleState->m_classList; 
            pClass != NULL;pClass = pClass->m_pNextClass)
        {
            if (lstrcmpA(szClassName, 
                            pClass->m_lpszClassName) == 0)
	    {
		 return pClass;
	    }
        }
		      
      }
      // 2 调用运行时类信息的函数动态创建CPerson对象
      pOb = pClassRef->CreateObject();
      // 3 同样虚函数机制,调用CPerson::Serialize()函数
      pOb->Serialize(*this);
      {
         ...
         else//读取
	{
	   ar>>m_strName>>m_iAge;
	}
      }  
   }



发布了93 篇原创文章 · 获赞 67 · 访问量 100万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章