Архивы блога

Программирование на языке MFC

Мой второй блог в серии программирования

Архив раздела «Приложения»

Что ж, в связи с важностью метода необходимо этот метод рассмотреть более подробно. Понятно, что в качестве аргумен­та методу передается указатель на записываемый в архив объ­ект. Сразу после проведения необходимых проверок вызывает­ся метод MapObject(). При этом (обратите, пожалуйста, на это внимание, читатель) методу MapObject() в качестве аргумента передается не указатель на объект, как следовало бы ожидать, a NULL. Исходный код метода MapObject() также находится в фай­ле arcobj.cpp:

void CArchive::MapObject(const CObject* pOb) {

if (IsStoring()) {

if (m_pStoreMap == NULL) {

// initialize the storage map

// (use CMapPtrToPtr because it is used for

// HANDLE maps too)

m_pStoreMap = new CMapPtrToPtr(m_nGrowSize) ; m_pStoreMap->InitHashTable(m_nHashSize); m_pStoreMap->SetAt(NULL, (void*) (DWORD)wNullTag) ; m_nMapCount = 1;

}

if (pOb != NULL) {

CheckCount(); (*m_pStoreMap)[(void*)pOb] = (void*)m_nMapCount++;

}

}

else {

if (m_pLoadArray == NULL) {

// initialize the loaded object pointer array // and set special values m_pLoadArray = new CPtrArray; m_pLoadArray->SetSize(1, m_nGrowSize); ASSERT(wNullTag == 0); m_pLoadArray->SetAt(wNullTag, NULL); m_nMapCount = 1;

}

if (pOb != NULL) {

CheckCount() ;

m_pLoadArray->InsertAt(m_nMapCount++, (void*)pOb);

}

}

}



Для того чтобы записать объект в архив, необходимо воспользо­ваться методом WriteObject().Ero исходный код находится в файле arcobj.cpp:

void CArchive::WriteObject(const CObject* pOb) {

// object can be NULL

ASSERT(IsStoringO); // proper direction DWORD nOblndex;

ASSERT(sizeof(nOblndex) == 4); ASSERT(sizeof(wNullTag) == 2); ASSERT(sizeof(wBigObjectTag) == 2); ASSERT(sizeof(wNewClassTag) == 2);

// make sure m_pStoreMap is initialized MapObject(NULL);

if (pOb == NULL) {

// save out null tag to represent NULL pointer *this « wNullTag;

}

else if ((nOblndex = (DWORD)(*m_pStoreMap)[(void*)pOb])

!= 0)

// assumes initialized to 0 map

{

// save out index of already stored object if (nOblndex < wBigObjectTag)

*this « (WORD)nOblndex; else {

*this « wBigObjectTag; *this « nOblndex;

}

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++;

// cause the object to serialize itself ((CObject*)pOb)->Serialize(*this); }



Как нетрудно догадаться, метод Write() действует так же, как и метод ReadQ. Как и в случае метода ReadQ, если число записываемых байтов равно нулю, то метод, не выполняя никаких действий, сразу же возвращает нулевое значение. Но если нам нужно на самом деле записать в архив определенное число байтов, то метод производит следующие действия. Сначала подготовленными для записи данными заполняется буфер архива. Если данные удалось нормально разместить в буфере архива, то работа метода на этом прекращается и метод возвращает число записанных в буфер архива байтов. Вероятно, что все данные, которые необходимо записать в архив, полностью размещены в буфере не будут. В этом случае метод сбрасывает буфер архива в файл, используя при этом метод FlushQ, а затем осуществляет запись в файл напрямую из буфера пользователя тех частей данных, которые потребовали бы перезаписи буфера архива. Как и в случае метода ReadQ, в буфере остается только последняя часть под­готовленных данных. Все данные, которые могли бы полностью заполнить буфер, записываются в файл напрямую. Однако в этом методе есть минимум один «подводный камень». Обратите внимание на то, что последняя часть данных, оставшаяся в буфере, в файл НЕ СБРАСЫВАЕТСЯ! Если сейчас закрыть файл, то часть данных, которая находится в буфере архива, в файл сброшена не будет! Для того чтобы предотвратить потерю данных необходимо после записи всех данных в архив осуществлять вызов метода FlushQ, который произведет сброс в файл остатка данных, находящегося в бу­фере архива.

Подводя итог сказанному, можно сделать вывод, что метод предназначен для буферированной записи в архив подготовлен­ных данных.

Что ж, уважаемый читатель, мы познакомились с некоторыми ме­тодами класса CArchive. Из того, что мы узнали про архив, можно пока сделать только один вывод – архив может использоваться для буферированного ввода и вывода данных в файл. Кроме этого, возможно, удобно бужет использовать перегруженные операторы и «»» ««» для работы сданными простых типов. Однако, как мне кажется, вы испытываете легкое разочарование. Подумаешь, стоило ли огород городить ради того, чтобы осуществлять буферированный ввод/вывод? Но не стоит торопить события. То, о чем мы говорили, является всего лишь прелюдией к тому, ради чего и создан класс CArchive. А создан он для того, чтобы можно было записывать в файл и читать из файла не только данные, но и объекты! А вот это уже интересно,верно?



03.02.2010

Естественно, раз существует метод Read(), то, наверное, дол­жен существовать и метод Write(). Действительно, такой метод су­ществует, его исходный код находится в файле агссоге.срр:

void CArchive::Write(const void* lpBuf, UINT nMax) {

ASSERT_VALID(m_pFile);

if (nMax == 0) return;

ASSERT(lpBuf !=NULL);

ASSERT(AfxIsValidAddress(lpBuf, nMax, FALSE));

// read-only access needed

ASSERT(m_bDirectBuffer || m_lpBufStart !=NULL); ASSERT(mjDDirectBuffer || m_lpBufCur !=NULL); ASSERT(m_lpBufStart == NULL ||

AfxIsValidAddress(m_lpBufStart,

m_lpBufMax – m_lpBufStart) ) ; ASSERT(m_lpBufCur == NULL ||

AfxIsValidAddress(m_lpBufCur,

m_lpBufMax – m_lpBufCur) ) ;

ASSERT(IsStoring());

// copy to buffer if possible 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(); // flush the full buffer

// write rest of buffer size chunks

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_lpBufGur == m_lpBufStart); memcpy(m_lpBufCur, lpBuf, nMax); m_lpBufCur += nMax;

}

}

