

Программирование на языке MFC
Мой второй блог в серии программирования
Тем не менее, мы может прекратить работу архива в любое время при помощи метода Abort(), исходный текст которого находится в файле агссоге.срр:
void CArchive::Abort() {
ASSERT(m_bDirectBuffer ||
m_lpBufStart == NULL || AfxIsValidAddress(m_lpBufStart,
m_lpBufMax – m_lpBufStart, IsStoring()));
ASSERT(m_bDirectBuffer ||
m_lpBufCur == NULL || AfxIsValidAddress(m_lpBufCur,
m_lpBufMax – m_lpBufCur, IsStoring()));
// disconnect from the file mjpFile = NULL;
if (!m_bUserBuf) {
ASSERT(!m_bDirectBuffer); delete[] m_lpBufStart; m_lpBufStart = NULL; m_lpBufCur = NULL;
}
delete m_pSchemaMap; m_pSchemaMap = NULL;
// m_pStoreMap and m_pLoadArray are unioned, // so we only need to delete one
ASSERT((CObject*)m_pStoreMap == (CObject*)m_pLoadArray); delete (CObject*)m_pLoadArray; m_pLoadArray = NULL;Посмотрим, что происходит с архивом в случае прекращения работы посредством вызова метода AbortQ. Первым делом метод «отсоединяет» архив от файла, присваивая полю m__pFile значение NULL. Затем в том случае, если у архива есть ассоциированный с ним буфер, производится удаление буфера. Если в буфере остались данные, которые не были записаны в файл, то эти данные будут потеряны. Указатели на начало буфера и на текущую позицию буфера делаются равными NULL. Затем удаляется указатель на хэш-таблицу, содержащую номера схем классов, а за ней и хэш-таблица (или массив) сохраненных объектов.
читать отзывы (0)
Метод производит считывание номера схемы класса и длины названия класса без учета завершающего нулевого байта, после чего в выделенный для названия класса символьный массив читает название класса. Если указанное в архиве число символов в названии класса превышает размер выделенного для него буфера или же при считывании из архива было считано меньше, чем указано, байтов, то метод немедленно возвращает NULL. После того как название класса считано, к нему дописывается нулевой байт, т. е. с этого момента название класса становится обычной строкой, завершающейся нулевым байтом. Затем метод проверяет, описан ли класс, название которого прочитано в архиве, в текущем процессе. Если класс не описан, то метод возвращает значение NULL. Таким образом, если: а) длина имени сохраненного класса равна 64 байтам или больше; б) если при считывании имени класса произошла какая-то ошибка и было считано меньше символов, чем указано; в) класс не описан в вызывающем модуле, то метод CRuntime::Load() возвращает значение NULL. Если же все проверки прошли нормально, то возвращается указатель на информацию времени выполнения того класса, имя которого совпадает и именем считанного из архива класса, и управление опять переходит в метод ReadClassQ. Таким образом, если метод CRuntimeClass::Load() возвратил ненулевое значение, то это является признаком того, что в процессе, осуществляющем чтение из архива, класс, описание которого только что было считано из архива, также описан и процесс готов работать с объектами данного класса.
Если метод CRuntimeClass::Load() вернул значение NULL, это означает, что произошла ошибка, которую метод ReadClass() самостоятельно устранить не в состоянии. Естественный выход для метода ReadClass() в этой ситуации – выработка исключения, что он и делает. Если же загрузка прошла нормально, то начинается проверка соответствия схем (версий) классов, ведь вполне вероятно, что в программе используется описание более новой версии класса, чем сохраненная в архиве, верно?
Если схемы класса в вызывающем модуле и в считанной из архива информации не совпали и при этом не указано, что класс может загружать информацию разных версий, то вырабатывается исключение. В том случае, если схемы не совпадают, но указано, что класс может загружать информацию разных версий, для хранения несовпадающих версий заводится новая хэш-таблица, указатель на нее записывается в поле mjDSchemaMap. Ключами в этой таблице служат указатели на информацию времени выполнения класса, считанного из архива, а значения, которые принимают элементы, равны номерам схем считанных из архива классов. Здесь я не поленюсь лишний раз повторить, что хэш-таблица заводится для хранения ТОЛЬКО тех схем, номера которых НЕ СОВПАДАЮТ со схемой класса, информация о котором передана методу в качестве аргумента.
Раз заведена хэш-таблица, то как же обойтись без проверки числа элементов в ней при помощи метода CheckCount()? А после проверки указатель на информацию времени исполнения класса записывается в очередной свободный элемент массива указателей, который был заведен при вызове метода MapObject(). Естественно, число элементов в этом массиве увеличивается на единицу. *
Тем самым мы завершили обработку описания впервые встретившегося класса. А что происходит в тех случаях, когда мы считываем ссылки на ранее встречавшиеся описания классов? Если индекс ранее встречавшегося класса не равен нулю (если читатель помнит, то нулевой элемент массива инициализируется значением NULL) и не превышает верхнего индекса массива (индекс в таком случае просто лишен смысла), из элемента массива с указанным индексом выбирается информация времени исполнения класса…
Так… А почему мы постоянно говорим о массиве? Дело в том, что индекс, с которым элемент был добавлен в ХЭШ-ТАБЛИЦУ при записи в архив, может использоваться как индекс МАССИВА при считывании из архива. Ведь в данном случае нам не нужно осуществлять массу проверок, верно? И для хранения данных вполне достаточно массива, доступ к элементам которого можно осуществлять по индексам, верно?
Практически сразу после вызова ReadClassQ вызывает метод MapObject() с параметром NULL. Но обратите внимание, читатель, на то, что в случае чтения информации из архива создается не хэш-таблица, а МАССИВ указателей. Указатель на массив записывается в поле m_pLoadArray, которое, кстати, описано в одном объединении (union’e) с полем m_pStoreMap. После этого в архиве создается один элемент, в который записывается значение NULL. Естественно, счетчик элементов массива, т. е. значение поля m_nMapCount, тоже делается равным одному. Как и в случае сохранения информации в массиве, при аргументе, равном NULL метод больше ничего не делает. Таким образом, вызов метода MapObject() с параметром NULL при чтении из архива приводит к созданию и инициализации массива указателей.
Затем метод ReadClass() начинает работу в точном соответствии с тем списком правил, который мы сформировали в конце предыдущего раздела. В том случае, если считан тэг объекта, то метод записывает тэг объекта по адресу, переданному ему в качестве третьего аргумента, и возвращает значение NULL. Отметим этот факт – метод ReadClassQ в том случае, если из архива считан тэг объекта, возвращает значение NULL. У объекта схемы (версии) быть не может, поэтому мы не заполняем указатель на схему. Однако если из архива считан тэг класса, нам придется немного повозиться с этим тэгом.
Давайте рассуждать. Если встречен тэг класса, то какие случаи должны быть рассмотрены при этом? Наверное, должны быть рассмотрены два случая: 1) считан тэг ранее не встречавшегося класса и 2) считана ссылка на тэг ранее встречавшегося в процессе считывания класса. В том случае, если метод ReadClass() считал из архива тэг ранее не встречавшегося класса, то он должен загрузить информацию времени выполнения этого класса, после чего осуществить все необходимые проверки, верно? Информация времени выполнения загружается при помощи метода CRuntimeClass::Load(), исходный текст которого можно найти в файле агссоге.срр:
CRuntimeClass* PASCAL CRuntimeClass::Load(CArchive& ar,
UINT*’pwSchemaNum)
// loads a runtime class description
{
WORD nLen;
char szClassName[64]; CRuntimeClass* pClass;
WORD wTemp;
ar >> wTemp; *pwSchemaNum = wTemp; ar >> nLen;
if (nLen >= _countof(szClassName) ||
ar.Read(szClassName, nLen*sizeof(char)) !=
nLen*sizeof(char))
{
return NULL;
}
szClassName[nLen] = Л\0′;
// search app specific classes
AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); AfxLockGlobals(CRIT_RUNTIMECLASSLIST);
for (pClass = pModuleState->m_classList; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (IstrcmpA(szClassName,
pClass->m_lpszClassName) == 0)
{
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST); return pClass;
}
}
AfxUnlockGlobals(CRIT_RUNTIMECLASSLIST);
#ifdef _AFXDLL
// search classes in shared DLLs AfxLockGlobals(CRITJDYNLINKLIST);
for (CDynLinkLibrary* pDLL = pModuleState->m_libraryList; pDLL != NULL; pDLL = pDLL->m_pNextDLL)
{
for (pClass = pDLL->m_classList; pClass != NULL; pClass = pClass->m_pNextClass)
{
if (IstrcmpA(szClassName,
pClass->m_lpszClassName) == 0)
{
AfxUnlockGlobals(CRIT_DYNLINKL1ST) ; return pClass;
}
}
}
AfxUnlockGlobals(CRIT_DYNLINKLIST); #endif
TRACE1 ("’Warning: Cannot load %hs from archive.
Class not defined.\n", szClassName);
return NULL; // not found
}
В тексте этого метода нужно обратить внимание на одну мелочь: для чтения названия класса выделяется буфер размером 64 байта. Следовательно, название класса не может быть длиннее 64 символов.
Уважаемый читатель, я очень надеюсь, что вы не пожалели времени, затраченного на изучение процесса записи объектов в архив. Несмотря на то, что вы, зная формат архива, в состоянии самостоятельно считать данные из архива, нам необходимо довести дело до логического завершения и изучить, каким образом объекты, сохраненные в архиве, могут быть извлечены оттуда средствами библиотеки MFC. Для того чтобы осуществить чтение объекта из архива, можно воспользоваться методом ReadObject(), который в файле arcobj.cpp описан следующим образом:
CObject* CArchive::ReadObject(const CRuntimeClass*
pClassRefRequested)
{
ASSERT(pClassRefRequested == NULL || AfxIsValidAddress(pClassRefRequested,
sizeof(CRuntimeClass), FALSE));
ASSERT(IsLoading()); // proper direction ASSERT(wNullTag == 0);
ASSERT((wClassTag « 16) == dwBigClassTag); ASSERT((wNewClassTag & wClassTag) == wClassTag);
// attempt to load next stream as CRuntimeClass UINT nSchema; DWORD obTag;
CRuntimeClass* pClassRef = ReadClass(pClassRefRequested,
SnSchema, &obTag);
// check to see if tag to already loaded object CObject* pOb; if (pClassRef == NULL) {
if (obTag > (DWORD)m_pLoadArray->GetUpperBound()) {
// tag is too large for the number of objects read so far AfxThrowArchiveException(CArchiveException::badlndex,
m_strFileName);
}
pOb = (CObject*)m_pLoadArray->GetAt(obTag); if (pOb != NULL &&
pClassRefRequested != NULL && !pOb->IsKindOf(pClassRefRequested))
{
// loaded an object but of the wrong class
AfxThrowArchiveException(CArchiveException:rbadClass,
m_strFileName);
}
}
else {
// allocate a new object based on the class just acquired pOb = pClassRef->CreateObject(); if (pOb == NULL)
AfxThrowMemoryException();
// Add to mapping array BEFORE de-serializing CheckCount();
m_pLoadArray->InsertAt(m_nMapCount + + , pOb) ;
// Serialize the object with the schema number set // in the archive
UINT nSchemaSave = m_nObjectSchema;
m_nObjectSchema = nSchema;
pOb->Serialize(*this);
m_nObjectSchema = nSchemaSave;
ASSERT_VALID(pOb) ;
}
return pOb;
}
Что ж, в связи с важностью метода необходимо этот метод рассмотреть более подробно. Понятно, что в качестве аргумента методу передается указатель на записываемый в архив объект. Сразу после проведения необходимых проверок вызывается метод 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);
}
}
}
Теперь мы знаем, как осуществляется чтение из архива простых типов данных. Надеюсь, для того чтобы разобрать перегруженный оператор ««», нам потребуется значительно меньше времени. Код оператора также существует в двух вариантах, опять мы остановимся на более простом, который находится в файле 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 занимает то положение, которое и должен занимать!
// Шаг 6. Регистрируем класс окон, к которому будет // принадлежать главное окно приложения.
pszWndClassName = AfxRegisterWndClass ( CS_HREDRAW |
CS_VREDRAW, LoadStandardCursor( IDC_ARROW ), ( HBRUSH ) ::GetStockObject( WHITE_BRUSH ), LoadStandardlcon( IDI_APPLICATION ) );
Однако я не могу считать раскрытой до конца тему о написании программ для MFC. Дело в том, что в исходных кодах MFC на каждом шагу попадаются макросы с именами DECLARE_DYNAMIC(), IMPLEMENTJDYNAMfCO, DECLARE_DYNCREATE() и IMPLEMENT-_DYNCREATE(). О них-то мы сейчас и поговорим.
У каждого объекта, унаследованного от объекта класса СОЬ-ject, есть поле, содержащее в себе информацию времени выполнения. Эта информация представляет собой структуру типа CRunt-imeClass, которая описана в файле следующим образом:
struct CRuntimeClass {
// Attributes
LPCSTR m_lpszClassName; int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class CObject* (PASCAL* m_pfnCreateObject) () ;
// NULL => abstract class
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass) (); #else
CRuntimeClass* m_pBaseClass; #endif
// Operations
CObject* CreateObject();
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
// Implementation
void Store(CArchive& ar) const;
static CRuntimeClass* PASCAL Load(CArchive& ar,
UINT* pwSchemaNum) ; // CRuntimeClass objects linked together in simple list CRuntimeClass* m_pNextClass;
// linked list of registered classes
};
Что хранит в себе информация времени выполнения? Естественно, она должна хранить имя класса. Для хранения имени класса служит поле mJpszClassName. Для того чтобы мы могли создать объект этого класса динамически, мы должны знать размер, который занимает объект этого класса. Размер объекта хранится в поле m__nObjectSize. Поле m__wSchema фактически является номером версии класса. Дело в том, что описание класса может изменяться от версии программы к версии, поэтому иногда (в частности, в ходе процесса сериализации) возникает необходимость узнать, к какой версии описания класса принадлежит объект.
Для того чтобы в ходе выполнения программы мог быть создан объект класса, который описывает CRuntimeClass, она хранит в себе указатель на конструктор по умолчанию этого класса – поле m_pfnCreateObject (CObject* (PASCAL* m_pfnCreateObject)()). Непосредственно для создания объекта можно воспользоваться методом CreateObjectQ, который, выполняя функцию, указатель на которую находится в поле m_pfnCreateObject, возвращает указатель на созданный объект.
Если в программе возникла необходимость получить информацию о том классе, от которого был унаследован описываемый в структуре файл, то для этого можно воспользоваться полем mjDfnGetBaseClass (CRuntimeClass* (PASCAL* m_pfnGetBase-Class)()), в котором находится указатель на метод, позволяющий получить такую информацию.
Для сохранения объекта описываемого класса в архиве служит метод Store(). Восстановить объект из архива можно при помощи метода Load(). О том, как работают эти методы, мы поговорим при рассмотрении процесса сериализации.
Но, наверное, самым интересным полем является поле mjDNextClass. Судя по тому, что оно фактически является указателем на информацию времени исполнения, а также взглянув на название этого поля, можно догадаться, что все структуры типа CRuntimeClass объединены в однонаправленный список. Зная указатель на начало этого списка, мы можем получить информацию о том, какие классы зарегистрированы в системе.
Итак, в файле afxcoll.h класс для работы с массивом указателей на объекты описан следующим образом:
class СОЬАггау : public CObject {
DECLARE_SERIAL(СОЬАггау) public:
// Construction СОЬАггау() ;
// Attributes
int GetSize() const;
int GetUpperBound() const;
void SetSize(int nNewSize, int nGrowBy = -1);
// Operations // Clean up void FreeExtraO; void RemoveAll();
// Accessing elements
CObject* GetAt(in-t nlndex) const;
void SetAt(int nlndex, CObject* newElement);
CObject*& ElementAt(int nlndex);
// Direct Access to the element data (may return NULL) const CObject** GetDataO const; CObject** GetDataO;
// Potentially growing the array
void SetAtGrow(int nlndex, CObject* newElement);
int Add(CObject* newElement);
int Append(const CObArray& src); void Copy(const CObArrayS src);
// overloaded operator helpers CObject* operator [] (int nlndex) const!1; CObject*& operator[](int nlndex);
// Operations that move elements around void InsertAt(int nlndex,
CObject* newElement,
int nCount = 1);
void RemoveAt(int nlndex, int nCount = 1);
void InsertAt(int nStartlndex, CObArray* pNewArray);
// Implementation protected:
CObject** m_pData; // the actual array of data
int m_nSize; // # of elements (upperBound – 1)
int m_nMaxSize; // max allocated int m_nGrowBy; // grow amount
public:
-CObArray();
void Serialize(CArchiveS); #ifdef _DEBUG
void Dump(CDumpContextS) const;
void AssertValid() const; #endif
protected:
// local typedefs for class templates typedef CObject* BASE_TYPE; typedef CObject* BASE_ARG_TYPE;
};
После выдачи отладочного сообщения функция 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;
}
