

Программирование на языке MFC
Мой второй блог в серии программирования
Архив раздела «Работа с файлами»
В качестве первого аргумента передается указатель на буфер, в который будет производиться считывание данных. Второй аргумент – это число байтов, которые необходимо считать из файла, с которым архив ассоциирован. Посмотрим теперь, что происходит во время работы метода.
Само собой разумеется, что если число байтов, которые мы хотим считать, равно нулю, то никаких действий не производится и метод немедленно возвращает нуль. Однако, если мы все же хотим считать определенное количество байтов, то нам нужно вспомнить о том, что с нашим архивом ассоциирован буфер в оперативной памяти. Вполне вероятно, что до обращения к методу Read() мы уже осуществляли чтение в буфер, правильно? Значит, все данные, которые мы хотим считать, или хотя бы часть этих данных могут находиться в буфере, не так ли? Можно просто взять эту часть данных из буфера, а не обращаться к диску, верно? Метод копирует тот участок буфера архива, который находится между указателями mJpBufCur и mJpBufMax, в начало буфера, указатель на который передан методу в качестве первого аргумента. Корректировка указателей при этом пока не производится.
Значит, читать осталось только ту часть, которую мы не считывали ранее, верно? Соответственно этому, метод корректирует счетчик байтов, подлежащих считыванию. Кроме этого, мы уже, возможно, при копировании части буфера архива уже заполнили данными часть переданного методу буфера, верно? Значит, необходимо сместить и указатель на то место, куда мы будем записывать данные. Метод осуществляет и эту коррректировку.
Если те данные, которые мы собирались читать из архива, полностью находились в буфере, то на этом работа метода завершается. Метод возвращает число взятых из буфера байтов (в данном случае безразлично, откуда берутся данные, верно?).
Но если необходимо читать данные из файла… Если бы метод читал данные из файла в буфер архива, а затем бы копировал их в буфер пользователя, то при этом возникли бы непроизводительные затраты, так как при многократном считывании пришлось бы каждый раз затирать данные буфера архива. Разработчики MFC поступили в данном случае более интересно, в буфере архива остается только ПОСЛЕДНЯЯ часть данных, вся остальная информация копируется из-файла в буфер пользователя напрямую. Обратите внимание, как вычисляется размер этой последней части – просто берется остаток от деления числа байтов, подлежащих считыванию из файла за вычетом находившихся в буфере архива, на размер буфера архива. При этом производятся все действия по заполнению буфера, которые производилсь и в методе FillBufferQ. После того как чтение выполнено полностью, метод возвращает общее число считанных из буфера и из файла байтов.
После всего того, что мы узнали, можно сделать вывод о том, что метод осуществляет буферированное чтение из архива.
читать отзывы (0)
Но, несмотря на то, что текст метода дочтаточно велик, понять принцип его работы гораздо проще. Если мы осуществляем чтение из архива, устанавливаем указатель файла на конец действительно считанных, а не буферированных данных, после чего указатель на текущую позицию приравниваем указателю на считанные данные, т. е. определяем, что буфер становится пустым. Если мы осуществляем запись в архив, то все, что находится в промежутке от начала до текущей позиции буфера, записывается в файл. После этого указатель на текущую позицию буфера устанавливается на начало буфера. Все! Буфер пуст! © Другими словами, метод осуществляет запись в файл содержимого буфера, после чего текущей позицией в буфере становится начало буфера (буфер «сбрасывается»).
Для того чтобы осуществить чтение из архива определенного числа байтов, можно вопользоваться методом 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 занимает то положение, которое и должен занимать!
В классе 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, является «указателем» на элемент списка. Использование в программе нескольких переменных типа POSITION позволяет использовать список как список со многими указателями.
Метод получает в качестве аргументов положение (текущего) элемента, перед которым будет вставлен новый элемент, и указатель на данные вновь создаваемого элемента. Во время работы метод создает новый элемент при помощи метода 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;
}
Методу в качестве аргументов передаются ССЫЛКА на положение текущего элемента, а также ссылка на слово, в которое будет записано значение ключа следующей ассоциации, и ссылка на указатель на объект, в который (указатель) будет записано значение, выбранное из ассоциации, следующей за текущей. Положение текущего элемента после выборки также сдвигается к следующему элементу. После того, что мы уже узнали при рассмотрении других методов, ничего интересного или заслуживающего внимания в тексте метода нет.
Ниже приведен файл 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;
}
