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

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

Архив раздела «Работа с файлами»

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;

}



Исходный текст макроса BEGIN_MESSAGE_MAP() находится в файле afxwin.h:

#ifdef _AFXDLL

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \

const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \

{ return &baseClassiimessageMap; } \ const AFX_MSGMAP* theClass::GetMessageMap() const \

{ return &theClass:imessageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP

theClass: .-messageMap = \

{ &theClass::_GetBaseMessageMap,

&theClass::_messageEntries[0] }; \ AFX_COMDAT const AFX_MSGMAP_ENTRY heClass::_messageEntries[] = \ { \

#else

#define BEGrN_MESSAGE_MAP(theClass, baseClass) \

const AFX_MSGMAP* theClass::GetMessageMap() const \

{ return &theClassiimessageMap; } \ AFX_COMDAT AFX_DATADEF const AFX_MSGMAP

theClassiimessageMap = \ { &baseClassiimessageMap, StheClassii_messageEntries[0] }; \ ABnX_COMDAT const AFX_MSGMAP_ENTRY

theClassii_messageEntries[] = \

{ \

#endif



В данном случае метод вызывает метод CWnd::PreTranslate-Message() для главного окна, ассоциированного с объектом клас­са CWinThread, и всех родительских окон главного окна. Если ка­кое-то из этих окон обработало сообщение, метод возвращает TRUE. В противном случае возвращается значение FALSE.

И наконец, в случае, если ни объект класса CWinThread, ни ас­социированное с ним главное окно и его предки сообщение не об­работали, проверяется, а не имеем ли мы дело с немодальным диалоговым окном и не может ли этот немодальный диалог обра­ботать сообщение. Возвращенное значение TRUE говорит о том, что сообщение обработано. Значение FALSE является признаком того, что сообщение не было обработано до сих пор.

Итак, метод CWinThread::PreTranslateMessage() пытается сам обработать сообщение при помощи метода DispatchThreadMes-sageExQ или передать его на обработку главному окну, ассоции­рованному с объектом класса CWinThread. В том случае, если сообщение обработано, метод возвращает TRUE. Возвращен­ное значение FALSE свидетельствует о том, что сообщение осталось необработанным.

Но, уважаемый читатель, нам опять необходимо вспомнить о том, что метод CWinThread::PreTranslateMessage() был вызван из метода PumpMessage(). Еще раз взглянув на его исходный текст, можно сде­лать следующий вывод – метод CWinThread::PumpMessage() реали­зует основной цикл обработки сообщений. В том случае, если по­лученное сообщение не удается обработать при помощи метода CWinThread::PreTranslateMessage(), для обработки сообщения вы­зываются глобальные функции TranslateMessageQ и DispatchMes-sageQ. Метод CWinThread::PumpMessage() ПОЧТИ ВСЕГДА воз­вращает значение TRUE. Значение FALSE возвращается ТОЛЬКО в случае получения сообщения WM_QUIT, что означает заверше­ние работы.



Макрос RUNTIME__CLASS является просто более удобной фор­мой получения указателя на информацию времени выполнения.

В результате работы макроса IMPLEMENJ_DYNAMIC форми­руются исходные тексты методов __GetBaseClass() и GetRuntime-Class(), а также инициализируется поле с именем class<HMfl-_класса>.

Тем не менее, в случае использования макроса DECLARE-_DYNAMIC() у нас нет возможности динамически создавать объек­ты класса. Для того чтобы получить такую возможность, можно при описании класса воспользоваться не макросом DECLARE-JDYNAMIC(), а макросом DECLARE_DYNCREATE(), чей исходный код можно найти в файле afx.h:

#define DECLARE_DYNCREATE(class_name) \ DECLARE_DYNAMIC(class_name) \ static CObject* PASCAL CreateObject();



Здесь я хотел бы обратить внимание читателя на один момент, важный для понимания смысла положения элемента. Предполо­жим, что положение – это не что иное, как своеобразный указатель на текущий элемент. Воспринимая положение как указатель на те­кущий элемент, можно облегчить себе понимание работы со спи­ском. Естественно, в программе можно завести массу переменных типа POSITION, которые будут связаны с одним списком. Но ведь это же фактически является созданием списка со многими указа­телями, не так ли? Итак, можно сделать вывод о том, что перемен­ная типа POSITION, является «указателем» на элемент списка. Использование в программе нескольких переменных типа POSI­TION позволяет использовать список как список со многими ука­зателями.



03.02.2010

Метод получает в качестве аргументов положение (текуще­го) элемента, перед которым будет вставлен новый элемент, и указатель на данные вновь создаваемого элемента. Во время работы метод создает новый элемент при помощи метода NewNodeQ, записывает в него указатель на данные, вставляет вновь созданный элемент перед текущим элементом и возвра­щает положение нового элемента.

Совершенно аналогичным образом работает метод lnsertAfter(), который вставляет документ после текущего элемента. Различия между исходными кодами этих методов крайне незначительны.

Научившись создавать список и добавлять в него элементы по­одиночке, можно рассмотреть и добавление к списку другого спи­ска. Метод AddHead() добавляет один список в начало другого:

void CObList:-.AddHead (CObList* pNewList) {

ASSERT_VALID(this); ASSERT(pNewList != NULL); ASSERT_KINDOF(CObList, pNewList); ASSERT_VALID(pNewList);

// add a list of same elements to head (maintain order) POSITION pos = pNewList->GetTailPosition(); while (pos != NULL)

AddHead(pNewList->GetPrev(pos));

}



Это положение может потом использоваться для получения зна­чений всех элементов таблицы при помощи метода GetNextAssoc():

void CMapWordToOb::GetNextAssoc(POSITIONS rNextPosition,

WORD& rKey,

CObject*& rvalue) const

{

ASSERT_VALID(this) ;

ASSERT(m_pHashTable != NULL); // never call on empty map CAssoc* pAssocRet = (CAssoc*)rNextPosition; ASSERT(pAssocRet != NULL);

if (pAssocRet == (CAssoc*) BE FORE_START_POSITION) {

// find the first association for (UINT nBucket = 0;

nBucket < m_nHashTableSize;

nBucket++)

if ((pAssocRet = m_pHashTable[nBucket]) != NULL) break;

ASSERT(pAssocRet != NULL); // must find something

.}

// find next association

ASSERT(AfxIsValidAddress(pAssocRet, sizeof(CAssoc)));

CAssoc* pAssocNext;

if ((pAssocNext = pAssocRet->pNext) == NULL) {

// go to next bucket

for (UINT nBucket = (HashKey(pAssocRet->key) %

m_nHashTableSize) + 1;

nBucket < m_nHashTableSize; nBucket++) if ((pAssocNext = m_pHashTable[nBucket]) != NULL) break;

}

rNextPosition = (POSITION) pAssocNext; // fill in return data rKey = pAssocRet->key; rValue = pAssocRet->value;

}

Методу в качестве аргументов передаются ССЫЛКА на положе­ние текущего элемента, а также ссылка на слово, в которое будет записано значение ключа следующей ассоциации, и ссылка на ука­затель на объект, в который (указатель) будет записано значение, выбранное из ассоциации, следующей за текущей. Положение те­кущего элемента после выборки также сдвигается к следующему элементу. После того, что мы уже узнали при рассмотрении других методов, ничего интересного или заслуживающего внимания в тек­сте метода нет.



03.02.2010

Ниже приведен файл tib.h:

//==============================================

// File: TIB.H

// Author: Matt Pietrek

// From: Microsoft Systems Journal "Under the Hood", // May 1996

//===============================================

#pragma pack(l)

typedef struct _EXCEРТION_REGISTRATION_RECORD {

struct _EXCEРТION_REGISTRATION_RECORD * pNext; FARPROC pfnHandler; } EXCEPTION_REGISTRATION_RECORD,

*РЕХСЕ РТION_REGISTRATION_RECORD; typedef struct _TIB {

PEXCEPTION_REGISTRATION_RECORD pvExcept; // OOh Head of

// exception record list

PVOID pvStackUserTop; // 04h Top of user stack

PVOID pvStackUserBase; // 08h Base of user stack

union // OCh (NT/Win95 differences)

{

struct // Win95 fields {

WORD pvTDB; // OCh TDB

WORD pvThunkSS; // OEh SS selector used

// for thunking to 16 bits
DWORD unknownl;
// lOh

} WIN95;

struct // WinNT fields {

PVOID SubSystemTib; // OCh

ULONG FiberData; // lOh

} WINNT; } TIBJJNION1;

PVOID pvArbitrary; // 14h Available for application use struct _tib *ptibSelf; // 18h Linear address of TIB structure

union {

// ICh (NT/Win95 differences)

struct // Win95 fields {

WORD WORD DWORD DWORD DWORD } WIN95;

ICh lEh 20h 24h

// // // //

TIBFlags; Winl6MutexCount; DebugContext; pCurrentPriority;

// 28h Message Queue selector

struct // WinNT fields {

DWORD unknownl;

// IChDWORD processID; // 20h

DWORD threadID; // 24h

DWORD unknown2; // 28h

} WINNT; } TIB_UNION2;

