Программирование на языке 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;

}



После этого времена создания, изменения и последнего доступа к файлу, полученные при помощи метода 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;

};



03.02.2010

Аналогично предыдущему работает метод 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;

}



03.02.2010

Очередным нашим шагом будет перебор всех элементов списка. Перебрать все элементы списка, начиная с текущего и заканчивая конечным, можно при помощи метода 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;

}