Назначение аргументов, передаваемых методу, предельно ясно. Первый аргумент – это буфер с данными, подготовленными для записи. Второй аргумент – размер этих данных, т. е. число байтов в буфере, которые необходимо записать в архив.



03.02.2010

В качестве первого аргумента передается указатель на буфер, в который будет производиться считывание данных. Второй аргу­мент – это число байтов, которые необходимо считать из файла, с которым архив ассоциирован. Посмотрим теперь, что происхо­дит во время работы метода.

Само собой разумеется, что если число байтов, которые мы хотим считать, равно нулю, то никаких действий не производит­ся и метод немедленно возвращает нуль. Однако, если мы все же хотим считать определенное количество байтов, то нам нуж­но вспомнить о том, что с нашим архивом ассоциирован буфер в оперативной памяти. Вполне вероятно, что до обращения к ме­тоду Read() мы уже осуществляли чтение в буфер, правильно? Значит, все данные, которые мы хотим считать, или хотя бы часть этих данных могут находиться в буфере, не так ли? Можно про­сто взять эту часть данных из буфера, а не обращаться к диску, верно? Метод копирует тот участок буфера архива, который на­ходится между указателями mJpBufCur и mJpBufMax, в начало буфера, указатель на который передан методу в качестве пер­вого аргумента. Корректировка указателей при этом пока не про­изводится.

Значит, читать осталось только ту часть, которую мы не считы­вали ранее, верно? Соответственно этому, метод корректирует счет­чик байтов, подлежащих считыванию. Кроме этого, мы уже, воз­можно, при копировании части буфера архива уже заполнили дан­ными часть переданного методу буфера, верно? Значит, необходи­мо сместить и указатель на то место, куда мы будем записывать данные. Метод осуществляет и эту коррректировку.

Если те данные, которые мы собирались читать из архива, пол­ностью находились в буфере, то на этом работа метода заверша­ется. Метод возвращает число взятых из буфера байтов (в данном случае безразлично, откуда берутся данные, верно?).

Но если необходимо читать данные из файла… Если бы ме­тод читал данные из файла в буфер архива, а затем бы копиро­вал их в буфер пользователя, то при этом возникли бы непро­изводительные затраты, так как при многократном считывании пришлось бы каждый раз затирать данные буфера архива. Раз­работчики MFC поступили в данном случае более интересно, в буфере архива остается только ПОСЛЕДНЯЯ часть данных, вся остальная информация копируется из-файла в буфер поль­зователя напрямую. Обратите внимание, как вычисляется размер этой последней части – просто берется остаток от деле­ния числа байтов, подлежащих считыванию из файла за выче­том находившихся в буфере архива, на размер буфера архива. При этом производятся все действия по заполнению буфера, которые производилсь и в методе FillBufferQ. После того как чтение выполнено полностью, метод возвращает общее число считанных из буфера и из файла байтов.

После всего того, что мы узнали, можно сделать вывод о том, что метод осуществляет буферированное чтение из архива.



03.02.2010