PVOID* pvTLSArray; // 2Ch Thread Local Storage array

union // 30h (NT/WinJ5 differences)

{

struct // Win95 fields {

PVOID* pProcess; // 30h Pointer to owning // process" database

} WIN95; } TIB_UNI0N3;

} TIB, *PTIB; #pragma pack()

И наконец, результат работы программы:

Pointer to the TIB = 815f75cc

Pointer to _EXCEРТION_REGISTRATION_RECORD = 0064fe28

Pointer to the next record = 0064ЈЈ68

Pointer to the exception handler = 00401c38

Pointer to _EXCEРТION_REGISTRATION_RECORD = 0064ff68

Pointer to the next record = ffffffff

Pointer to the exception handler = bffc05b4

Warning: m_pMainWnd is NULL in CWinApp: :Run – quitting

application.



И наконец, пятое поле, NumberParameters, содержит число эле­ментов в массиве Exceptionlnformation, который является шестым полем структуры.

Структура CONTEXT содержит машинно-забисимые данные. Для компьютеров на базе процессоров х86 она в файле winnt.h описа­на следующим образом:

typedef struct _CONTEXT { //

// The flags values within this flag control

// the contents of a CONTEXT record.

//

// If the context record is used as an input

// parameter, then for each portion of the context

// record controlled by a flag whose value is set,

// it is assumed that that portion of the

// context record contains valid context.

// If the context record is being used to modify

// a threads context, then only that

// portion of the threads context will be modified.

//

// If the context record is used as an IN OUT parameter

// to capture the context of a thread, then only those // portions of the thread’s context corresponding to // set flags will be returned. //

// The context record is never used as an OUT only // parameter.

DWORD ContextFlags;

// This section is specified/returned if

// CONTEXT_DEBUG_REGISTERS is set in ContextFlags.

// Note that CONTEXT_DEBUG_REGISTERS is NOT

// included in CONTEXT_FULL.

DWORD DrO; DWORD Drl; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7;

// This section is specified/returned if the // ContextFlags word contians the flag // CONTE*T_FLOATING_POINT.

FLOATING_SAVE_AREA FloatSave;

//

// This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS.

DWORD SegGs;

DWORD SegFs;

DWORD SegEs;

DWORD SegDs;

//

// This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. //

DWORD Edi; DWORD Esi;

DWORD Ebx;

DWORD Edx;

DWORD Ecx;

DWORD Eax;

//

// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//

DWORD Ebp;

DWORD Eip;

DWORD SegCs;

DWORD EFlags;

DWORD Esp;

DWORD SegSs;

//

// This section is specified/returned if the // ContextFlags word contains the flag // CONTEXT_EXTENDED_REGISTERS.

// The format and contexts are processor specific //

BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;



После выдачи отладочного сообщения функция AfxThrowFileEx-ception() создает новый объект класса CFileException, используя при этом значения, переданные функции в качестве аргументов, и формирует исключение при помощи оператора throw. Только и всего.

Теперь неплохо было бы разобраться, что же делает тот метод GetErrorMessage(), который сводит в базовом методе на нет все усилия программиста получить хоть какое-то сообщение?

Исходный код этого метода находится в файле filex.cpp:

BOOL CFileException::GetErrorMessage(LPTSTR IpszError,

UINT nMaxError, PUINT pnHelpContext)

{

ASSERT(IpszError != NULL &&

AfxlsValidString(IpszError, nMaxError));

if (pnHelpContext != NULL)

^pnHelpContext = m_cause + AFX_IDP_FILE_NONE;

CString strMessage;

CString strFileName = m_strFileName; if (strFileName.IsEmpty())

strFileName.LoadString(AFX_IDS_UNNAME D_FILE); AfxFormatStringl(strMessage,

m_cause + AFX_IDP_FILE_NONE, strFileName); lstrcpyn(IpszError, strMessage, nMaxError);

return TRUE;

}



Названия методов, которые обеспечивают получение информации о файле, ассоциированном с объектом типа CFile, начинаются с «Get». Мне трудно определить, какие методы важны более, ка­кие – менее, поэтому я буду рассматривать эти методы в том по­рядке, в котором они расположены в описании класса. Назначение этих методов и, соответственно, характер формируемой ими ин­формации мы определим на основе анализа исходных кодов этих методов. Итак, начнем с метода GetPosition().

Исходный код метода GetPositionQ находится в файле filecore.cpp:

DWORD CFile::GetPosition() const {

ASSERT_VALID(this);

ASSERT(m_hFile != (UINT)hFileNull);

DWORD dwPos = ::SetFilePointer((HANDLE)m_hFile,

0,

NULL,

FILE_CURRENT);

if (dwPos == (DWORD)-1)

CFileException:: ThrowOsError ((LONG) :: GetLastError ()) ;

return dwPos;

}