CArchive 是 MFC 序列化架構的核心引擎,它實現了高效的緩衝區 I/O、基本型別與 CString 的流式讀寫、以及 CObject 派生物件的多態序列化。本文將從緩衝區模型、基本型別讀寫、大塊資料處理、字串序列化格式、物件圖儲存與類別資訊管理五大維度,系統剖析其內部運作機制。
CArchive 內部維護一塊連續記憶體緩衝區,三個指標協同工作:
m_lpBufStart:緩衝區起始位址m_lpBufMax:緩衝區末尾位址(容量邊界)m_lpBufCur:當前讀寫位置初始化邏輯:
m_lpBufCur = IsLoading() ? m_lpBufMax : m_lpBufStart;
// 讀模式:游標從尾部起(資料已滿)
// 寫模式:游標從頭部起(資料為空)
這設計使得「讀取」是向前消費,「寫入」是向後堆疊,直觀且高效。
CArchive& CArchive::operator<<(DWORD dw)
{
if (m_lpBufCur + sizeof(DWORD) > m_lpBufMax) // 空間不足
Flush(); // 刷入底層儲存
if (!(m_nMode & bNoByteSwap))
_AfxByteSwap(dw, m_lpBufCur); // 大小端轉換
else
*(DWORD*)m_lpBufCur = dw; // 直接寫入
m_lpBufCur += sizeof(DWORD);
return *this;
}
CArchive& CArchive::operator>>(DWORD& dw)
{
if (m_lpBufCur + sizeof(DWORD) > m_lpBufMax) // 資料不足
FillBuffer(sizeof(DWORD) - (m_lpBufMax - m_lpBufCur));
dw = *(DWORD*)m_lpBufCur; // 讀取
m_lpBufCur += sizeof(DWORD);
if (!(m_nMode & bNoByteSwap))
_AfxByteSwap(dw, (BYTE*)&dw); // 大小端轉換
return *this;
}
關鍵點:
Flush/FillBuffer。bNoByteSwap 模式控制是否執行 _AfxByteSwap,支援跨平台資料交換。*this 支援鏈式呼叫 ar << a << b << c。void CArchive::Flush()
{
if (IsLoading()) { /* 讀模式:退回未讀資料,游標歸尾 */ }
else { // 寫模式
if (!m_bDirectBuffer) {
if (m_lpBufCur != m_lpBufStart)
m_pFile->Write(m_lpBufStart, m_lpBufCur - m_lpBufStart);
} else { // CMemFile 等直接緩衝
if (m_lpBufCur != m_lpBufStart)
m_pFile->GetBufferPtr(CFile::bufferCommit, m_lpBufCur - m_lpBufStart);
// 取得下一塊緩衝區
VERIFY(m_pFile->GetBufferPtr(CFile::bufferWrite, m_nBufSize,
(void**)&m_lpBufStart, (void**)&m_lpBufMax) == (UINT)m_nBufSize);
}
m_lpBufCur = m_lpBufStart; // 重置游標
}
}
void CArchive::FillBuffer(UINT nBytesNeeded)
{
UINT nUnused = m_lpBufMax - m_lpBufCur; // 剩餘未讀
ULONG nTotalNeeded = nBytesNeeded + nUnused;
if (!m_bDirectBuffer) {
if (m_lpBufCur > m_lpBufStart) {
// 移動殘留資料至緩衝區頭部
memmove(m_lpBufStart, m_lpBufCur, nUnused);
m_lpBufCur = m_lpBufStart;
m_lpBufMax = m_lpBufStart + nUnused;
}
// 從檔案讀取滿足需求
UINT nRead = nUnused, nLeft = m_nBufSize - nUnused;
BYTE* lpTemp = m_lpBufStart + nUnused;
do {
nBytes = m_pFile->Read(lpTemp, nLeft);
lpTemp += nBytes; nRead += nBytes; nLeft -= nBytes;
} while (nBytes > 0 && nLeft > 0 && nRead < nBytesNeeded);
m_lpBufMax = m_lpBufStart + nRead;
} else { // 直接緩衝
if (nUnused) m_pFile->Seek(-(LONG)nUnused, CFile::current);
UINT nActual = m_pFile->GetBufferPtr(CFile::bufferRead, m_nBufSize,
(void**)&m_lpBufStart, (void**)&m_lpBufMax);
m_lpBufCur = m_lpBufStart;
}
if ((ULONG)(m_lpBufMax - m_lpBufCur) < nTotalNeeded)
AfxThrowArchiveException(CArchiveException::endOfFile);
}
對於大於緩衝區的資料塊,採用「頭尾用緩衝區,中間直接 I/O」策略:
m_pFile->Read/Write,繞過緩衝區。此設計顯著提升大檔案(如影像、資料庫匯出)的序列化效能。
WriteString 直接呼叫 Write(lpsz, lstrlen(lpsz)*sizeof(TCHAR)),不寫入長度前綴。ReadString 逐字元讀取直到 '\n' 或 '\r',支援負 nMax 保留換行符。寫入格式(以 Unicode 為例):
// 特徵標記
ar << (BYTE)0xff << (WORD)0xfffe;
// 長度編碼
if (len < 255) ar << (BYTE)len;
else if (len < 0xfffe) ar << (BYTE)0xff << (WORD)len;
else ar << (BYTE)0xff << (WORD)0xffff << (DWORD)len;
// 字元資料
ar.Write(str.m_pchData, len * sizeof(TCHAR));
讀取邏輯:_AfxReadStringLength 解析前綴,自動偵測 ANSI/Unicode 並轉換。
優勢:二進位安全、支援嵌入 null、跨平台字元集自適應、可嵌套於物件圖中。
void CArchive::WriteObject(const CObject* pOb)
{
MapObject(NULL); // 確保 m_pStoreMap 初始化
if (pOb == NULL) { *this << wNullTag; return; }
DWORD nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb];
if (nObIndex != 0) { // 已寫入過
*this << (nObIndex < wBigObjectTag ? (WORD)nObIndex : (wBigObjectTag, nObIndex));
return;
}
// 首次寫入
CRuntimeClass* pClassRef = pOb->GetRuntimeClass();
WriteClass(pClassRef); // 寫入類別資訊
CheckCount();
(*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
pOb->Serialize(*this); // 遞迴序列化成員
}
CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
{
UINT nSchema; DWORD obTag;
CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);
if (pClassRef == NULL) { // 共享指標標記
CObject* pOb = (CObject*)m_pLoadArray->GetAt(obTag);
// 類別相容性檢查...
return pOb;
}
// 新物件
CObject* pOb = pClassRef->CreateObject(); // 動態建立
CheckCount();
m_pLoadArray->InsertAt(m_nMapCount++, pOb); // 註冊以支援共享指標
UINT nSchemaSave = m_nObjectSchema;
m_nObjectSchema = nSchema; // 設定版本號供 Serialize 使用
pOb->Serialize(*this);
m_nObjectSchema = nSchemaSave;
return pOb;
}
m_pStoreMap(寫)與 m_pLoadArray(讀)維護 物件指標 ⇄ 索引 雙向映射,解決共享指標與循環引用。WriteClass/ReadClass 同樣使用 Map 機制,同一類別僅序列化一次 CRuntimeClass。m_nObjectSchema 傳遞給 Serialize,衍生類可根據版本決定讀寫邏輯。CArchive 以緩衝區為核心,層層封裝:
CFile 多型支援檔案、記憶體、Socket 等多種後端。掌握其內部協議(特別是物件索引與類別去重邏輯),對於除錯序列化失效、跨版本資料遷移、實作自訂儲存後端極具指導意義。