Но, несмотря на то, что текст метода дочтаточно велик, понять принцип его работы гораздо проще. Если мы осуществляем чте­ние из архива, устанавливаем указатель файла на конец действи­тельно считанных, а не буферированных данных, после чего ука­затель на текущую позицию приравниваем указателю на считан­ные данные, т. е. определяем, что буфер становится пустым. Если мы осуществляем запись в архив, то все, что находится в проме­жутке от начала до текущей позиции буфера, записывается в файл. После этого указатель на текущую позицию буфера устанавлива­ется на начало буфера. Все! Буфер пуст! © Другими словами, ме­тод осуществляет запись в файл содержимого буфера, после чего текущей позицией в буфере становится начало буфера (бу­фер «сбрасывается»).

Для того чтобы осуществить чтение из архива определенного числа байтов, можно вопользоваться методом Read(), чей исход­ный код находится в файле агссоге.срр:

UINT CArchive::Read(void* lpBuf, UINT nMax) {

ASSERT_VALID(m_pFile); if (nMax == 0) return 0;

ASSERT(lpBuf !=NULL);

ASSERT(AfxIsValidAddress(lpBuf, nMax)); ASSERT(m_bDirectBuffer || m_lpBufStart !=NULL); ASSERT(m_bDirectBuffer || m_lpBufCur !=NULL); ASSERT(m_lpBufStart == NULL ||

AfxIsValidAddress(m_lpBufStart,

m_lpBufMax – m_lpBufStart, FALSE)); ASSERT(m_lpBufCur == NULL ||

AfxIsValidAddress(m_lpBufCur,

m_lpBufMax – m_lpBufCur, FALSE));

ASSERT(IsLoading());

// try to fill from buffer first 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) {

ASSERT(m_lpBufCur == m_lpBufMax);

// read rest in buffer size chunks

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;

// read last chunk into buffer then copy if (nRead == nTemp) {

ASSERT(m_lpBufCur — m_lpBufMax); ASSERT(nMaxTemp < (UINT)m_nBufSize);

// fill buffer (similar to CArchive::FillBuffer,
// but no exception)
«-

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); 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;

// use first part for rest of read nTemp = min(nMaxTemp,

(UINT)(m_lpBufMax – m_lpBufCur)); memcpy(lpBuf, m_lpBufCur, nTemp); m_lpBufCur += nTemp; nMaxTemp -= nTemp;

}

}

return nMax – nMaxTemp;

}



Теперь мы знаем, как осуществляется чтение из архива про­стых типов данных. Надеюсь, для того чтобы разобрать перегру­женный оператор ««», нам потребуется значительно меньше вре­мени. Код оператора также существует в двух вариантах, опять мы остановимся на более простом, который находится в файле afx.h:

Схема взаимоотношения буфера и его полей

_AFX_INLINE CArchive& CArchive::operator«(WORD w) {

if (m_lpBufCur + sizeof(WORD) > m_lpBufMax) Flush();

*(UNALIGNED WORD*)m_lpBufCur = w; m_lpBufCur += sizeof(WORD); 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 = mjlpBufMax; // empty

}

