MFC 提供CArchive類實作資料的緩沖區讀寫,同時定義了類對象的存儲與讀取方案。
以下對CArchvie 的内部實作作分析。
1.概述
2.内部資料
3.基本資料讀寫
4.緩沖區的更新
5.指定長度資料段落的讀寫
6.字元串的讀寫
7.CObject派生對象的讀寫
1.概述
CArchive使用了緩沖區,即一段記憶體空間作為臨時資料存儲地,對CArchive的讀寫都先依次排列到此緩沖區,當緩沖區滿或使用者要求時,将此段整理後的資料讀寫到指定的存儲煤質。
當建立CArchive對象時,應指定其模式是用于緩沖區讀,還是用于緩沖區寫。
可以這樣了解,CArchive對象相當于鐵路的貨運練排程站,零散的貨物被收集,當總量到達火車運量的時候,由火車裝運走。
當接到火車的貨物時,則貨物由被分散到各自的貨主。與貨運不同的是,交貨、取貨是按時間循序執行的,而不是憑票據。是以必須保證送貨的和取貨的貨主按同樣的循序去存或取。
對于大型的貨物,則是拆散成火車機關,運走,取貨時,依次取各部分,組裝成原物。
2.内部資料
緩沖區指針 BYTE* m_lpBufStart,指向緩沖區,這個緩沖區有可能是底層CFile(如派生類CMemFile)對象提供的,但一般是CArchive自己建立的。
緩沖區尾部指針 BYTE* m_lpBufMax;
緩沖區目前位置指針 BYTE* m_lpBufCur;
初始化時,如果是讀模式,目前位置在尾部,如果是寫模式,目前位置在頭部:
m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
3.基本資料讀寫
對于基本的資料類型,例如位元組、雙字等,可以直接使用">>"、"<<"符号進行讀出、寫入。
//操作符定義捕:
//插入操作
CArchive& operator<<(BYTE by);
CArchive& operator<<(WORD w);
CArchive& operator<<(LONG l);
CArchive& operator<<(DWORD dw);
CArchive& operator<<(float f);
CArchive& operator<<(double d);
CArchive& operator<<(int i);
CArchive& operator<<(short w);
CArchive& operator<<(char ch);
CArchive& operator<<(unsigned u);
//提取操作
CArchive& operator>>(BYTE& by);
CArchive& operator>>(WORD& w);
CArchive& operator>>(DWORD& dw);
CArchive& operator>>(LONG& l);
CArchive& operator>>(float& f);
CArchive& operator>>(double& d);
CArchive& operator>>(int& i);
CArchive& operator>>(short& w);
CArchive& operator>>(char& ch);
CArchive& operator>>(unsigned& u);
下面以雙字為例,分析原碼
雙字的插入(寫)
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) - (UINT)(m_lpBufMax - m_lpBufCur)); //重新讀入内容到緩沖區
dw = *(DWORD*)m_lpBufCur; //讀取雙字
m_lpBufCur += sizeof(DWORD); //移動目前位置指針
_AfxByteSwap(dw, (BYTE*)&dw); //處理位元組順序
4.緩沖區的更新
以上操作中,當緩沖區将插入滿或緩沖區将提取空時,都将對緩沖區進行更新處理
緩沖區将插入緻滿時調用Flush();
void CArchive::Flush()
ASSERT_VALID(m_pFile);
ASSERT(m_bDirectBuffer || m_lpBufStart != NULL);
ASSERT(m_bDirectBuffer || m_lpBufCur != NULL);
ASSERT(m_lpBufStart == NULL ||
AfxIsValidAddress(m_lpBufStart, m_lpBufMax - m_lpBufStart, IsStoring()));
ASSERT(m_lpBufCur == NULL ||
AfxIsValidAddress(m_lpBufCur, m_lpBufMax - m_lpBufCur, IsStoring()));
if (IsLoading())
{
// unget the characters in the buffer, seek back unused amount
if (m_lpBufMax != m_lpBufCur)
m_pFile-> Seek(-(m_lpBufMax - m_lpBufCur), CFile::current);
m_lpBufCur = m_lpBufMax; // 指向尾
}
else //寫模式
if (!m_bDirectBuffer)
{
// 内容寫入到檔案
if (m_lpBufCur != m_lpBufStart)
m_pFile-> Write(m_lpBufStart, m_lpBufCur - m_lpBufStart);
}
else
//如果是直接針對記憶體區域的的(例如CMemFile中) (隻需移動相關指針,指向新的一塊記憶體)
m_pFile-> GetBufferPtr(CFile::bufferCommit, m_lpBufCur - m_lpBufStart);
// get next buffer
VERIFY(m_pFile-> GetBufferPtr(CFile::bufferWrite, m_nBufSize,
(void**)&m_lpBufStart, (void**)&m_lpBufMax) == (UINT)m_nBufSize);
ASSERT((UINT)m_nBufSize == (UINT)(m_lpBufMax - m_lpBufStart));
m_lpBufCur = m_lpBufStart; //指向緩沖區首
緩沖區将提取空将調用GFileeBuffer,nBytesNeeded為目前剩餘部分上尚有用的位元組
void CArchive::FillBuffer(UINT nBytesNeeded)
ASSERT(IsLoading());
ASSERT(nBytesNeeded > 0);
ASSERT(nBytesNeeded <= (UINT)m_nBufSize);
AfxIsValidAddress(m_lpBufStart, m_lpBufMax - m_lpBufStart, FALSE));
AfxIsValidAddress(m_lpBufCur, m_lpBufMax - m_lpBufCur, FALSE));
UINT nUnused = m_lpBufMax - m_lpBufCur;
ULONG nTotalNeeded = ((ULONG)nBytesNeeded) + nUnused;
// 從檔案中讀取
if (!m_bDirectBuffer)
ASSERT(m_lpBufCur != NULL);
ASSERT(m_lpBufStart != NULL);
ASSERT(m_lpBufMax != NULL);
if (m_lpBufCur > m_lpBufStart)
//保留剩餘的尚未處理的部分,将它們移動到頭
if ((int)nUnused > 0)
{
memmove(m_lpBufStart, m_lpBufCur, nUnused);
m_lpBufCur = m_lpBufStart;
m_lpBufMax = m_lpBufStart + nUnused;
}
// read to satisfy nBytesNeeded or nLeft if possible
UINT nRead = nUnused;
UINT nLeft = m_nBufSize-nUnused;
UINT nBytes;
BYTE* lpTemp = m_lpBufStart + nUnused;
do
nBytes = m_pFile-> Read(lpTemp, nLeft);
lpTemp = lpTemp + nBytes;
nRead += nBytes;
nLeft -= nBytes;
while (nBytes > 0 && nLeft > 0 && nRead < nBytesNeeded);
m_lpBufCur = m_lpBufStart;
m_lpBufMax = m_lpBufStart + nRead;
// 如果是針對記憶體區域(CMemFile),移動相關指針,指向新的一塊記憶體
if (nUnused != 0)
m_pFile-> Seek(-(LONG)nUnused, CFile::current);
UINT nActual = m_pFile-> GetBufferPtr(CFile::bufferRead, m_nBufSize,
(void**)&m_lpBufStart, (void**)&m_lpBufMax);
ASSERT(nActual == (UINT)(m_lpBufMax - m_lpBufStart));
m_lpBufCur = m_lpBufStart;
// not enough data to fill request?
if ((ULONG)(m_lpBufMax - m_lpBufCur) < nTotalNeeded)
AfxThrowArchiveException(CArchiveException::endOfFile);
5.指定長度資料段落的讀寫
以下分析
UINT Read(void* lpBuf, UINT nMax); 讀取長度為nMax的資料
void Write(const void* lpBuf, UINT nMax); 寫入指定長度nMax的資料
對于大段資料的讀寫,先使用目前緩沖區中的内容或空間讀取或寫入,若這些空間夠用了,則結束。
否則,從剩餘的資料中找出最大的緩沖區整數倍大小的一塊資料,直接讀寫到存儲煤質(不反複使用緩沖區)。
剩餘的餘數部分,再使用緩沖區讀寫。
(說明:緩沖區讀寫的主要目的是将零散的資料以緩沖區大小為尺度來處理。對于大型資料,其中間的部分,不是零散的資料,使用緩沖區已經沒有意思,故直接讀寫)
①讀取
UINT CArchive::Read(void* lpBuf, UINT nMax)
if (nMax == 0)
return 0;
UINT nMaxTemp = nMax; //還需要讀入的長度,讀入一部分,就減相應數值,直到此數值變為零
//處理目前緩沖區中剩餘部分。
//如果要求讀入位元組小于緩沖區中剩餘部分,則第一部分為要求讀入的位元組數,
//否則讀入全部剩餘部分
UINT nTemp = min(nMaxTemp, (UINT)(m_lpBufMax - m_lpBufCur));
memcpy(lpBuf, m_lpBufCur, nTemp);
m_lpBufCur += nTemp;
lpBuf = (BYTE*)lpBuf + nTemp; //移動讀出内容所在區域的指針
nMaxTemp -= nTemp;
//目前緩沖區中剩餘部分不夠要求讀入的長度。
//還有位元組需要讀,則需要根據需要執行若幹次填充緩沖區,讀出,直到讀出指定位元組。
if (nMaxTemp != 0)
//計算出去除尾數部分的位元組大小(整數個緩沖區大小)
//對于這些部分,位元組從檔案對象中讀出,放到輸出緩沖區
nTemp = nMaxTemp - (nMaxTemp % m_nBufSize);
UINT nRead = 0;
UINT nLeft = nTemp;
UINT nBytes;
do
nBytes = m_pFile-> Read(lpBuf, nLeft); //要求讀入此整數緩沖區部分大小
lpBuf = (BYTE*)lpBuf + nBytes;
nRead += nBytes;
nLeft -= nBytes;
while ((nBytes > 0) && (nLeft > 0)); 知道讀入了預定大小,或到達檔案尾
nMaxTemp -= nRead;
if (nRead == nTemp) //讀入的位元組等于讀入的整數倍部分 該讀最後的餘數部分了
// 建立裝有此最後餘數部分的内容的CArchive的工作緩沖區。
if (!m_bDirectBuffer)
UINT nLeft = max(nMaxTemp, (UINT)m_nBufSize);
UINT nBytes;
BYTE* lpTemp = m_lpBufStart;
nRead = 0;
do
{
nBytes = m_pFile-> Read(lpTemp, nLeft); //從檔案中讀入到CArchive緩沖區
lpTemp = lpTemp + nBytes;
nRead += nBytes;
nLeft -= nBytes;
}
while ((nBytes > 0) && (nLeft > 0) && nRead < nMaxTemp);
m_lpBufMax = m_lpBufStart + nRead;
else
nRead = m_pFile-> GetBufferPtr(CFile::bufferRead, m_nBufSize,
(void**)&m_lpBufStart, (void**)&m_lpBufMax);
ASSERT(nRead == (UINT)(m_lpBufMax - m_lpBufStart));
//讀出此剩餘部分到輸出
nTemp = min(nMaxTemp, (UINT)(m_lpBufMax - m_lpBufCur));
memcpy(lpBuf, m_lpBufCur, nTemp);
m_lpBufCur += nTemp;
nMaxTemp -= nTemp;
return nMax - nMaxTemp;
②儲存,寫入
void CArchive::Write(const void* lpBuf, UINT nMax)
return;
//讀入可能的部分到緩沖區目前的剩餘部分
UINT nTemp = min(nMax, (UINT)(m_lpBufMax - m_lpBufCur));
memcpy(m_lpBufCur, lpBuf, nTemp);
lpBuf = (BYTE*)lpBuf + nTemp;
nMax -= nTemp;
if (nMax > 0) //還有未寫入的部分
Flush(); //将目前緩沖區寫入到存儲煤質
//計算出整數倍緩沖區大小的位元組數
nTemp = nMax - (nMax % m_nBufSize);
m_pFile-> Write(lpBuf, nTemp); //直接寫到檔案
lpBuf = (BYTE*)lpBuf + nTemp;
nMax -= nTemp;
//剩餘部分添加到緩沖區
if (m_bDirectBuffer)
// sync up direct mode buffer to new file position
// copy remaining to active buffer
ASSERT(nMax < (UINT)m_nBufSize);
ASSERT(m_lpBufCur == m_lpBufStart);
memcpy(m_lpBufCur, lpBuf, nMax);
m_lpBufCur += nMax;
6.字元串的讀寫
①CArchive提供的WriteString和ReadString
字元串寫
void CArchive::WriteString(LPCTSTR lpsz)
ASSERT(AfxIsValidString(lpsz));
Write(lpsz, lstrlen(lpsz) * sizeof(TCHAR)); //調用Write,将字元串對應的一段資料寫入
字元串讀(讀取一行字元串)
LPTSTR CArchive::ReadString(LPTSTR lpsz, UINT nMax)
// if nMax is negative (such a large number doesn't make sense given today's
// 2gb address space), then assume it to mean "keep the newline".
int nStop = (int)nMax < 0 ? -(int)nMax : (int)nMax;
ASSERT(AfxIsValidAddress(lpsz, (nStop+1) * sizeof(TCHAR)));
_TUCHAR ch;
int nRead = 0;
TRY
while (nRead < nStop)
*this >> ch; //讀出一個位元組
// stop and end-of-line (trailing '/n' is ignored) 遇換行—回車
if (ch == '/n' || ch == '/r')
if (ch == '/r')
*this >> ch;
// store the newline when called with negative nMax
if ((int)nMax != nStop)
lpsz[nRead++] = ch;
break;
lpsz[nRead++] = ch;
CATCH(CArchiveException, e)
if (e-> m_cause == CArchiveException::endOfFile)
DELETE_EXCEPTION(e);
if (nRead == 0)
return NULL;
THROW_LAST();
END_CATCH
lpsz[nRead] = '/0';
return lpsz;
ReadString到CString對象,可以多行字元
BOOL CArchive::ReadString(CString& rString)
rString = &afxChNil; // empty string without deallocating
const int nMaxSize = 128;
LPTSTR lpsz = rString.GetBuffer(nMaxSize);
LPTSTR lpszResult;
int nLen;
for (;;)
lpszResult = ReadString(lpsz, (UINT)-nMaxSize); // store the newline
rString.ReleaseBuffer();
// if string is read completely or EOF
if (lpszResult == NULL ||
(nLen = lstrlen(lpsz)) < nMaxSize ||
lpsz[nLen-1] == '/n')
break;
nLen = rString.GetLength();
lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;
// remove '/n' from end of string if present
lpsz = rString.GetBuffer(0);
nLen = rString.GetLength();
if (nLen != 0 && lpsz[nLen-1] == '/n')
rString.GetBufferSetLength(nLen-1);
return lpszResult != NULL;
②使用CString對象的"<<"與">>"符讀寫字元串
CString定義了輸入輸出符,可以象基本類型的資料一樣使用CArchive
的操作符定義
friend CArchive& AFXAPI operator<<(CArchive& ar, const CString& string);
friend CArchive& AFXAPI operator>>(CArchive& ar, CString& string);
// CString serialization code
// String format:
// UNICODE strings are always prefixed by 0xff, 0xfffe
// if < 0xff chars: len:BYTE, TCHAR chars
// if >= 0xff characters: 0xff, len:WORD, TCHAR chars
// if >= 0xfffe characters: 0xff, 0xffff, len:DWORD, TCHARs
CArchive& AFXAPI operator<<(CArchive& ar, const CString& string)
// special signature to recognize unicode strings
#ifdef _UNICODE
ar << (BYTE)0xff;
ar << (WORD)0xfffe;
#endif
if (string.GetData()-> nDataLength < 255)
ar << (BYTE)string.GetData()-> nDataLength;
else if (string.GetData()-> nDataLength < 0xfffe)
ar << (BYTE)0xff;
ar << (WORD)string.GetData()-> nDataLength;
ar << (WORD)0xffff;
ar << (DWORD)string.GetData()-> nDataLength;
ar.Write(string.m_pchData, string.GetData()-> nDataLength*sizeof(TCHAR));
return ar;
// return string length or -1 if UNICODE string is found in the archive
AFX_STATIC UINT AFXAPI _AfxReadStringLength(CArchive& ar)
DWORD nNewLen;
// attempt BYTE length first
BYTE bLen;
ar >> bLen;
if (bLen < 0xff)
return bLen;
// attempt WORD length
WORD wLen;
ar >> wLen;
if (wLen == 0xfffe)
// UNICODE string prefix (length will follow)
return (UINT)-1;
else if (wLen == 0xffff)
// read DWORD of length
ar >> nNewLen;
return (UINT)nNewLen;
return wLen;
CArchive& AFXAPI operator>>(CArchive& ar, CString& string)
int nConvert = 1; // if we get ANSI, convert
#else
int nConvert = 0; // if we get UNICODE, convert
UINT nNewLen = _AfxReadStringLength(ar);
if (nNewLen == (UINT)-1)
nConvert = 1 - nConvert;
nNewLen = _AfxReadStringLength(ar);
ASSERT(nNewLen != -1);
// set length of string to new length
UINT nByteLen = nNewLen;
string.GetBufferSetLength((int)nNewLen);
nByteLen += nByteLen * (1 - nConvert); // bytes to read
nByteLen += nByteLen * nConvert; // bytes to read
if (nNewLen == 0)
string.GetBufferSetLength(0);
string.GetBufferSetLength((int)nByteLen+nConvert);
// read in the characters
if (nNewLen != 0)
ASSERT(nByteLen != 0);
// read new data
if (ar.Read(string.m_pchData, nByteLen) != nByteLen)
AfxThrowArchiveException(CArchiveException::endOfFile);
// convert the data if as necessary
if (nConvert != 0)
CStringData* pOldData = string.GetData();
LPSTR lpsz = (LPSTR)string.m_pchData;
LPWSTR lpsz = (LPWSTR)string.m_pchData;
lpsz[nNewLen] = '/0'; // must be NUL terminated
string.Init(); // don't delete the old data
string = lpsz; // convert with operator=(LPWCSTR)
CString::FreeData(pOldData);
MFC中多數類都從CObject類派生,CObject類與CArchive類有着良好的合作關系,能實作将對象序列化儲存到檔案或其他媒介中去,或者讀取預先儲存的對象,動态建立對象等功能。
①CObject定義了針對CArvhive的輸入輸出操作符,可以向其他基本資料類型一樣使用"<<"、"<<"符号
CArchive& AFXAPI operator<<(CArchive& ar, const CObject* pOb)
{ ar.WriteObject(pOb); return ar; }
CArchive& AFXAPI operator>>(CArchive& ar, CObject*& pOb)
{ pOb = ar.ReadObject(NULL); return ar; }
當使用這些符号時,實際上執行的是CArchive的WriteObject和ReadObject成員
②WriteObject與ReadObject
在WriteObject與ReadObject中先寫入或讀取運作時類資訊(CRuntimeClas),再調用Serialze(..),按其中的代碼讀寫具體的對象資料。
是以,隻要在CObject派生類中重載Serilize()函數,寫入具體的讀寫過程,就可以使對象具有存儲與建立能力。
//将對象寫入到緩沖區
void CArchive::WriteObject(const CObject* pOb)
DWORD nObIndex;
// make sure m_pStoreMap is initialized
MapObject(NULL);
if (pOb == NULL)
// save out null tag to represent NULL pointer
*this << wNullTag;
else if ((nObIndex = (DWORD)(*m_pStoreMap)[(void*)pOb]) != 0)
// assumes initialized to 0 map
// save out index of already stored object
if (nObIndex < wBigObjectTag)
*this << (WORD)nObIndex;
*this << wBigObjectTag;
*this << nObIndex;
// write class of object first
CRuntimeClass* pClassRef = pOb-> GetRuntimeClass();
WriteClass(pClassRef); //寫入運作類資訊
// enter in stored object table, checking for overflow
CheckCount();
(*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;
// 調用CObject的Serialize成員,按其中的代碼寫入類中資料。
((CObject*)pOb)-> Serialize(*this);
CObject* CArchive::ReadObject(const CRuntimeClass* pClassRefRequested)
// attempt to load next stream as CRuntimeClass
UINT nSchema;
DWORD obTag;
//先讀入運作時類資訊
CRuntimeClass* pClassRef = ReadClass(pClassRefRequested, &nSchema, &obTag);
// check to see if tag to already loaded object
CObject* pOb;
if (pClassRef == NULL)
if (obTag > (DWORD)m_pLoadArray-> GetUpperBound())
// tag is too large for the number of objects read so far
AfxThrowArchiveException(CArchiveException::badIndex,
m_strFileName);
pOb = (CObject*)m_pLoadArray-> GetAt(obTag);
if (pOb != NULL && pClassRefRequested != NULL &&
!pOb-> IsKindOf(pClassRefRequested))
// loaded an object but of the wrong class
AfxThrowArchiveException(CArchiveException::badClass,
// 建立對象
pOb = pClassRef-> CreateObject();
if (pOb == NULL)
AfxThrowMemoryException();
// Add to mapping array BEFORE de-serializing
m_pLoadArray-> InsertAt(m_nMapCount++, pOb);
// Serialize the object with the schema number set in the archive
UINT nSchemaSave = m_nObjectSchema;
m_nObjectSchema = nSchema;
pOb-> Serialize(*this); //調用CObject的Serialize,按其中代碼讀入對象資料。
m_nObjectSchema = nSchemaSave;
ASSERT_VALID(pOb);
return pOb;
③運作時類資訊的讀寫
為了避免衆多重複的同類對象寫入重複的類資訊,CArchive中使用CMap對象儲存和檢索類資訊。