MFC 序列化提供比原始文件操作更高層、類型安全、透明字節流的持久化方案。本文系統講解使類支持序列化的五步驟、CArchive 核心操作、版本控制機制、動態數據結構(鏈表)序列化實戰、多態指針數組存儲及避坑指南。
class CMyData : public CObjectDECLARE_SERIAL(CMyData)CMyData() {} (反序列化時調用)virtual void Serialize(CArchive& ar)IMPLEMENT_SERIAL(CMyData, CObject, VERSION)void CMyData::Serialize(CArchive& ar) {
CObject::Serialize(ar); // 調用基類 (通常為空)
if (ar.IsStoring()) {
// 存儲 (寫入文件)
ar << m_strName << m_nAge << m_dSalary;
// 複雜類型
ar << m_rectBounds; // CRect 支持 <<
m_font.Serialize(ar); // CFont 等 GDI 對象有 Serialize
} else {
// 加載 (讀取文件)
ar >> m_strName >> m_nAge >> m_dSalary;
ar >> m_rectBounds;
m_font.Serialize(ar);
}
}
| 操作符 | 支持類型 | 說明 |
|---|---|---|
<< / >> | 基本類型 (int, float, double, BYTE, WORD, DWORD, LONG) | 自動處理字節序 |
<< / >> | CString, CTime, CTimeSpan, COleDateTime | 字符串長度前綴 + 內容 |
<< / >> | MFC 集合類 (CArray, CList, CMap, CStringArray 等) | 自動存儲計數 + 元素 |
<< / >> | GDI 對象 (CRect, CPoint, CSize, CFont, CPen, CBrush) | 調用對象 Serialize |
Write/Read | 原始緩衝區 | ar.Write(pBuf, len) / ar.Read(pBuf, len) |
WriteString/ReadString | 文本行 | 文本模式換行處理 |
IMPLEMENT_SERIAL(CMyData, CObject, 2) // 版本號 = 2
void CMyData::Serialize(CArchive& ar) {
int schema = ar.GetObjectSchema(); // 獲取文件中對象版本
if (ar.IsStoring()) {
// 寫入時始終用最新版本
ar << m_strName << m_nAge << m_dSalary << m_bActive; // v2 新增字段
} else {
// 讀取時根據版本兼容
ar >> m_strName >> m_nAge >> m_dSalary;
if (schema >= 2) {
ar >> m_bActive; // v2 才有此字段
} else {
m_bActive = TRUE; // v1 默認值
}
}
}
// PIDRecord.h
struct PIDNode {
char szName[32];
char szID[18];
// 注意:指針不直接序列化!
};
class CPIDList : public CObject {
DECLARE_SERIAL(CPIDList)
public:
CPIDList();
virtual ~CPIDList();
void AddRecord(LPCTSTR name, LPCTSTR id);
// ... 其他操作
protected:
PIDNode* m_pHead; // 鏈表頭指針
virtual void Serialize(CArchive& ar);
};
IMPLEMENT_SERIAL(CPIDList, CObject, 1)
void CPIDList::Serialize(CArchive& ar) {
CObject::Serialize(ar);
if (ar.IsStoring()) {
// 1. 計算節點數
int count = 0;
for (PIDNode* p = m_pHead; p; p = p->pNext) count++;
// 2. 寫入節點數
ar << count;
// 3. 順序寫入節點數據 (不寫指針!)
for (PIDNode* p = m_pHead; p; p = p->pNext) {
ar.Write(p->szName, sizeof(p->szName));
ar.Write(p->szID, sizeof(p->szID));
}
} else {
// 1. 讀取節點數
int count;
ar >> count;
// 2. 清空現有鏈表
ClearList();
// 3. 順序讀取並重建鏈表
for (int i = 0; i < count; ++i) {
PIDNode* pNode = new PIDNode;
ar.Read(pNode->szName, sizeof(pNode->szName));
ar.Read(pNode->szID, sizeof(pNode->szID));
pNode->pNext = nullptr;
AppendNode(pNode); // 尾插法重建
}
}
}
場景:CArray,CShape 有派生 CCircle, CRectangle。
// 基類必須支持動態創建
class CShape : public CObject {
DECLARE_SERIAL(CShape)
DECLARE_DYNCREATE(CShape) // 關鍵!
public:
virtual void Draw(CDC*) = 0;
virtual void Serialize(CArchive& ar);
};
IMPLEMENT_SERIAL(CShape, CObject, 1)
IMPLEMENT_DYNCREATE(CShape, CObject) // 實現動態創建
// 派生類同樣需要
class CCircle : public CShape {
DECLARE_SERIAL(CCircle)
DECLARE_DYNCREATE(CCircle)
// ...
};
IMPLEMENT_SERIAL(CCircle, CShape, 1)
IMPLEMENT_DYNCREATE(CCircle, CShape)
// 容器類序列化
void CDrawDoc::Serialize(CArchive& ar) {
if (ar.IsStoring()) {
ar << (int)m_shapes.GetSize();
for (int i = 0; i < m_shapes.GetSize(); ++i) {
m_shapes[i]->Serialize(ar); // 多態調用,自動寫入類名
}
} else {
int count;
ar >> count;
m_shapes.SetSize(count);
for (int i = 0; i < count; ++i) {
CShape* pShape = NULL;
pShape->Serialize(ar); // 關鍵:靜態調用基類 Serialize,內部會根據類名動態創建對象!
m_shapes[i] = pShape;
}
}
}
原理:
CObject::Serialize內部寫入/讀取CRuntimeClass信息,反序列化時調用CRuntimeClass::CreateObject()動態創建正確類型對象,隨後調用該對象的Serialize。
// CDocument 成員
virtual void Serialize(CArchive& ar); // 子類重載
// 框架自動調用流程
// 1. 用戶選「保存」→ CDocument::OnSaveDocument(lpszPathName)
// 2. CFile file(lpszPath, CFile::modeCreate|modeWrite)
// 3. CArchive ar(&file, CArchive::store)
// 4. Serialize(ar) → 子類實現
// 5. ar.Close(), file.Close()
// 加載同理:OnOpenDocument → CArchive::load → Serialize
pClass->CreateObject() 需無參構造CRuntimeClass 正確導出本文整理自 MFC 經典技術文檔,適用於 VC6~VS2022 所有版本。