else {

if (!m_bDirectBuffer) {

// write out the current buffer to file if (m_lpBufCur != m_lpBufStart) m_pFile->Write(m_lpBufStart, m_lpBufCur – m_lpBufStart);

}

else {

// commit current buffer

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(), которому в качестве аргу­мента передается количество байтов, недостаточных для нормаль­ного выполнения операции записи. Исходный код метода FillBuffer() можно найти в файле агссоге.срр. Я прошу читателя запастись терпе­нием, потому что код метода достаточно объемен:

void CArchive::FillBuffer(UINT nBytesNeeded) {

ASSERT_VALID(m_pFile); ASSERT (IsLoadingO ) ;

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;

// fill up the current buffer from file if (!m_bDirectBuffer) {

ASSERT(m_lpBufCur != NULL); ASSERT(m_lpBufStart != NULL); ASSERT(m_lpBufMax !=NULL);

if (m_lpBufCur > m_lpBufStart) {

// copy unused

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 {

// seek to unused portion and get the buffer

I/ starting there 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);

}

После проверки и, при необходимости, выдачи всех предупреж­дений метод определяет прежде всего число неиспользуемых бай­тов в буфере. Обратите внимание на то, что это значение вычис­ляется как разность между значениями полей mJpBufMax и mJpBufCur, а не (mJpBufStart + m_nBufSize – mJpBufCur). Почему? Пока непонятно… Затем определяется, сколько всего бай­тов необходимо для того, чтобы операция чтения могла завер­шиться нормально. Естественно, число необходимых байтов равно сумме числа неиспользуемых байтов в буфере и числа недоста­ющих байтов, переданных методу в качестве аргумента. Далее про­исходит следующее. Если у нас буфер занят частично, т. е. число неиспользуемых байтов не равно нулю, то мы копируем свободный участок памяти в начало буфера. При этом указатель на текущую позицию в буфере устанавливается на начало буфера. Это понятно. Но непонятно следующее – почему указатель m JpBufMax устанав­ливается на конец переписанного участка? Подождите, пожалуйста, осталось совсем недолго…

Идем дальше. И вот здесь… Посмотрите: значение переменной nRead делается равным числу неиспользованных байтов! Опять за­гадка. .. Определяем, сколько байтов еще необходимо считать для того, чтобы заполнить буфер до конца, читаем из файла в буфер… Полю mJpBufCur присваиваивается значение указателя на начало буфера… Вот оно! В поле mJpBufMax мы записываем число считан­ных байтов! Т. е. поле mJpBufMax указывает на конец тех данных, которые мы считали! Обычно поле mJpBufMax будет указывать на считывании достигли конца файла! В этом случае поле mJpBufMax будет указывать куда-то на середину буфера, на последний байт считанных данных! Т. е. поле mJpBufMax указывает на конец считанных данных!

Понятно теперь, почему неиспользованным участком считается участок от указателя на текущую позицию в буфере до указателя mJpBufMax. Конечно же! Ведь после предыдущих операций чтения мы могли выполнять операции записи, т. е. указатель на текущую позицию в файле мог сместиться в сторону уменьшения. Но ведь в этом участке находятся данные, которые мы уже считывали! Зачем нам осуществлять считывание одних и тех же данных повторно? Естественно, нам легче их скопировать из одного места буфера в другое, чем осуществлять лишние обращения к диску!

Схематично взаимоотношения буфера и его полей показаны на 9.

Подводя итог сказанному выше, можно сделать вывод о том, что метод FHIBufferQ осуществляет заполнение выделенного ар­хиву буфера данными из файла, осуществляя буферированное чтение данных.

Обратите внимание, что указатель m JpBufCur после осуществ­ления считывания данных указывает на начало буфера. А теперь, если мы вернемся в код перегруженного оператора «»», мы уви­дим, что сразу после вызова FillBuffer() оператор записывает по указанной ему ссылке считанное слово и увеличивает указатель mJpBufCur на величину, равную размеру считанного данного. Ука­затель mJpBufCur занимает то положение, которое и должен за­нимать!



03.02.2010

В классе CArchive операторы ««» и «»» для каждого типа опре­делены дважды. Применяются они в зависимости от того, осуществ­лено ли определение константы препроцессора _AFXJ3YTESWAP. Если такое определение осуществлено, то перед записью в архив порядок следования байтов в словах будет изменяться. Но помимо директивы препроцессора для того, чтобы изменялся порядок следова­ния байтов, мы должны указать конструктору, что режим работы архива должен предусматривать изменение порядка следования байтов. Для простоты мы будем рассматривать случай, когда порядок следования байтов изменяться не должен.

Исходный код перегруженной операции ««» для переменной типа WORD, как, впрочем, и для переменных других типов, нахо­дится в файле afx.ini (коды для случая необходимости изменения порядка следования байтов находятся в файле afxcore.cpp):

_AFX_INLINE CArchive& CArchive:: operator» (WORD& w) { if (m_lpBufCur + sizeof(WORD) > m_lpBufMax)

FillBuffer(sizeof(WORD) – (UINT)(m_lpBufMax -

m_lpBufCur));

w = * (UNALIGNED WORD*)m_lpBufCur; m_lpBufCur += sizeof(WORD); return *this;

}



В данном случае аргументами конструктора являются указа­тель на буфер в памяти, размер буфера и значение прираще­ния. При открытии указатель файла устанавливается в начало файла. Обратите внимание на то, каким образом устанавлива­ется размер файла. Если при вызове конструктора мы указали, что nGrowBytes равно нулю, то размер файла будет соответст­вовать размеру буфера в памяти. Стоит указать ненулевое при­ращение – нулевым будет размер файла. Кстати, по умолчанию размер приращения при использовании этого конструктора ра­вен нулю. Возможно, это связано с механизмом выделения до­полнительной памяти буферу? Что ж, немного позже мы поймем, соответствует ли наша догадка действительности.

Пример

Естественно, поле mJpBuffer делается равным указанному раз­меру буфера. Таким образом, мы практически полностью опреде­лили то, что нам нужно для управления данными в памяти – указа­тель на буфер, размер буфера и размер приращения.



У класса CMemFile есть два конструктора. Первый из них в некото­ром смысле можно назвать конструктором «по умолчанию». Ис­ходный код его находится в файле filemem.cpp:

CMemFile::CMemFile(UINT nGrowBytes) {

ASSERT(nGrowBytes <= UINT_MAX); m_nGrowBytes = nGrowBytes; m_nPosition = 0; m_nBufferSize = 0; m_nFileSize = 0; m_lpBuffer = NULL; m_bAutoDeiete = TRUE;

}

Очевидно, что при вызове конструктора не происходит ника­ких действий, за исключением инициализации полей объекта. Итак, поле m_nGrowBytes содержит приращение (количество байтов), на которое будет увеличиваться буфер в памяти в тех случаях, если размер буфера окажется недостаточным для хра­нения записываемых в него данных. По умолчанию значение m_nGrowBytes равно 1024. Поле m_nPosition представляет собой указатель текущей позиции в файле. Поле mJpBuffer, указатель на ассоциированный с объектом буфер в памяти, делается рав­ным NULL, что фактически означает, что у объекта нет ассоцииро­ванного с ним буфера. В таком случае естественным выглядит и то, что значение длины файла, т. е. поля m_nFileSize, тоже делается равным нулю. Этот конструктор может быть использован в тех случаях, когда в момент вызова конструкторабуфер в памяти еще не выделен или же конструктор не может получить адрес этого буфера.

Исходный код второго конструктора тоже находится в файле filemem.cpp:

CMemFile::CMemFile(BYTE* lpBuffer,

UINT nBufferSize, UINT nGrowBytes)

{

ASSERT(nGrowBytes <= UINT_MAX);

m_nGrowBytes = nGrowBytes;

m_nPosition = 0;

m_nBufferSize = nBufferSize;

m_nFileSize = nGrowBytes == 0 ? nBufferSize : 0; m_lpBuffer = lpBuffer; m_bAutoDelete = FALSE;

}



03.02.2010

В библиотеке классов MFC реализован еще один тип файлов, так называемый файл в памяти. Как следует из названия файла, файл осуществляет хранение данных не на устройстве, а в оперативной памяти. Как станет впоследствии понятно из исходных текстов ме­тодов, файл в памяти представляет собой просто буфер в памяти, к которому можно обращаться точно таким же образом, как к фай­лу на диске.

Устройство файла в памяти упрощенно можно представить сле­дующим образом. В памяти физически выделяется буфер. Внутри этого буфера определяется некоторая зона, назовем ее зоной фай­ла, в которую можно производить запись данных и из которой эти данные можно читать. При необходимости зона, а соответственно, и буфер, динамически увеличивают свои размеры, но при этом все­гда размер буфера больше или, по крайней мере, равен размеру зоны файла.

Схематично устройство файла в памяти представлено на ри­сунке выше. На 8 весь прямоугольник представляет собой бу­фер в памяти. Зона файла на рисунке затенена. Заштрихованная часть зоны файла представляет собой расстояние от начала фай­ла или, что то же самое, от начала файла, до текущей позиции файла.

При работе с файлом в памяти программист не задумывается о буфере и зоне, для работы с файлом в памяти используются привычные методы Read(), Write(), Seek() и так далее. С точки зре­ния программиста единственным отличием файла в памяти от файла на диске является то, что файл в памяти не имеет имени (в данном случае «именем» файла является указатель на буфер ©). Естественно, что файл в памяти существует до тех пор, пока программа может управлять памятью. При потере указателя на блок памяти, разрушении по какой-то причине памяти или, тем более, при выходе из операционной системы или выключении компьютеpa файл в памяти прекращает свое существование. В файле afx.h этот класс описан следующим образом:

class CMemFile : public CFile {

DECLARE_DYNAMIC (CMemFile) public:

// Constructors

CMemFile(UINT nGrowBytes = 1024); CMemFile(BYTE* lpBuffer,

UINT nBufferSize,

UINT nGrowBytes = 0) ;

// Operations

void Attach(BYTE* lpBuffer,

UINT nBufferSize,

UINT nGrowBytes = 0); BYTE* Detach();

// Advanced Overridables protected:

virtual BYTE* Alloc(DWORD nBytes);

virtual BYTE* Realloc(BYTE* lpMem, DWORD nBytes);

virtual BYTE* Memcpy(BYTE* lpMemTarget,

const BYTE* lpMemSource, UINT nBytes); virtual void Free(BYTE* lpMem); virtual void GrowFile(DWORD dwNewLen);

// Implementation protected:

UINT m_nGrowBytes;

DWORD m_nPosition;

DWORD m_nBufferSize;

DWORD m_nFileSize;

BYTE* m_lpBuffer;

BOOL m_bAutoDelete;

public:

virtual -CMemFile() ; #ifdef _DEBUG

virtual void Dump(CDumpContext& dc) const;

virtual void AssertValid() const;

#endif

virtual DWORD GetPosition() const;

BOOL GetStatus(CFileStatus& rStatus) const;

virtual LONG Seek(LONG lOff, UINT nFrom);

virtual void SetLength(DWORD dwNewLen);

virtual UINT Read(void* lpBuf, UINT nCount);

virtual void Write(const void* lpBuf, UINT nCount);

virtual void Abort ();

virtual void Flush ();

virtual void Close ();

virtual UINT GetBufferPtr(UINT nCommand,

UINT nCount = 0,

void** ppBufStart = NULL,

void** ppBufMax = NULL);

// Unsupported APIs

virtual CFile* Duplicate() const;

virtual void LockRange(DWORD dwPos, DWORD dwCount); virtual void UnlockRange(DWORD dwPos, DWORD dwCount);

};



Исходный код деструктора класса находится в файле filetxt.cpp:

CStdioFile::-CStdioFile() {

ASSERT_VAL-ID (this) ;

if (m_pStream != NULL && m_bCloseOnDelete) Close ();

}

Как нетрудно заметить, если у объекта есть ассоциированный с ним поток и установлени признак закрытия файла при уничтоже­нии объекта, то файл, а соответственно и поток, закрывается.



В отличие от первой версии, функции в качестве аргумента передаются указатель на строку (не на объект класса CString), в ко­торую будут считаны данные, и максимально возможная длина строки. Метод ЕДИНСТВЕННЫЙ РАЗ вызывает функцию _fgetts(). В том случае, если строка была считана нормально, метод возвра­щает указатель на считанную строку. В противном случае метод формирует исключение. Нужно заметить, что максимальный размер строки, которая может быть считана, ограничивается вторым аргументом метода.

Произвести запись строки в поток можно при помощи метода WriteString(). Его исходный код находится в файле filetxt.cpp:

Пример

void CStdioFile::WriteString(LPCTSTR lpsz) {

ASSERT(lpsz != NULL); ASSERT(m_pStream != NULL);

if (_fputts(lpsz, m_pStream) == _TEOF)

AfxThrowFileException(CFileException::diskFull,

_doserrno, m_strFileName);

}

В качестве аргумента функции передается указатель на запи­сываемую строку. Для записи строки в поток метод вызывает функ­цию _fgetts(). Если во время записи происходит ошибка, формиру­ется исключение.



Отсюда видно, что размер буфера фактически на единицу пре­вышает указанную максимальную длину буфера. Последний эле­мент при выделении буфера получает нулевое значение, т. е. фак­тически является терминатором строки.

Далее в методе осуществляется запуск цикла, который прерыва­ется только в случае считывания полной строки или достижения кон­ца файла. Внутри цикла осуществляется считывание строки из пото­ка, ассоциированного с объектом, при помощи функции _fgetts(). При этом метод фактически использует динамический буфер для считы­вания строки. Первоначальный размер буфера, выделяемого для стро­ки, равен 128 символам. В том случае, если функцией _fgetts() были считаны 128 символов, но при этом не был достигнут конец строки, размер выделенного буфера увеличивается еще на 128 байтов (точ­нее, свыделяется новый буфер большего размера, содержимое ста­рого буфера копируется в новый буфер, после чего старый буфер освобождается), и так до тех пор, пока строка не будет считана пол­ностью или не будет достигнут конец файла. Если считывание строки прошло нормально, то метод возвращает значение TRUE. В том слу­чае, если при чтении информации из потока произошла ошибка, то формируется исключение. Подводя итог сказанному выше, можно сказать, что метод осуществляет считывание строки в перемен­ную типа CString. При этом размер считываемой строки практи­чески не ограничен.

Существует также более простая версия метода ReadString(). Ее исходный код также находится в файле filetxt.cpp:

LPTSTR CStdioFile::ReadString(LPTSTR lpsz, UINT nMax) {

ASSERT(lpsz != NULL);

ASSERT(AfxIsValidAddress(lpsz, nMax)); ASSERT(m_pStream != NULL);

LPTSTR IpszResult = _fgetts(lpsz, nMax, m_pStream);

if (IpszResult == NULL && !feof(m_pStream))

{

clearerr(m_pStream);

AfxThrowFileException(CFileException::generic,

_doserrno, m_strFileName);

return IpszResult;

}



Аргументом метода является ссылка на переменную типа CString. Что же происходит при чтении строки из потока? Можно заметить, что переменную, переданную методу в качестве аргу­мента, перед вызовом метода обнулять вовсе не обязательно, потому что метод сразу же присваивав этой строке значение afxChNil, которое описано в файле strcore.cpp следующим образом:

AFX_DATADEF TCHAR afxChNil = Л\0′;

Другими словами, метод производит обнуление переданной ему строки самостоятельно. Затем происходит выделение буфера для считывания строки. При этом параметр nMaxSize определяет не количество символов в буфере, а максимальную размерность сим­вольного массива, в который фактически будет записана строка. При выделении буфера указатель на этот буфер формируется сле­дующим образом: pData = (CStringData*) new BYTE[sizeof(CStringData) +

(nLen+1)*sizeof(TCHAR)];



Методы Read() и Write() с функциональной точки зрения не отлича­ются от соответствующих методов родительского класса. С точки зрения реализации от методов родительского класса они отлича­ются тем, что для записи информации используется функция _fputts(), а не ReadFile(), а для чтения данных используется функ­ция _fgetts(), а не WriteFile(). Однако у класса CStdioFile есть два дополнительных метода, позволяющих осуществлять запись в файл не просто определенного количества символов, а строки, завер­шающейся нулем. Методом, осуществляющим чтение строки из файла, является ReadString(), текст которого находится в файле fileext.cpp:

BOOL CStdioFile::ReadString(CString& rString) {

ASSERT_VALID(this);

rString = SafxChNil; // empty string without

// deallocating const int nMaxSize = 128;

LPTSTR lpsz = rString.GetBuffer(nMaxSize);

LPTSTR IpszResult;

int nLen = 0;

for (;;)

{

IpszResult = _fgetts(lpsz, nMaxSize+1, m_pStream); rString.ReleaseBuffer() ;

// handle error/eof case

if (IpszResult == NULL && !feof(m_pStream)) {

clearerr(m_pStream);

AfxThrowFileException(CFileException::generic,

_doserrno, m_strFileName);

}

»

// if string is read completely or EOF if (IpszResult == NULL ||

(nLen = lstrlen(lpsz)) < nMaxSize ||

lpsz[nLen-l] == л\п‘)

break;

nLen = rString.GetLength() ;

lpsz = rString.GetBuffer(nMaxSize + nLen) + nLen;

}

// remove л\п‘ from end of string if present lpsz = rString.GetBuffer(0); nLen = rString.GetLength(); if (nLen != 0 && lpsz[nLen-l] == л\п‘) rString.GetBufferSetLength(nLen-1);

return IpszResult != NULL;

}



Методы, используемые для получения информации о текущем положении указателя файла (GetPosition()) и установки указателя файла (Seek(), SeetToBegin(), SeekToEnd()) в объектах класса CStdioFile, практически ничем не отличаются от соответствующих методов родительского класса. Разница состоит лишь в том, что в случае класса CStdioFile для установки указателей файла исполь­зуются функции fseek() и ftell(), а не функция SetFilePointer(), кото­рая применяется в родительском классе.



03.02.2010

Метод закрывает ассоциированный с объектом поток, что автоматически приводит и к закрытию файла, «очищает» зна­чения полей m_hFile и тjpStream. В случае возникновении ошиб­ки при закрытии потока формируется исключение.

В том случае, если для программиста не очень важно, закрылся поток нормально или с ошибкой, программист может воспользо­ваться методом Abort(), текст которого приведен ниже:

void CStdioFile::Abort() {

ASSERT_VALID(this);

if (m_pStream != NULL && m_bCloseOnDelete)

fclose(m_pStream); // close but ignore errors m_hFile = (UINT) hFileNull; m_pStream = NULL; m_bCloseOnDelete = FALSE;

Как и метод Close(), метод Abort() закрывает поток, обнуляет поля m_hFile и m_pStream, но в случае возникновения ошибки при за­крытии потока никаких исключений не формируется.



03.02.2010

Текст метода можно разделить на несколько частей. В первой части производится открытие файла при помощи вызова метода Ореп() родительского класса. Во второй части метода из передан­ных методу аргументов формируются параметры для открытия потока. Третья часть производит открытие потока, при этом полю m_p_Stream присваивается указатель на открытый поток, т. е. объ­ект ассоциируется с потоком. И наконец, в том случае, если поток по каким-то причинам открыть невозможно, формируется исклю­чение. Резюмируя сказанное выше, делаем вывод: метод откры­вает файл, открывает поток и ассоциирует файл и поток с объ­ектом.

Для закрытия файла и потока используется метод Close(), ис­ходный текст которого находится в файле filetxt.cpp:

void CStdioFile::Close() {

ASSERT_VALID(this); ASSERT(m_pStream != NULL);

int nErr = 0;

if (m_pStream != NULL)

nErr = fclose(m_pStream);

m_hFile = (UINT) hFileNull; m_bCloseOnDelete = FALSE; m_pStream = NULL;

if (nErr != 0)

AfxThrowFileException(CFileException::diskFull,

_doserrno, m_strFileName);

}



Поток, который будет ассоциирован с объектом, может быть от­крыт при помощи вызова метода Ореп(), текст которого находится в файле filetxt.cpp:

BOOL CStdioFile::Open(LPCTSTR IpszFileName,

UINT nOpenFlags, CFileException* pException)

{

ASSERT(pException == NULL ||

AfxIsValidAddress(pException,

sizeof(CFileException))); ASSERT(IpszFileName !=NULL); ASSERT(AfxlsValidString(IpszFileNameП;

m_pStream = NULL;

if (! CFile :-.Open (IpszFileName, (nOpenFlags & -typeText) , pException))

return FALSE;

ASSERT(m_hFile != hFileNull); ASSERT(m_bCloseOnDelete);

char szMode[4]; // C-runtime open string int nMode = 0;

// determine read/write mode depending on CFile mode

if (nOpenFlags & modeCreate) {

if (nOpenFlags & modeNoTruncate)

szMode[nMode++] = ла‘; else

szMode[nMode++] = yw’;

}

else if (nOpenFlags & modeWrite)

szMode[nMode++] = ла‘; else

szMode[nMode++] = лг‘; // add л+’ if necessary (when read/write modes mismatched)

if (szMode[0] == yr’ && (nOpenFlags & modeReadWrite) I I szMode[0] != лг‘ && !(nOpenFlags & modeWrite))

{

// current szMode mismatched, need to add y+’ to fix szMode[nMode++] = л+’;

}

// will be inverted if not necessary int nFlags = _0_RDONLY|_0_TEXT; if (nOpenFlags & (modeWriteImodeReadWrite)) nFlags A= _0_RDONLY;

if (nOpenFlags & typeBinary)

szMode[nMode++] = ЛЬ‘, nFlags л= _0_TEXT; else

szMode[nMode++] = yt’; szMode[nMode++] = Л0′;

// open a C-runtime low-level file handle

int nHandle = _open_osfhandle(m_hFile, nFlags);

// open a C-runtime stream from that handle if (nHandle != -1)

m_pStream = _fdopen(nHandle, szMode);

if (m_pStream == NULL) {

// an error somewhere along the way…

if (pException != NULL)

{

pException->m_10sError = _doserrno; pException->m_cause =

CFileException::OsErrorToException(_doserrno);

}

CFile::Abort(); // close m_hFile return FALSE;

}

return TRUE;

}



И наконец, третий конструктор. Его аргументами, как и в случае родительского класса, являются указатель на строку, содержащую имя открываемого файла, и флаги открытия файла. Текст этого конструктора можно найти в файле filetxt.cpp:

CStdioFile::CStdioFile(LPCTSTR IpszFileName, UINT nOpenFlags) {

ASSERT(IpszFileName != NULL);

ASSERT(AfxIsValidString(IpszFileName));

CFileException e;

if (!Open(IpszFileName, nOpenFlags, &e)) AfxThrowFileException(e.m_cause,

e.m_10sError,

e.m_strFileName);

>

Вполне очевидно, что конструктор создает объект и открывает файл при помощи вызова метода Ореп(). В случае возникновения ошибки формируется исключение.



Ниже приведен текст второго конструктора, аргументом которого является указатель на ранее созданный поток:

CStdioFile::CStdioFile(FILE* pOpenStream) : CFile(hFileNull) {

m_pStream = pOpenStream;

m_hFile = (UINT)_get_osfhandle(_fileno(pOpenStream)); ASSERT(!m_bCloseOnDelete);

}

При ближайшем рассмотрении текста конструктора становится очевидно, что конструктор создает объект, причем указатель на поток присваивается полю т__pStream, а полю mJhFile унасле-дованнму из родительского класса, присваивается значение хэнд­ла файла, соответствующего потоку. Другими словами, конст­руктор ассоциирует уже созданный поток и соответствующий пото­ку файл с объектом. Конструктор целесообразно использовать в тех случаях, когда к моменту создания объекта поток уже создан.



Как и у родительского класса, у класса CStdioFile также есть три конструктора. Первый конструктор – по умолчанию. Его исходный код, который находится в файле filetxt.cpp, приведен ниже:

CStdioFile::CStdioFile() {

m_pStream = NULL;

}

Из текста следует, что конструктор создает объект и присваи­вает полю т_pStream значение NULL, указывая тем самым, что у объекта нет ассоциированного с ним потока. Как и в случае родительского класса, этот конструктор целесообразно использо­вать в том случае, когда к моменту создания объекта поток либо не существует и его создание только предполагается, либо у объекта нет никаких данных о потоке и предполагается, что поток будет ассоциирован с объектом позже.



Итак, связь между сообщениями, указателями на функции-об­работчики, а также аргументами функций и возвращаемыми зна­чениями нам вполне ясна. Значит, мы можем вернуться к рассмот­рению макроса DECLARE_MESSAGE_MAP.

Для доступа к элементам таблицы сообщений используется структура messageMap типа AFXJVISGMAP. Эта структура описа­на в файле afxwin.h:

struct AFX_MSGMAP {

#ifdef _AFXDLL

const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();

#else

const AFX_MSGMAP* pBaseMap;

#endif

const AFX_MSGMAP_ENTRY* IpEntries;

};