MFC 提供CArchive類實現數據的緩沖區讀寫,同時定義了類對象的存儲與讀取方案。 以下對CArchvie 的內部實現作分析。
一.概述 CArchive使用了緩沖區,即一段內存空間作為臨時數據存儲地,對CArchive的讀寫都先依次排列到此緩沖區,當緩沖區滿或用戶要求時,將此段整理後的數據讀寫到指定的存儲煤質。
當建立CArchive對象時,應指定其模式是用於緩沖區讀,還是用於緩沖區寫。
可以這樣理解,CArchive對象相當於鐵路的貨運練調度站,零散的貨物被收集,當總量到達火車運量的時候,由火車裝運走。
當接到火車的貨物時,則貨物由被分散到各自的貨主。與貨運不同的是,交貨、取貨是按時間循序執行的,而不是憑票據。因此必須保證送貨的和取貨的貨主按同樣的循序去存或取。
對於大型的貨物,則是拆散成火車單位,運走,取貨時,依次取各部分,組裝成原物。
二.內部數據
緩沖區指針 BYTE* m_lpBufStart,指向緩沖區,這個緩沖區有可能是底層CFile(如派生類CMemFile)對象提供的,但一般是CArchive自己建立的。
緩沖區尾部指針 BYTE* m_lpBufMax;
緩沖區當前位置指針 BYTE* m_lpBufCur;
初始化時,如果是讀模式,當前位置在尾部,如果是寫模式,當前位置在頭部:
m_lpBufCur = (IsLoading()) ? m_lpBufMax : m_lpBufStart;
三.基本數據讀寫
對於基本的數據類型,例如字節、雙字等,可以直接使用">>"、"<<"符號進行讀出、寫入。
//操作符定義捕:
//插入操作
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); //移動當前位置指針
if (!(m_nMode & bNoByteSwap))
_AfxByteSwap(dw, (BYTE*)&dw); //處理字節順序
return *this;
}
四.緩沖區的更新
以上操作中,當緩沖區將插入滿或緩沖區將提取空時,都將對緩沖區進行更新處理。
緩沖區將插入滿時調用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中) (只需移動相關指針,指向新的一塊內存)
if (m_lpBufCur != m_lpBufStart)
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; //指向緩沖區首
}
}
緩沖區將提取空,會調用FillBuffer。 nBytesNeeded為當前剩余部分上尚有用的字節
void CArchive::FillBuffer(UINT nBytesNeeded)
{
ASSERT_VALID(m_pFile);
ASSERT(IsLoading());
ASSERT(m_bDirectBuffer || m_lpBufStart != NULL);
ASSERT(m_bDirectBuffer || m_lpBufCur != NULL);
ASSERT(nBytesNeeded > 0);
ASSERT(nBytesNeeded <= (UINT)m_nBufSize);
ASSERT(m_lpBufStart == NULL ||
AfxIsValidAddress(m_lpBufStart, m_lpBufMax - m_lpBufStart, FALSE));
ASSERT(m_lpBufCur == NULL ||
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;
}
}
else
{
// 如果是針對內存區域(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);
}
五.指定長度數據段落的讀寫
以下分析
UINT Read(void* lpBuf, UINT nMax); 讀取長度為nMax的數據
void Write(const void* lpBuf, UINT nMax); 寫入指定長度nMax的數據
對於大段數據的讀寫,先使用當前緩沖區中的內容或空間讀取或寫入,若這些空間夠用了,則結束。
否則,從剩余的數據中找出最大的緩沖區整數倍大小的一塊數據,直接讀寫到存儲煤質(不反復使用緩沖區)。
剩余的余數部分,再使用緩沖區讀寫。
(說明:緩沖區讀寫的主要目的是將零散的數據以緩沖區大小為尺度來處理。對於大型數據,其中間的部分,不是零散的數據,使用緩沖區已經沒有意思,故直接讀寫)
①讀取
UINT CArchive::Read(void* lpBuf, UINT nMax)
{
ASSERT_VALID(m_pFile);
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_lpBufCur = m_lpBufStart;
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));
m_lpBufCur = 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)
{
if (nMax == 0)
return;
//讀入可能的部分到緩沖區當前的剩余部分
UINT nTemp = min(nMax, (UINT)(m_lpBufMax - m_lpBufCur));
memcpy(m_lpBufCur, lpBuf, nTemp);
m_lpBufCur += 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
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;
}
// copy remaining to active buffer
ASSERT(nMax < (UINT)m_nBufSize);
ASSERT(m_lpBufCur == m_lpBufStart);
memcpy(m_lpBufCur, lpBuf, nMax);
m_lpBufCur += nMax;
}
}
六.字符串的讀寫
①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; } else { 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;
}
else
{
ar << (BYTE)0xff;
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;
}
else
return wLen;
}
CArchive& AFXAPI operator>>(CArchive& ar, CString& string)
{
#ifdef _UNICODE
int nConvert = 1; // if we get ANSI, convert
#else
int nConvert = 0; // if we get UNICODE, convert
#endif
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;
#ifdef _UNICODE
string.GetBufferSetLength((int)nNewLen);
nByteLen += nByteLen * (1 - nConvert); // bytes to read
#else
nByteLen += nByteLen * nConvert; // bytes to read
if (nNewLen == 0)
string.GetBufferSetLength(0);
else
string.GetBufferSetLength((int)nByteLen+nConvert);
#endif
// 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)
{
#ifdef _UNICODE
CStringData* pOldData = string.GetData();
LPSTR lpsz = (LPSTR)string.m_pchData;
#else
CStringData* pOldData = string.GetData();
LPWSTR lpsz = (LPWSTR)string.m_pchData;
#endif
lpsz[nNewLen] = ''\0''; // must be NUL terminated
string.Init(); // don''t delete the old data
string = lpsz; // convert with operator=(LPWCSTR)
CString::FreeData(pOldData);
}
}
return ar;
}
七.CObject派生對象的讀寫
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;
else
{
*this << wBigObjectTag;
*this << nObIndex;
}
}
else
{
// 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,
m_strFileName);
}
}
else
{
// 建立對象
pOb = pClassRef-> CreateObject();
if (pOb == NULL)
AfxThrowMemoryException();
// Add to mapping array BEFORE de-serializing
CheckCount();
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對象儲存和檢索類信息。
void CArchive::WriteClass(const CRuntimeClass* pClassRef)
{
ASSERT(pClassRef != NULL);
ASSERT(IsStoring()); // proper direction
if (pClassRef-> m_wSchema == 0xFFFF)
{
TRACE1("Warning: Cannot call WriteClass/WriteObject for %hs.\n",
pClassRef-> m_lpszClassName);
AfxThrowNotSupportedException();
}
// make sure m_pStoreMap is initialized
MapObject(NULL);
// write out class id of pOb, with high bit set to indicate
// new object follows
// ASSUME: initialized to 0 map
DWORD nClassIndex;
if ((nClassIndex = (DWORD)(*m_pStoreMap)[(void*)pClassRef]) != 0)
{
// previously seen class, write out the index tagged by high bit
if (nClassIndex < wBigObjectTag)
*this << (WORD)(wClassTag | nClassIndex);
else
{
*this << wBigObjectTag;
*this << (dwBigClassTag | nClassIndex);
}
}
else
{
// store new class
*this << wNewClassTag;
pClassRef-> Store(*this);
// store new class reference in map, checking for overflow
CheckCount();
(*m_pStoreMap)[(void*)pClassRef] = (void*)m_nMapCount++;
}
}
CRuntimeClass* CArchive::ReadClass(const CRuntimeClass* pClassRefRequested,
UINT* pSchema, DWORD* pObTag)
{
ASSERT(pClassRefRequested == NULL ||
AfxIsValidAddress(pClassRefRequested, sizeof(CRuntimeClass), FALSE));
ASSERT(IsLoading()); // proper direction
if (pClassRefRequested != NULL && pClassRefRequested-> m_wSchema == 0xFFFF)
{
TRACE1("Warning: Cannot call ReadClass/ReadObject for %hs.\n",
pClassRefRequested-> m_lpszClassName);
AfxThrowNotSupportedException();
}
// make sure m_pLoadArray is initialized
MapObject(NULL);
// read object tag - if prefixed by wBigObjectTag then DWORD tag follows
DWORD obTag;
WORD wTag;
*this >> wTag;
if (wTag == wBigObjectTag)
*this >> obTag;
else
obTag = ((wTag & wClassTag) << 16) | (wTag & ~wClassTag);
// check for object tag (throw exception if expecting class tag)
if (!(obTag & dwBigClassTag))
{
if (pObTag == NULL)
AfxThrowArchiveException(CArchiveException::badIndex, m_strFileName);
*pObTag = obTag;
return NULL;
}
CRuntimeClass* pClassRef;
UINT nSchema;
if (wTag == wNewClassTag)
{
// new object follows a new class id
if ((pClassRef = CRuntimeClass::Load(*this, &nSchema)) == NULL)
AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);
// check nSchema against the expected schema
if ((pClassRef-> m_wSchema & ~VERSIONABLE_SCHEMA) != nSchema)
{
if (!(pClassRef-> m_wSchema & VERSIONABLE_SCHEMA))
{
// schema doesn''t match and not marked as VERSIONABLE_SCHEMA
AfxThrowArchiveException(CArchiveException::badSchema,
m_strFileName);
}
else
{
// they differ -- store the schema for later retrieval
if (m_pSchemaMap == NULL)
m_pSchemaMap = new CMapPtrToPtr;
ASSERT_VALID(m_pSchemaMap);
m_pSchemaMap-> SetAt(pClassRef, (void*)nSchema);
}
}
CheckCount();
m_pLoadArray-> InsertAt(m_nMapCount++, pClassRef);
}
else
{
// existing class index in obTag followed by new object
DWORD nClassIndex = (obTag & ~dwBigClassTag);
if (nClassIndex == 0 || nClassIndex > (DWORD)m_pLoadArray-> GetUpperBound())
AfxThrowArchiveException(CArchiveException::badIndex,
m_strFileName);
pClassRef = (CRuntimeClass*)m_pLoadArray-> GetAt(nClassIndex);
ASSERT(pClassRef != NULL);
// determine schema stored against objects of this type
void* pTemp;
BOOL bFound = FALSE;
nSchema = 0;
if (m_pSchemaMap != NULL)
{
bFound = m_pSchemaMap-> Lookup( pClassRef, pTemp );
if (bFound)
nSchema = (UINT)pTemp;
}
if (!bFound)
nSchema = pClassRef-> m_wSchema & ~VERSIONABLE_SCHEMA;
}
// check for correct derivation
if (pClassRefRequested != NULL &&
!pClassRef-> IsDerivedFrom(pClassRefRequested))
{
AfxThrowArchiveException(CArchiveException::badClass, m_strFileName);
}
// store nSchema for later examination
if (pSchema != NULL)
*pSchema = nSchema;
else
m_nObjectSchema = nSchema;
// store obTag for later examination
if (pObTag != NULL)
*pObTag = obTag;
// return the resulting CRuntimeClass*
return pClassRef;
}