序列化:把对象信息保存到存储区(如把对象信息写到文件)
反序列化:从存储区中读取对象并创建对象(从文件中读取对象信息并创建对象)
MFC序列化使用步骤
- 将所要序列化的类先继承
CObject
- 类中重写虚函数
void Serialize(CArchive& archive)
- 在类的声明中使用宏
DECLARE_SERIAL(CPerson)
- 类中必须提供一个默认构造
- 在类的实现中使用宏
DECLARE_SERIAL(CPerson)
class CPerson : public CObject//第一步:继承自CObject类
{
DECLARE_SERIAL(CPerson)//第三步:类中添加宏DECLARE_SERIAL
public:
CPerson() {}//第四步:提供默认构造
CPerson(char* szName, int nAge):m_csName(szName), m_nAge(nAge){}
void Serialize(CArchive& archive) override;//第二步:重写虚函数
private:
CString m_csName;
int m_nAge;
};
IMPLEMENT_SERIAL(CPerson, CObject, 1)//第五步:类实现中,添加宏
void CPerson::Serialize(CArchive& archive) override
{
CObject::Serialize(archive);
if (archive.IsStoring()) //存储写入到存储区
{
archive << m_csName << m_nAge;
}
else //从存储区读取数据,
{
archive >> m_csName >> m_nAge;
}
}
MFC序列化主要是靠RTTI和CARchive这个类进行读写操作,看MFC的宏就可以看出
#define DECLARE_SERIAL(class_name) \
_DECLARE_DYNCREATE(class_name) \ 调用的也是动态创建这个宏,我在博客中已经分析过这个宏的作用,这个宏主要是操作CRuntimeClass这个结构体,通过这个结构体形成RTTI,而通过RTTI就可以获取这个对象的信息,这样就可以通过CARchive进行读写对象
而CRuntimeClass形成序列化的RTTI的链表用的是另一个成员m_pNextClass,如下就是序列化主要用到的成员
void AFXAPI AfxClassInit(CRuntimeClass* pNewClass);//这个函数用来把注册的类添加进链表
struct AFX_CLASSINIT
{ AFX_CLASSINIT(CRuntimeClass* pNewClass) { AfxClassInit(pNewClass); } };//通过构造函数调用AfxClassInit
struct CRuntimeClass
{
CRuntimeClass* m_pNextClass; // 指向下一个注册类的链表指针
const AFX_CLASSINIT* m_pClassInit;//通过这个类的构造函数把自己注册添加进链表,m_pNextClass指向下一个注册类
};
简单模拟MFC序列化例子
//1. CObject类中添加一个虚函数,用于存储和读取数据
// 子类重写虚函数可以实现自身数据的存储和读取
virtual void Serialize(FILE* file, BOOL bStore);
//2. 保存一个全局对象的CRuntimeClass指针链表的头节点
//将所有类的信息保存起来,用于反序列化的时候根据类名new对应对象
CRuntimeClass* g_pRuntimeClassLstHead = NULL;
//全局函数将类信息添加到链表中
void AfxClassInit(CRuntimeClass* pNewRuntimeClass)
{
pNewRuntimeClass->m_pNextClass = g_pRuntimeClassLstHead;
g_pRuntimeClassLstHead = pNewRuntimeClass;
}
//每个类中都会定义一个此结构体类型的静态成员,通过构造函数添加类信息
struct AFX_CLASSINIT
{
AFX_CLASSINIT(CRuntimeClass* pNewRuntimeClass)
{
AfxClassInit(pNewRuntimeClass);
}
};
//3. 目标类中定义一个静态成员,在其类的构造中将类信息添加到全局链表中
class CObject
{
public:
CObject();
virtual ~CObject();
virtual void Serialize(FILE* file, BOOL bStore);
static CRuntimeClass classCObject;
static AFX_CLASSINIT _init_CObject; //通过此成员的构造函数,将类信息添加到全局链表中
virtual CRuntimeClass* GetRuntimeClass() const;
static CObject* CreateObject();
};
CRuntimeClass CObject::classCObject = {
"CObject",
sizeof(class CObject),
0xFFFF,
NULL,
NULL
};
//静态成员实现,目的:添加此类的类信息的全局链表中
AFX_CLASSINIT CObject::_init_CObject((CRuntimeClass*)&CObject::classCObject);
//3. 封装一个CArchive类用于操作文件,存读数据
class CArchive : public CObject
{
public:
CArchive(FILE* file)
{
m_file = file;
}
virtual ~CArchive();
void WriteObject(FILE* file, CObject* pObj)
{
//写入对象的类型信息
CRuntimeClass* pRC = pObj->GetRuntimeClass();
int nLen = strlen(pRC->m_lpszClassName);
fwrite(&nLen, sizeof(int), 1, file);
fwrite(pRC->m_lpszClassName, nLen, 1, file);
//写入对象的数据
pObj->Serialize(file, TRUE);
}
void ReadObject(FILE* file, CObject*& pObj)
{
//1. 读取类名
int nLen = 0;
fread(&nLen, sizeof(int), 1, file);
char* szName = new char[nLen + 1];
fread(szName, nLen, 1, file);
szName[nLen] = 0;
//2. 到RTTI链表中查找对应的RuntimeClass
CRuntimeClass* pRC = g_pRuntimeClassLstHead;
CRuntimeClass* pCurrentRC = NULL;
while (pRC != NULL)
{
if (strcmp(pRC->m_lpszClassName, szName) == 0)
{
pCurrentRC = pRC;
break;
}
pRC = pRC->m_pNextClass;
}
if (pCurrentRC == NULL)
{
return;
}
//3. 动态创建对象
pObj = pCurrentRC->CreateObject();
if (pObj == NULL)
{
return;
}
//4. 读取对象的数据
pObj->Serialize(file, FALSE);
}
public:
FILE* m_file;
};
//为了让使用更加方便,会在子类中通过友元函数重载运算符,使文件存读通过流操作即可完成
class CData : public CObject
{
DECLARE_SERIAL(CData)
public:
CData();
virtual ~CData();
virtual void Serialize(FILE* file, BOOL bStore)
{
CObject::Serialize(file, bStore);//先调用父类的虚函数,存读父类的数据
if (bStore)
{
fwrite(&m_nData, sizeof(int), 1, file);
}
else
{
fread(&m_nData, sizeof(int), 1, file);
}
}
friend CArchive& operator<<(CArchive& arc, CData* pObj) //数据写入
{
arc.WriteObject(arc.m_file, pObj);
return arc;
}
friend CArchive& operator>>(CArchive& arc, CObject*& pObj)//数据读取
{
arc.ReadObject(arc.m_file, pObj);
return arc;
}
private:
int m_nData;
};
int main(int argc, char* argv[])
{
CData data1;
FILE* file = fopen("b.bin", "w+");
CArchive arc(file);
arc << &data1;
fclose(file);
FILE* file1 = fopen("b.bin", "rb+");
CObject* pObj = NULL;
CArchive arc2(file1);
arc2 >> pObj;
fclose(file1);
}