

Программирование на языке MFC
Мой второй блог в серии программирования
Архив раздела «Возможности MFC»
Для чтения информации из файла может быть использован метод Read(), исходный код которого находится в файле filecore.cpp:
UINT CFile::Read(void* lpBuf, UINT nCount) {
ASSERT_VALID(this);
ASSERT(m_hFile != (UINT)hFileNull);
if (nCount == 0)
return 0; // avoid Win32 "null-read"
ASSERT(lpBuf != NULL);
ASSERT(AfxIsValidAddress(lpBuf, nCount)); DWORD dwRead;
if (!::ReadFile((HANDLE)m_hFile, lpBuf, nCount, SdwRead, NULL)) CFileException::ThrowOsError((LONG)::GetLastError ());
return (UINT)dwRead;
}
читать отзывы (0)
После этого времена создания, изменения и последнего доступа к файлу, полученные при помощи метода CTime::GetTime(), преобразуются в форму, необходимую для использования функцией SetFile-Time(). После открытия файла при помощи функции CreateFile(), производится установка всех времен, связанных с файлом, а также и всех остальных атрибутов. В случае возникновения каких-либо ошибок формируются исключения.
Следующим методом, позволяющим в кдком-то смысле изменить информацию о файле, является метод SetFilePath(). Его исходный код можно найти в файле afx.ini:
_AFX_INLINE void CFile::SetFilePath(LPCTSTR IpszNewName) {
ASSERT_VALID(this);
ASSERT(AfxIsValidString(IpszNewName)); m_strFileName = IpszNewName;
}
Этот метод ничего не делает, кроме того, что присваивает новое значение полю m_strFileName. Другими словами, метод ассоциирует имя файла с объектом типа CFile. Этот метод может использоваться в тех случаях, когда, скажем, при создании объекта не было известно имя ассоциированного файла.
И, наконец, последний метод этой группы – SetLength(). Его исходный код находится в файле filecore.cpp:
void CFile::SetLength(DWORD dwNewLen) {
ASSERT_VALID(this);
ASSERT(m_hFile != (UINT)hFileNull);
Seek((LONG)dwNewLen, (UINT)begin);
if (!::SetEndOfFile((HANDLE)m_hFile))
CFileException:: ThrowOsError ((LONG) : -.GetLastError ()) ;
}
Метод устанавливает указатель файла на положение, соответствующее концу файла новой длины, после чего метод производит изменение положения маркера конца файла. Побочным эффектом этого метода, как нетрудно заметить, является изменение положения указателя файла после завершения работы метода.
Но все то, что я сказал выше, не принесет никакой пользы, если мы не сумеем читать из файла данные и осуществлять запись в файл информации.
Иногда у программиста может возникнуть необходимость каким-то образом изменить информацию о файле. Для этой цели класс CFile предлагает несколько методов. Начнем рассмотрение таковых с метода SetStatus(). Исходный код метода находится в файле filest.cpp:
void PASCAL CFile::SetStatus(LPCTSTR IpszFileName,
const CFileStatusS status)
{
DWORD wAttr;
FILETIME creationTime;
FILETIME lastAccessTime;
FILETIME lastWriteTime;
LPFILETIME lpCreationTime = NULL;
LPFILETIME lpLastAccessTime = NULL;
LPFILETIME lpLastWriteTime = NULL;
if ((wAttr = GetFileAttributes((LPTSTR)IpszFileName) ) ==
(DWORD)-1L)
CFileException::ThrowOsError((LONG)GetLastError() ) ;
if ((DWORD)status.m_attribute != wAttr && (wAttr & readonly))
{
// Set file attribute, only if currently readonly.
// This way we will be able to modify the time
// assuming the caller changed the file from readonly.
if (!SetFileAttributes((LPTSTR)IpszFileName, (DWORD)status.m_attribute)) CFileException::ThrowOsError((LONG)GetLastError() ) ;
}
// last modification time
if (status.m_mtime.GetTime() != 0)
{
AfxTimeToFileTime(status.m_mtime, &lastWriteTime) ; lpLastWriteTime = SlastWriteTime;
// last access time
if (status.m_atime.GetTime() != 0)
{
AfxTimeToFileTime(status,m_atime, SlastAccessTime); lpLastAccessTime = SlastAccessTime;
}
// create time
if (status.m_ctime.GetTime() != 0) {
AfxTimeToFileTime(status.m_ctime, ScreationTime); lpCreationTime = &creationTime;
}
HANDLE hFile = ::CreateFile(IpszFileName,
GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
CFileException::ThrowOsError((LONG)::GetLastError());
if (!SetFileTime((HANDLE)hFile,
lpCreationTime,
lpLastAccessTime,
lpLastWriteTime)) CFileException::ThrowOsError((LONG) ::GetLastError ());
if (!::CloseHandle(hFile))
CFileException::ThrowOsError((LONG)::GetLastError());
}
if ((DWORD)status.m_attribute != wAttr && !(wAttr & readonly))
{
if (!SetFileAttributes((LPTSTR)IpszFileName, (DWORD)status.m_attribute)) CFileException::ThrowOsError((LONG)GetLastError ());
}
}
Давайте, читатель, попробуем понять, что же делает этот метод. Во-первых, метод проверяет, не являются ли атрибуты файла ошибочными. При обнаружении об ошибке формируется исключение.
Как мы видим, в данном случае вызывается метод GetStatus(), результатом чего является заполненная данными о файле структура status типа CFileStatus. Возвращая поле m_szFullName этой структуры, метод тем самым возвращает ПОЛНОЕ имя файла.
Еще одним методом, позволяющим получить информацию о файле, является метод GetLength(). Исходный код этого метода находится в файле filecore.cpp:
DWORD CFile::GetLength() const {
ASSERTJVALJD(this); DWORD dwLen, dwCur;
// Seek is a non const operation CFile* pFile = (CFile*)this; dwCur = pFile->Seek(OL, current); dwLen = pFile->SeekToEnd(); VERIFY(dwCur == (DWORD)pFile->Seek(dwCur, begin));
return dwLen;
Второй метод из группы, о которой идет разговор, – это метод GetFileTitle():
CString CFile::GetFileTitle() const {
ASSERT_VALID(this);
CFileStatus status; GetStatus(status); CString strResult;
AfxGetFileTitle(status.m_szFullName,
strResult.GetBuffer(_MAX_FNAME) , _MAX_FNAME);
strResult.ReleaseBuffer();
return strResult;
Легко заметить, что все отличие этого метода от предыдущего состоит в том, что из полного имени файла при помощи функции AfxGetFileTitle() выбирается не название файла и его расширение, а только название без расширения. Таким образом мы пришли к выводу о том, что метод возвращает объект класса CString, содержащий название (без расширения) файла. Так как для получения имени файла используется глобальная функция AfxGetFileTitle(), то очевидно, что будет возвращено не полное имя файла, а только сокращенное, т.е. название файла (без расширения), например, «MyFile», «YourArchive» и т.д.
И наконец, последним методом из нашей группы является метод GetFilePath():
CString CFile::GetFilePath() const {
ASSERT_VALID(this);
CFileStatus status;
GetStatus(status);
return status.m_szFullName;
}
Вспомним, что метод GetStatus() в поле m_szFullName структуры CFileStatus записывает ПОЛНОЕ имя файла. Как мы видим, в данном случае при помощи функции AfxGetFileName() из ПОЛНОГО имени файла выбирается сокращенное имя файла, т.е. название файла и его расширение, например, «MyFile.txt», «YourArchive.arj» и т.д. Метод возвращает сокращенное имя файла, т. е. название файла и его расширение.
Вызов этой формы метода точно таким же образом заполняет структуру типа CFileStatus соответствующими значениями, но сформированные данные характеризуют файл, имя которого было передано методу в качестве первого аргумента.
Следом идут три метода, которые функционально очень похожи.Исходные коды этих методов могут быть найден в файле filest.cpp. Первый из этих методов – GetFileName().
CString CFile::GetFileName() const {
ASSERT_VALID(this);
CFileStatus status; GetStatus(status); CString strResult;
AfxGetFileName(status.m_szFullName,
strResult.GetBuffer(_MAX_FNAME), _MAX_FNAME);
strResult.ReleaseBuffer();
return strResult;
}
Какую информацию мы можем извлечь из текста этого макроса? Прежде всего, у каждого класса, с которым мы используем этот макрос, появляются дополнительные поля и методы. В частности, видно, что таблица сообщений фактически является массивом структур типа AFX_MSGMAP_ENTRY. Эта структура описана в файле afxwin.h следующим образом:
struct AFX_MSGMAP_ENTRY
{
UINT nMessage;// windows message
UINT nCode; // control code or WM_NOTIFY code
UINT nID; // control ID (or 0 for windows messages)
UINT nLastID; // used for entries specifying a range of
// control id’s UINT nSig; // signature type (action) or pointer
// to message # AFX_PMSG pfn; // routine to call (or special value)
};
Назначение полей этой структуры может изменяться в зависимости от сообщения. Всего предусмотрено девять возможных форматов полей. Форматы и назначение полей для каждого формата приведены в Приложении А. При рассмотрении этой таблицы у читателя может возникнуть вопрос – а что такое сигнатура? Фактически сигнатура определяет тип возвращаемого функцией-обработчиком значения, а также параметры функции-обработчика. Возможные значения сигнатуры приведены в Приложении Б.
Во-первых, этот макрос завершает таблицу сообщений записью с нулевой сигнатурой. Во-вторых, он ставит ту самую завершающую фигурную скобку, которой мы недосчитались при анализе текста макроса BEGIN_MESSAGE_MAP. На этом наше знакомство с таблицей сообщений можно завершить.
Перед началом рассмотрения таблицы сообщений мы остановились на методе DispatchThreadMessageEx() класса CWinThread. Итак, вернемся к коду этого метода и продолжим работу.
Итак, что происходит в этом методе? Сначала метод выбирает указатель на таблицу сообщений при помощи метода GetMessageMap(), а затем входит в цикл для поиска необходимого обработчика сообщений. При этом обработка сообщений с кодом менее OxCOOO несколько отличается от обработки сообщений с кодом от OxCOOO и выше.
Если обрабатывается сообщение с кодом менее OxCOOO, это означает, что сообщение является сообщением Windows. Для поиска необходимого обработчика вызывается функция AfxFind-MessageEntry(). Исходный код этой функции находится в файле wincore.cpp:

AfxFindMessageEntry(const AFX_MSGMAP_ENTRY* IpEntry,
UINT nMsg, UINT nCode, UINT nID)
{
#if defined(_M_IX86) && !defined(_AFX_PORTABLE) // 32-bit Intel 386/486 version.
ASSERT(offsetof(AFX_MSGMAP_ENTRY, nMessage) == 0) ;
ASSERT(offsetof(AFX_MSGMAP_ENTRY, nCode) == 4);
ASSERT(offsetof(AFX_MSGMAPJ2NTRY, nIDl == 8);
ASSERT(offsetof(AFX_MSGMAP_ENTRY, nLastID) == 12);
ASSERT(offsetof(AFX_MSGMAP_ENTRY, nSig) == 16);
asm
MOV EBX,IpEntry
MOV EAX, nMsg
MOV EDX,nCode
MOV ECX,nID
loop:
CMP DWORD PTR [EBX+16],0
JZ _ failed
CMP EAX,DWORD PTR [EBX]
JE found_message
nSig (0 => end)
nMessage
next:
ADD EBX,SIZE AFX_MSGMAP_ENTRY
JMP short loop
found_message:
CMP EDX,DWORD PTR [EBX+4]
JNE next
nCode
// message and code good so far // check the ID
CMP ECX,DWORD PTR [EBX+8]
JB _ next
CMP ECX,DWORD PTR [EBX+12]
JA next
nID
nLastID
// found a match
MOV IpEntry,EBX
JMP short end
return EBX
failed:
XOR EAX,EAX
MOV IpEntry,EAX
return NULL
end:
return IpEntry; #else // _AFX_PORTABLE
// С version of search routine while (lpEntry->nSig != AfxSig_end) {
if (lpEntry->nMessage — nMsg && lpEntry->nCode == nCode && nID >= lpEntry->nID && nID <= lpEntry->nLastID)
{
return IpEntry;
}
lpEntry++;
}
return NULL; // not found #endif // _AFX_PORTABLE }
А теперь вернемся к методу DispatchThreadMessageEx(). В том случае, если AfxFindMessageEntry() вернула FALSE, т. е. не нашла в таблице сообщений нужной строки, цикл поиск необходимого обработчика продолжается. При этом указателем на таблицу сообщений становится уже указатель на таблицу сообщений базового класса, другими словами, поиск осуществляется в таблице сообщений базового класса. Цикл продолжается до тех пор, пока указатель на таблицу сообщений не станет равным NULL или пока не будет найден необходимый элемент в таблице сообщений, т.е. пока функция AfxFindMessageEntry() не вернет ненулевое значение. Обратим на этот факт особое внимание – если в таблице сообщений объекта не найдена строка, соответствующая сообщению, то осуществляется поиск в таблице сообщений родительского класса, и так до тех пор, пока либо не будет найдена соответствующая сообщению строка, либо не будут перебраны все родительские классы, имеющие таблицы сообщений.
Программа заводит объект определенного класса, а затем перебирает информацию времени выполнения классов-предков до тех пор, пока не дойдет до класса, у которого нет предков. Всю информацию времени выполнения о себе и q родительских классах программа выдает в окно отладки. Результат работы этой программы:
Class name – CRuntimelnfoApp object size = 200 schema = OOOOffff
pointer to CreateObject() = 00401005 pointer ro _GetBaseClass = 0040101e pointer to the next class = 00000000 Class name – CWinApp object size = 200 schema = OOOOffff
pointer to CreateObject() = 00000000 pointer ro _GetBaseClass = 5f497b92 pointer to the next class = 00000000 Class name – CWinThread object size = 112 schema = OOOOffff
pointer to CreateObject() = 00000000 pointer ro _GetBaseClass = 5f496e42 pointer to the next class = 00000000 Class name – CCmdTarget object size = 32 schema = OOOOffff
pointer to CreateObject() = 00000000 pointer ro _GetBaseClass = 5f495811 pointer to the next class = 00000000 Class name – CObject object size = 4 schema = OOOOffff
pointer to CreateObject() = 00000000 pointer ro _GetBaseClass = 5f4268b0 pointer to the next class = 00000000
Warning: m_j?MainWnd is NULL in CWinApp: :Run – quitting application.
Внимательно взглянув на результаты работы программы, можно отследить всю цепочку классов, от которых опосредствованно унаследован CRuntimelnfoApp:
Кроме того, можно заметить, что только класс CRuntimelnfoApp поддерживает динамическое создание объектов [указатель на Сге-ateObject() не равен нулю], потому что при его определении я использовал макросы DECLARE_DYNCREATE() и IMPLEMENT-_DYNCREATE(). Все остальные классы не позволяют динамически создавать объекты. Естественно, это только учебный пример, однако, он демонстрирует, каким образом можно извлечь массу пользы из информации времени выполнения.
Итак, в файле 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;
};
Аналогично предыдущему работает метод AddTail():
POSITION CObList::AddTail(CObject* newElement) {
ASSERT_VALID(this);
CNode* pNewNode = NewNode(m_pNodeTail, NULL); pNewNode->data = newElement;
if (m_pNodeTail != NULL)
m_pNodeTail->pNext = pNewNode;
else
m_pNodeHead = pNewNode; m_pNodeTail = pNewNode; return (POSITION) pNewNode;
}
Очередным нашим шагом будет перебор всех элементов списка. Перебрать все элементы списка, начиная с текущего и заканчивая конечным, можно при помощи метода GetNext():
_AFXCOLL_INLINE CObject* CObList::GetNext(POSITIONS
rPosition) const // return *Position++ { CNode* pNode = (CNode*) rPosition;
ASSERT(AfxIsValidAddress(pNode, sizeof(CNode))); rPosition = (POSITION) pNode->pNext; return pNode->data; }
В качестве аргумента методу передается ссылка на положение текущего элемента. Метод делает текущим (обратите, пожалуйста, внимание, читатель, на изменение переменной типа POSITION, переданной через ссылку) следующий элемент и возвращает указатель на данные элемента, который только что стал текущим. Точно так же метод GetPrevQ делает текущим предыдущий элемент, после чего возвращает указатель на данные элемента, который только что стал текущим.
В качестве аргументов методу передаются ключ и ассоциированное с ним значение. Вся соль этого метода состоит в том, каким образом переопределен оператор П в классе CMapWordToOb. Исходный текст этого переопределения, находящийся в файле map__wo.cpp, приведен ниже:
C0bject*& CMapWordToOb::operator[](WORD key) {
ASSERT_VALID(this);
UINT nHash; CAssoc* pAssoc;
if ((pAssoc = GetAssocAt(key, nHash)) == NULL) {
if (m_pHashTable == NULL)
InitHashTable(m_nHashTableSize);
// it doesn’t exist, add a new Association pAssoc = NewAssoc(); pAssoc->key = key;
// xpAssoc->value’ is a constructed object, nothing more
// put into hash table pAssoc->pNext = m_pHashTable[nHash]; m_pHashTable[nHash] = pAssoc;
}
return pAssoc->value; // return new reference
}
Давайте попробуем разобрать, что происходит при сохранении данных. Естественно, первым делом в файл записывается число элементов таблицы. После этого в файл записываются ключи и значения, хранящиеся только в НЕПУСТЫХ списках ассоциаций. Это повторяется для всех элементов таблицы. При считывании данных сначала считываемая число элементов таблицы, затем попарно считываются ключ и значение ассоциации. Сразу после считывания ассоциация помещается в нужное место хэш-таблицы при помощи метода SetAt().
Обратным для метода NewAssoc() является метод FreeAssoc():
void CMapWordToOb::FreeAssoc(CMapWordToOb::CAssoc* pAssoc) {
pAssoc->pNext = m_pFreeList;
m_pFreeList = pAssoc; m_nCount—;
ASSERT(m_nCount >= 0); // make sure we don’t underflow
// if no more elements, cleanup completely if (m_nCount == 0) RemoveAll();
}
Метод освобождает всю память, занимаемую хэш-таблицей (но не элементами, на которые указывают указатели в ассоциациях).
Освобождается также и память, которая находится в списке свободных «заготовок», обнуляется число элементов таблицы. Указатели на список свободных блоков (поле m__pBlocks), на список свободных элементов (поле m_pFreel_ist), на хэш-таблицу (поле m_pHashTable) делаются равными NULL. В связи с тем, что память, занимаемая данными, на которые указывают указатели в ассоциациях, не освобождается, ответственность за освобождение памяти лежит на программисте. Однако разница между методами RemoveKey() и Re-moveAII() состоит еще и в следующем. При работе метода Re-moveKeyO мы теоретически можем найти указатель на данные из освобожденной ассоциации. При работе метода RemoveKey() никаким образом не можем восстановить указатели на данные. Деструктор объекта
CMapWordToOb::-CMapWordToOb() {
RemoveAll();
ASSERT(m_nCount == 0);
}
просто вызывает метод RemoveAII(), описанный выше, для того, чтобы освоболить всю память, занимаемую хэш-таблицей.
Итак, уважаемый читатель, мы расмотрели все три организации данных, для работы с которыми MFC предоставляет наборы классов.
Исходя из этой таблицы, для обработки исключения в SEH можно написать следующую конструкцию:
try .»
{
RaiseException( … ); //в случае формирования
// программного исключения
}
except(<составной оператор>)
{
>
ИЛИ
try {
RaiseException( … ); //в случае формирования
// программного исключения
}
_ finally
{ }
Очевидно, что первое поле этой структуры представляет собой указатель на предыдущую структуру этого же типа, а второе поле -указатель на обработчик исключений. Из всего сказанного выше можно сделать вывод о том, что обработчики исключений связаны в однонаправленный список, указатель на начало которого записывается в структуру типа _NT_TIB, адрес которой, в свою очередь, записывается в регистр FS.
Итак, мы получили отправную точку для дальнейших поисков. Скорее всего, мы начинаем формировать в стеке структуру типа _EXCEPTION_REGISTRATION, в которую будет записан указатель на обработчик исключений, находящийся в нашей программе. Однако не забудьте о том, что стек растет от больших адресов памяти, поэтому сначала в стек будут записан указатель на обработчик исключения, а потом указатель на следующую структуру.
Теперь забежим немного вперед. В нашем потоке до сего момента не зарегистрировано НИ ОДНОГО обработчика исключений. Если мы сейчас взглянем на содержимое участка памяти, по которому расположен TIB (fs:[00000000]), то мы увидим, что значение первого двойного слова по этому адресу, т. е. значение слова, в ко-тором должен находится указатель на структуру типа _EXCEPTION_REGISTRATION, равно OxFFFFFFFF. Из этого вытекает, что значение OxFFFFFFFF является обозначением конца списка регистрации обработчиков исключений*
Чтобы проверить наш вывод, напишем попутно еще одну небольшую программу, которая будет читать указатель на начало списка обработчиков исключений, а потом выводить полученную информацию в окно отладки. Текст этой программы приведен ниже:
#include <afxwin.h> #include "tib.h"
// Объявляем класс приложения, его поля и методы, class CTheFirstApp : public CWinApp
{
public:
virtual BOOL Initlnstance();
};
// Инициализация класса приложения. BOOL CTheFirstApp::Initlnstance () {
PTIB pTIB ; asm
{
// Выбираем из TIB’а указатель на самого себя. MOV ЕАХ , FS:[ 18h ] MOV pTIB , ЕАХ
}
// Выдаем на отображение значение указателя на TIB. TRACE( "Pointer to the TIB = %x\n", pTIB );
// Перебираем весь список обработчиков исключений
for ( _ЕХСЕРТION_REGISTRATION_RECORD* pTempExceptionPointer
= pTIB->pvExcept; UINT( pTempExceptionPointer ) != Oxffffffff; pTempExceptionPointer = pTempExceptionPointer->pNext )
{
TRACE("Pointer to
_EXCEPTION_REGISTRATION_RECORD=%08x\n",
pTempExceptionPointer ); TRACE( "Pointer to the next record = %08x\n",
pTempExceptionPointer->pNext ); TRACE( "Pointer to the exception handler = %08x\n",
pTempExceptionPointer->pfnHandler );
}
return TRUE;
CTheFirstApp theApp;
Этот значение флага является признаком того, что после выработки исключения работа программы продолжена быть не может.
Третий аргумент – это количество передаваемых программе элементов массива, на который указывает четвертый аргумент функции. Эти аргументы определяются прикладной программой.
Функция RaiseException() и является той функцией, которая производит перебор списка исключений и передачу управления в соответствующее место кода.
Характерной особенностью SEH является то, что после возникновения исключения в стек помещаются данные о состоянии компьютера в момент исключения. В состав этих данных входят структуры типа EXCEPTION_RECORD, CONTEXT и EXCEPTION-_POINTERS.
Структура EXCEPTION_POINTERS описана в файле winnt.p следующим образом:
typedef struct _EXCEPTION_POINTERS {
PEXCEPTION_RECORD ExceptionRecord;
PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;
Первое поле этой структуры содержит указатель на структуру типа EXCEPTION_RECORD, а второе – указатель на структуру CONTEXT. Для того чтобы получить информацию об исключении, можно воспользоваться функцией
LPEXCEPTION_POINTERS GetExceptionlnformation (VOID) ;
которая возвращает указатель на структуры EXCEPTION_RECORD и CONTEXT.
Тип EXCEPTION_RECORD описан в файле winnt.h следующим образом:
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode; DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptiqnAddress; DWORD NumberParameters; UINT_PTR
Exceptionlnformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD;
Значение EXCEPTION__MAXIMUM_PARAMETERS определено как
#define EXCEPTION_MAXIMUM_PARAMETERS 15
// maximum number of // exception parameters
Первое поле структуры, ExceptionCode, содержит код исключения. Коды исключения, применяемые в Windows, приведены в табл. 5.
Второе поле, ExceptionFlags, содержит флаги исключения. Это поле может принимать два значения – 0 (программа может продолжать работу после исключения) и EXCEPTION_NONCONTINU-ABLE (программа не может продолжать работу). Значение ЕХСЕР-TION_NONCONTINUABLE описано в файле winnt.h:
#define EXCEPTION_NONCONTINUABLE 0×1
// Noncontinuable exception
Третье поле, Exception Record, является указателем на структуру ExceptionRecord, содержащую данные о предыдущем, еще не обработанном исключении, которое может возникнуть, скажем, в том случае, если текущее исключение сформировано во время обработки другого исключения.
Четвертое поле, ExceptionAddress, содержит адрес, по которому произошло исключение.
Упоминаемый в описании тип FLOATING_SAVE_AREA в свою очередь в файле winnt.h описан следующим образом: typedef FLOATING_SAVE_AREA *PFLOATING_SAVE_AREA;
Упоминаемые в описаниях значения также можно найти в файле winnt.h:
#define MAXIMUM_SUPPORTED_EXTENSION 512
#define SIZE_OF_80387_REGISTERS 80
Библиотека MFC предоставляет программисту набор классов для обработки исключительных ситуаций. Базовым для классов
обработки исключений является класс CException. Помимо этого, от него унаследованы несколько классов, предназначенных для обработки исключений разных типов. Список классов для обработки исключений приведен в табл. 7.
Для того чтобы выработать исключение, можно использовать оператор throw, но тогда нам пришлось бы самим заботиться об аргументах этого оператора. Для того чтобы облегчить выработку исключения, в MFC используются несколько функций, специально предназначенных для выработки исключений. В табл. 8 приведен список этих функций.
Рассмотрение классов исключений мы начнем с базового класса CException. В файле afx.h этот класс описан следующим образом:
#ifdef _AFXDLL
class CException : public CObject #else
class AFX_NOVTABLE CException : public CObject
#endif
{
// abstract class for dynamic type checking DECLARE_DYNAMIC(CException) public:
11 Constructors
CException(); // sets m_bAutoDelete = TRUE CException(BOOL bAutoDelete); // sets m_bAutoDelete =
// bAutoDelete
// Operations
void Delete(); // use to delete exception in // ‘catch’ block
virtual BOOL GetErrorMessage(LPTSTR IpszError,
UINT nMaxError, PUINT pnHelpContext = NULL); virtual int ReportError(UINT nType = MB_OK,
UINT nMessagelD = 0);
// Implementation (setting m_bAutoDelete to FALSE // is advanced) public:
virtual -CException();
BOOL m_bAutoDelete; #ifdef _DEBUG
void PASCAL operator delete(void* pbData); #if _MSC_VER >= 1200
void PASCAL operator delete(void* pbData,
LPCSTR IpszFileName, int nLine);
#endif
protected:
BOOL m_bReadyForDelete; #endif };
Исходный код конструктора по умолчанию этого объекта находится в файле except.cpp:
CException::CException() {
// most exceptions are deleted when not needed
m_bAutoDelete = TRUE; #ifdef _DEBUG
m_bReadyForDelete = FALSE; #endif }
Этот конструктор не делает ничего, кроме того, что присваивает полю m_bAutoDelete значение TRUE. Это означает, что в том случае, когда надобность в объекте отпадет, он будет удален автоматически.
Однако, у объекта есть и второй конструктор. Исходный код его приведен ниже:
CException::CException(BOOL bAutoDelete)
{
// for exceptions which are not auto-delete (usually)
m_bAutoDelete = bAutoDelete; #ifdef _DEBUG
m_bReadyForDelete = FALSE; #endif }
В качестве аргумента конструктору передается желаемое значение поля m_bAutoDelete. От предыдущего этот конструктор практически не отличается.
Исходный текст метода GetErrorMessage() приведен ниже:
BOOL CException::GetErrorMessage(LPTSTR IpszError,
UINT nMaxError, PUINT pnHelpContext /* = NULL */ )
{
if (pnHelpContext != NULL) *pnHelpContext = 0;
if (nMaxError != 0 && IpszError != NULL) *lpszError = Л0′ ;
return FALSE;
}
Методу в качестве первого аргумента передается указатель на буфер, в который будет записана строка, содержащая информацию об ошибке. Второй аргумент – это длина буфера. Аргумент третий – указатель на идентификатор помощи, по умолчанию равный NULL. Невооруженным взглядом видно, что метод обязательно должен быть переопределен. Дело в том, что метод просто-напросто обнуляет все переданные ему значения.
Кстати, для того чтобы перекодировать значение, возвращаемое функцией GetLastError(), в код причины, вызвавшей прерывание, можно использовать метод OsErrorToException(), исходный код которого находится в файле filex.cpp:
int PASCAL CFileException::OsErrorToException(LONG lOsErr)
{
// NT Error codes switch ((UINT)lOsErr)
{
case NO_ERROR:
return CFileException::none; case ERROR_FILE_NOT_FOUND:
return CFileException::fileNotFound; case ERROR_PATH_NOT_FOUND:
return CFileException::badPath; case ERROR_TOO_MANY_OPEN_FILES:
return CFileException::tooManyOpenFiles; case ERROR_ACCESS_DENIED:
return CFileException:: accessDenied; case ERROR_INVALID_HANDLE:
return CFileException::fileNotFound; case ERROR_BAD_FORMAT:
return CFileException::invalidFile; case ERROR_INVALID_ACCESS:
return CFileException::accessDenied; case ERROR_INVALID_DRIVE:
return CFileException::badPath; case ERROR_CURRENT_DIRECTORY:
return CFileException::removeCurrentDir; case ERROR_NOT_SAME_DEVICE:
return CFileException::badPath; case ERROR_NO_MORE_FILES:
return CFileException::fileNotFound; case ERROR_WRITE_PROTECT:
return CFileException::accessDenied; case ERROR_BAD_UNIT:
return CFileException::hardIO; case ERROR_NOT_READY:
return CFileException::hardIO;
case ERROR_BAD_COMMAND:
return CFileException::hardIO; case ERROR_CRC:
return CFileException::hardIO; case ERROR_BAD_LENGTH:
return CFileException:ibadSeek; case ERROR_SEEK:
return CFileException:ibadSeek; case ERROR_NOT_DOS_DISK:
return CFileException::invalidFile; case ERROR_SECTOR_NOT_FOUND:
return CFileException::badSeek; case ERROR_WRITE_FAULT:
return CFileException::accessDenied; case ERROR_READ_FAULT:
return CFileException::badSeek; case ERROR_SHARING_VIOLATION:
return CFileException::sharingViolation; case ERROR_LOCK_VIOLATION:
return CFileException::lockViolation; case ERROR_WRONG_DISK:
return CFileException:rbadPath; case ERROR_SHARING_BUFFER_EXCEEDED:
return CFileException::tooManyOpenFiles; case ERROR_HANDLE_EOF:
return CFileException::endOfFile; case ERROR_HANDLE_DISK_FULL:
return CFileException::diskFull; case ERROR_DUP_NAME:
return CFileException::badPath; case ERROR_BAD_NETPATH:
return CFileException::badPath; case ERROR_NETWORK_BUSY:
return CFileException::accessDenied; case ERROR_DEV_NOT_EXIST:
return CFileException::badPath; case ERROR_ADAP_HDW_ERR:
return CFileException::hardIO; case ERROR_BAD_NET_RESP:
return CFileException::accessDenied; case ERROR_UNEXP_NET_ERR:
return CFileException::hardIO; case ERROR BAD REM ADAP:
return CFileException::invalidFile; case ERROR_NO_SPOOL_SPACE:
return CFileException::directoryFull; case ERROR_NETNAME_DELETED:
return CFileException: -.accessDenied; case ERROR_NETWORK_ACCESS_DENIED:
return CFileException::accessDenied; case ERROR_BAD_DEV_TYPE:
return CFileException: : invalidFile;*1 case ERROR_BAD_NET_NAME:
return CFileException::badPath; case ERROR_TOO_MANY_NAMES:
return CFileException::tooManyOpenFiles; case ERROR_SHARING_PAUSED:
return CFileException::badPath; case ERROR_REQ_NOT_ACCEP:
return CFileException::accessDenied; case ERROR_FILE_EXISTS:
return CFileException::accessDenied; case ERROR_CANNOT_MAKE:
return CFileException::accessDenied; case ERROR_ALREADY_ASSIGNED:
return CFileException::badPath; case ERROR_INVALID_PASSWORD:
return CFileException::accessDenied; case ERROR_NET_WRITE_FAULT:
return CFileException::hardIO; case ERROR_DISK_CHANGE:
return CFileException::fileNotFound; case ERROR_DRIVE_LOCKED:
return CFileException::lockViolation; case ERROR_BUFFER_OVERFLOW:
return CFileException::badPath; case ERROR_DISK_FULL:
return CFileException:rdiskFull; case ERROR_NO_MORE_SEARCH_HANDLES:
return CFileException::tooManyOpenFiles; case ERROR_INVALID_TARGET_HANDLE:
return CFileException::invalidFile; case ERROR_INVALID_CATEGORY:
return CFileException::hardIO; case ERROR_INVALID_NAME:
return CFileException::badPath;
case ERROR_INVALID_LEVEL:
return CFileException:rbadPath; case ERROR_NO_VOLUME_LABEL:
return CFileException::badPath; case ERROR_NEGATIVE_SEEK:
return CFileException::badSeek; case ERROR_SEEK_ON_DEVICE:
return CFileException::badSeek; case ERROR_DIR_NOT_ROOT:
return CFileException::badPath; case ERROR_DIR_NOT_EMPTY:
return CFileException::removeCurrentDir; case ERROR_LABEL_TOO_LONG:
return CFileException::badPath; case ERROR_BAD_PATHNAME:
return CFileException::badPath; case ERROR_LOCK_FAILED:
return CFileException::lockViolation; case ERROR_BUSY:
return CFileException::accessDenied; case ERROR_INVALID_ORDINAL:
return CFileException::invalidFile; case ERROR_ALREADY_EXISTS:
return CFileException::accessDenied; case ERROR_INVALID_EXE_SIGNATURE:
return CFileException::invalidFile; case ERROR_BAD_EXE_FORMAT:
return CFileException::invalidFile; case ERROR_FILENAME_EXCED_RANGE:
return CFileException::badPath; case ERROR_META_EXPANSION_TOO_LONG:
return CFileException::badPath; case ERROR_DIRECTORY:
return CFileException::badPath; case ERROR_OPERATION_ABORTED:
return CFileException::hardIO; case ERROR_IO_INCOMPLETE:
return CFileException::hardIO; case ERROR_IO_PENDING:
return CFileException::hardIO; case ERROR_SWAPERROR:
return CFileException::accessDenied; default:
return CFileException::generic;
}
}
Первым из группы методов, позволяющих перемещать указатель файла, является метод Seek. Его исходный код находится в файле filecore.cpp:
LONG CFile::Seek(LONG lOff, UINT nFrom) {
ASSERT_VALID(this);
ASSERT(m_hFile != (UINT)hFileNull); ASSERT(nFrom == begin ||
nFrom == end ||
nFrom == current); ASSERT(begin == FILE_BEGIN &&
end == FILE_END &&
current == FILE CURRENT);
DWORD dwNew = ::SetFilePointer((HANDLE)m_hFile,
lOff, NULL,
(DWORD)nFrom);
if (dwNew == (DWORD)-1)
CFileException::ThrowOsError((LONG)::GetLastError());
return dwNew;
}
