Издание четвертое windows ® для профессионалов создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows



Pdf просмотр
страница63/68
Дата28.11.2016
Размер3.57 Mb.
Просмотров12454
Скачиваний0
1   ...   60   61   62   63   64   65   66   67   68
Рис. 25-1.
продолжение
// может вызываться для более "тонкой" обработки если передать память не удалось ExceptionFilter(PEXCEPTION_POINTERS pep,
BOOL fRetryUntilSuccessful = FALSE);
protected:
// замещается для более "тонкой" обработки исключения связанного с нарушением доступа virtual LONG OnAccessViolation(PVOID pvAddrTouched, BOOL fAttemptedRead,
PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful);
private:
static CVMArray* sm_pHead;
// адрес первого объекта m_pNext;
// адрес следующего объекта m_pArray;
// указатель на регион, зарезервированный под массив (разреженную матрицу m_cbReserve;
// размер зарезервированного региона (в байтах адрес предыдущего фильтра необработанных исключений static PTOP_LEVEL_EXCEPTION_FILTER sm_pfnUnhandledExceptionFilterPrev;
// наш глобальный фильтр необработанных исключений для экземпляров этого класса static LONG WINAPI UnhandledExceptionFilter(PEXCEPTION_POINTERS pep);
};
///////////////////////////////////////////////////////////////////////////////
// заголовок связанного списка объектов template
CVMArray* CVMArray::sm_pHead = NULL;
// адрес предыдущего фильтра необработанных исключений template
PTOP_LEVEL_EXCEPTION_FILTER CVMArray::sm_pfnUnhandledExceptionFilterPrev;
///////////////////////////////////////////////////////////////////////////////
template
CVMArray::CVMArray(DWORD dwReserveElements) {
if (sm_pHead == NULL) {
// устанавливаем наш глобальный фильтр необработанных исключений при создании первого экземпляра класса sm_pfnUnhandledExceptionFilterPrev =
SetUnhandledExceptionFilter(UnhandledExceptionFilter);
}
m_pNext = sm_pHead; // следующий узел был вверху списка sm_pHead = this;
// сейчас вверху списка находится этот узел
см. след. стр.

640
Ч АС Т Ь V
СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ
Рис. 25-1.
продолжение
m_cbReserve = sizeof(TYPE) * dwReserveElements;
// резервируем регион для всего массива m_pArray = (TYPE*) VirtualAlloc(NULL, m_cbReserve,
MEM_RESERVE | MEM_TOP_DOWN, PAGE_READWRITE);
chASSERT(m_pArray != NULL);
}
///////////////////////////////////////////////////////////////////////////////
template
CVMArray::
CVMArray() {
// освобождаем регион массива (возвращаем всю переданную ему память, 0, MEM_RELEASE);
// удаляем этот объект из связанного списка p = sm_pHead;
if (p == this) { // удаляем верхний узел sm_pHead = p >m_pNext;
} else {
BOOL fFound = FALSE;
// проходим по списку сверху и модифицируем указатели for (; !fFound && (p >m_pNext != NULL); p = p >m_pNext) {
if (p >m_pNext == this) {
// узел, указывающий на нас, должен указывать наследующий узел p >m_pNext = p >m_pNext >m_pNext;
break;
}
}
chASSERT(fFound);
}
}
///////////////////////////////////////////////////////////////////////////////
// предлагаемый по умолчанию механизм обработки нарушений доступа возникающих при попытках передачи физической памяти template
LONG CVMArray::OnAccessViolation(PVOID pvAddrTouched,
BOOL fAttemptedRead, PEXCEPTION_POINTERS pep, BOOL fRetryUntilSuccessful) {
BOOL fCommittedStorage = FALSE; // считаем, что передать память не удалось do {
// пытаемся передать физическую память fCommittedStorage = (NULL != VirtualAlloc(pvAddrTouched,
sizeof(TYPE), MEM_COMMIT, PAGE_READWRITE));

641
Г ЛАВА 25
Необработанные исключения и исключения C++
Рис. 25-1.
продолжение
// если передать память не удается, предлагаем пользователю освободить память if (!fCommittedStorage && fRetryUntilSuccessful) {
MessageBox(NULL,
TEXT("Please close some other applications and Press OK."),
TEXT("Insufficient Memory Available"), MB_ICONWARNING | MB_OK);
}
} while (!fCommittedStorage && fRetryUntilSuccessful);
// если память передана, пытаемся возобновить выполнение программы вином случае активизируем обработчик return(fCommittedStorage
? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_EXECUTE_HANDLER);
}
///////////////////////////////////////////////////////////////////////////////
// фильтр, связываемый с отдельным объектом класса CVMArray template
LONG CVMArray::ExceptionFilter(PEXCEPTION_POINTERS pep,
BOOL fRetryUntilSuccessful) {
// по умолчанию пытаемся использовать другой фильтр (самый безопасный путь lDisposition = EXCEPTION_CONTINUE_SEARCH;
// мы обрабатываем только нарушение доступа if (pep >ExceptionRecord >ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
return(lDisposition);
// получаем адрес и информацию о попытке чтения/записи
PVOID pvAddrTouched = (PVOID) pep >ExceptionRecord >ExceptionInformation[1];
BOOL fAttemptedRead = (pep >ExceptionRecord >ExceptionInformation[0] == 0);
// попадает ли полученный адрес в регион зарезервированный для этого VMArray?
if ((m_pArray <= pvAddrTouched) &&
(pvAddrTouched < ((PBYTE) m_pArray + m_cbReserve))) {
// была попытка записи в наш массив, пытаемся исправить ситуацию lDisposition = OnAccessViolation(pvAddrTouched, fAttemptedRead,
pep, fRetryUntilSuccessful);
}
return(lDisposition);
}
///////////////////////////////////////////////////////////////////////////////
// фильтр, связываемый со всеми объектами класса CVMArray template
LONG WINAPI CVMArray::UnhandledExceptionFilter(PEXCEPTION_POINTERS pep) {
см. след. стр.

642
Ч АС Т Ь V
СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ
Рис. 25-1.
продолжение
// по умолчанию пытаемся использовать другой фильтр (самый безопасный путь lDisposition = EXCEPTION_CONTINUE_SEARCH;
// мы обрабатываем только нарушение доступа if (pep >ExceptionRecord >ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// проходим все узлы связанного списка for (CVMArray* p = sm_pHead; p != NULL; p = p >m_pNext) {
// Интересуемся, может ли данный узел обработать исключение Примечание исключение НАДО обработать, иначе процесс будет закрыт = p >ExceptionFilter(pep, TRUE);
// если подходящий узел найден ион обработал исключение выходим из цикла if (lDisposition != EXCEPTION_CONTINUE_SEARCH)
break;
}
}
// если ни один узел не смог устранить проблему пытаемся использовать предыдущий фильтр исключений if (lDisposition == EXCEPTION_CONTINUE_SEARCH)
lDisposition = sm_pfnUnhandledExceptionFilterPrev(pep);
return(lDisposition);
}
//////////////////////////////// Конец файла //////////////////////////////////
Исключения C++ и структурные исключения
Разработчики часто спрашивают меня, что лучше использовать SEH или исключения. Ответ на этот вопрос Вы найдете здесь.
Для начала позвольте напомнить, что SEH — механизм операционной системы,
доступный в любом языке программирования, а исключения C++ поддерживаются только в C++. Создавая приложение на C++, Вы должны использовать средства именно этого языка, а не SEH. Причина в том, что исключения C++ — часть самого языка и его компилятор автоматически создает код, который вызывает деструкторы объектов и тем самым обеспечивает корректную очистку ресурсов.
Однако Вы должны иметь ввиду, что компилятор Microsoft Visual C++ реализует обработку исключений C++ на основе SEH операционной системы. Например, когда
Вы создаете C++ блок
try, компилятор генерирует SEH блок __try. C++ блок catch становится фильтром исключений, а код блока
catch — кодом SEH блока __except.
По сути, обрабатывая C++ оператор
throw, компилятор генерирует вызов Windows функции
RaiseException, и значение переменной, указанной в throw, передается этой функции как дополнительный аргумент.
Сказанное мной поясняет фрагмент кода, показанный ниже. Функция слева использует средства обработки исключений C++, а функция справа демонстрирует, как компилятор C++ создает соответствующие им SEH эквиваленты.

643
Г ЛАВА 25
Необработанные исключения и исключения C++
void ChunkyFunky() {
void ChunkyFunky() {
try {
__try {
// тело блока try
// тело блока try
M
M
throw 5;
RaiseException(Code=0xE06D7363,
Flag=EXCEPTION_NONCONTINUABLE,Args=5);
}
}
catch (int x) {
__except ((ArgType == Integer) ?
EXCEPTION_EXECUTE_HANDLER :
EXCEPTION_CONTINUE_SEARCH) {
// тело блока catch
// тело блока Обратите внимание на несколько интересных особенностей этого кода. Во первых вызывается с кодом исключения 0xE06D7363. Это код программного исключения, выбранный разработчиками Visual C++ на случай выталкивания) исключений C++. Вы можете сами в этом убедиться, открыв диалоговое окно Exceptions отладчика и прокрутив его список до конца, как наследующей ил люстрации.
Заметьте также, что при выталкивании исключения C++ всегда используется флаг. Исключения C++ не разрешают возобновлять выполнение программы, и возврат EXCEPTION_CONTINUE_EXECUTION фильтром, диагно стирующим исключения C++, был бы ошибкой. Если Вы посмотрите на фильтр
__except
в функции справа, то увидите, что он возвращает только EXCEPTION_EXECUTE_HAND
LER или Остальные аргументы
RaiseException используются механизмом, который фактически выталкивает (throw) указанную переменную. Точный механизм того, как данные из переменной передаются
RaiseException, не задокументирован, но догадаться о его реализации в компиляторе не так уж трудно.
И последнее, о чем хотелось бы сказать. Назначение фильтра
__except — сравнивать тип
throw переменных с типом переменной, используемой в C++ операторе catch.
Если их типы совпадают, фильтр возвращает EXCEPTION_EXECUTE_HANDLER, вызывая выполнение операторов в блоке
catch (__except). А если они не совпадают, фильтр возвращает EXCEPTION_CONTINUE_SEARCH для проверки вышестоящих по дереву вызовов фильтров
catch.

644
Ч АС Т Ь V
СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ
Так как исключения C++ реализуются через SEH, оба эти механизма можно использовать водной программе. Например, я предпочитаю передавать физическую память при исключениях, вызываемых нарушениями доступа. Хотя C++ вообще не поддерживает этот тип обработки исключений (с возобновлением выполнения, он позволяет применять SEH в тех местах программы, где это нужно, и Ваш фильтр
__except может возвращать EXCEPTION_CONTINUE_EXECU
TION. Ну а в остальных частях исходного кода, где возобновление выполнения после обработки исключения не требуется, я пользуюсь механизмом обработки исключений, предлагаемым C++.
Перехват структурных исключений в C++
Обычно механизм обработки исключений вне позволяет приложению восстановиться после таких серьезных исключений, как нарушение доступа или деление на нуль. Однако Microsoft добавила поддержку соответствующей функциональности в свой компилятор. Так, следующий код предотвратит аварийное завершение процесса main() {
try {
* (PBYTE) 0 = 0; // нарушение доступа (...) {
// этот код обрабатывает исключения, связанные с нарушением доступа процесс завершается корректно
}
И это прекрасно, так как приложение может корректно справляться с серьезными исключениями. Но было бы еще лучше, если бы блок
catch как то различал коды исключений — чтобы мы могли писать, например, такой исходный код Functastic() {
try {
* (PBYTE) 0 = 0;
// нарушение доступа int x = 0;
x = 5 / x;
// деление на нуль (StructuredException) {
switch (StructuredExceptionCode) {
case EXCEPTION_ACCESS_VIOLATION:
// здесь обрабатывается нарушение доступа break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
// здесь обрабатывается деление на нуль break;
default:
// другие исключения мы не обрабатываем throw;
// может, какой нибудь другой блок catch
// обработает это исключение

645
Г ЛАВА 25
Необработанные исключения и исключения C++
break;
// никогда не выполняется
}
}
}
Так вот, хочу Вас порадовать. В Visual C++ теперь возможно и такое. От Вас потребуется создать C++ класс, используемый специально для идентификации структурных исключений. Например
// для доступа к _set_se_translator
M
class CSE {
public:
// вызовите эту функцию для каждого потока static void MapSEtoCE() { _set_se_translator(TranslateSEtoCE); }
operator DWORD() { return(m_er.ExceptionCode); }
private:
CSE(PEXCEPTION_POINTERS pep) {
m_er
= *pep >ExceptionRecord;
m_context = *pep >ContextRecord;
}
static void _cdecl TranslateSEtoCE(UINT dwEC,
PEXCEPTION_POINTERS pep) {
throw CSE(pep);
}
private:
EXCEPTION_RECORD m_er;
// машинно независимая информация об исключении // машинно зависимая информация об исключении
};
Внутри входных функций потоков вызывайте статическую функцию член
Map
SEtoCE. В свою очередь она обращается к библиотечной C функции _set_se_translator,
передавая ей адрес функции
TranslateSEtoCE класса CSE. Вызов _set_se_translator сообщает, что при возбуждении структурных исключений Вы хотите вызывать
Trans
lateSEtoCE. Эта функция вызывает конструктор CSE объекта и инициализирует два элемента данных машинно зависимой и машинно независимой информацией об исключении. Созданный таким образом CSE объект может быть вытолкнут также, как и любая другая переменная. И теперь Ваш C++ код способен обрабатывать структурные исключения, захватывая (catching) переменную этого типа.
Вот пример захвата такого C++ объекта Functastic() {
CSE::MapSEtoCE(); // должна быть вызвана до возникновения исключений try {
* (PBYTE) 0 = 0;
// нарушение доступа int x = 0;
x = 5 / x;
// деление на нуль (CSE se) {
switch (se) { // вызывает функцию член оператора DWORD()
см. след. стр.

646
Ч АС Т Ь V
СТРУКТУРНАЯ ОБРАБОТКА ИСКЛЮЧЕНИЙ EXCEPTION_ACCESS_VIOLATION:
// здесь обрабатывается исключение, вызванное нарушением доступа break;
case EXCEPTION_INT_DIVIDE_BY_ZERO:
// здесь обрабатывается исключение, вызванное делением на нуль break;
default:
// другие исключения мы не обрабатываем throw;
// может, какой нибудь другой блок catch
// обработает это исключение break;
// никогда не выполняется
}
}
}

Ч АС Т Ь V I
ОПЕРАЦИИ С ОКНАМИ

648
Г ЛАВА 6
Оконные сообщения
В
этой главе я расскажу, как работает подсистема передачи сообщений в Windows применительно к приложениям с графическим пользовательским интерфейсом. Разрабатывая подсистему управления окнами в Windows 2000 и Windows 98, Microsoft преследовала две основные цели:
í
обратная совместимость с 16 разрядной Windows, облегчающая перенос существующих разрядных приложений;
í
отказоустойчивость подсистемы управления окнами, чтобы ни один поток не мог нарушить работу других потоков в системе.
К сожалению, эти цели прямо противоречат друг другу. В 16 разрядной Windows передача сообщения в окно всегда осуществляется синхронно отправитель не может продолжить работу, пока окно не обработает полученное сообщение. Обычно таки нужно. Но, если на обработку сообщения потребуется длительное время или если окно
«зависнет», выполнение отправителя просто прекратится. А значит, такая операционная система не вправе претендовать на устойчивость к сбоям.
Это противоречие было серьезным вызовом для команды разработчиков из Micro soft. В итоге было выбрано компромиссное решение, отвечающее двум вышеупомянутым целям. Помните о них, читая эту главу, и Вы поймете, почему Microsoft сделала именно такой выбор.
Для начала рассмотрим некоторые базовые принципы. Один процесс в Windows может создать до 10 000 User объектов различных типов — значков, курсоров, оконных классов, меню, таблиц клавиш акселераторов и т. д. Когда поток из какого либо процесса вызывает функцию, создающую один из этих объектов, последний переходит во владение процесса. Поэтому, если процесс завершается, не уничтожив данный объект явным образом, операционная система делает это за него. Однако два User объекта (окна и ловушки) принадлежат только создавшему их потоку. И вновь, если поток создает окно или устанавливает ловушку, а потом завершается, операционная система автоматически уничтожает окно или удаляет ловушку.
Этот принцип принадлежности окон и ловушек создавшему их потоку оказывает существенное влияние на механизм функционирования окон поток, создавший окно,
должен обрабатывать все его сообщения. Поясню данный принцип на примере. Допустим, поток создал окно, а затем прекратил работу. Тогда его окно уже не получит сообщение WM_DESTROY или WM_NCDESTROY, потому что поток уже завершился и обрабатывать сообщения, посылаемые этому окну, больше некому.
Это также означает, что каждому потоку, создавшему хотя бы одно окно, система выделяет очередь сообщений, используемую для их диспетчеризации. Чтобы окно в конечном счете получило эти сообщения, поток
должен иметь собственный цикл выборки сообщений. В этой главе мы детально рассмотрим, что представляют собой

649
Г ЛАВА 26
Оконные сообщения очереди сообщений потоков. В частности, я расскажу, как сообщения помещаются в эту очередь и как они извлекаются из нее, а потом обрабатываются.
Очередь сообщений потока
Как я уже говорил, одна из главных целей Windows — предоставить всем приложениям отказоустойчивую среду. Для этого любой поток должен выполняться в такой среде, где он может считать себя единственным. Точнее, у каждого потока должны быть очереди сообщений, полностью независимые от других потоков. Кроме того, для каждого потока нужно смоделировать среду, позволяющую ему самостоятельно управлять фокусом ввода с клавиатуры, активизировать окна, захватывать мышь и т. д.
Создавая какой либо поток, система предполагает, что он не будет иметь отношения к поддержке пользовательского интерфейса. Это позволяет уменьшить объем выделяемых ему системных ресурсов. Но, как только поток обратится к той или иной функции (например, для проверки очереди сообщений или создания окна, система автоматически выделит ему дополнительные ресурсы, необходимые для выполнения задач, связанных с пользовательским интерфейсом. А если конкретнее, то система создает структуру THREADINFO и сопоставляет ее с этим потоком.
Элементы этой структуры используются, чтобы обмануть поток — заставить его считать, будто он выполняется в среде, принадлежащей только ему. THREADINFO это внутренняя (недокументированная) структура, идентифицирующая очередь асинхронных сообщений потока (posted message queue), очередь синхронных сообщений потока (sent message queue), очередь ответных сообщений (reply message queue), очередь виртуального ввода (virtualized input queue) и флаги пробуждения (wake она также включает ряд других переменных членов, характеризующих локальное состояние ввода для данного потока. На рис. 26 1 показаны структуры сопоставленные стремя потоками.
Структура THREADINFO — фундамент всей подсистемы передачи сообщений читая следующие разделы, время от времени посматривайте на эту иллюстрацию.
Посылка асинхронных сообщений в очередь потока
Когда с потоком связывается структура THREADINFO, он получает свой набор очередей сообщений. Если процесс создает три потока и все они вызывают функцию
Create
Window, то и наборов очередей сообщений будет тоже три. Сообщения ставятся в очередь асинхронных сообщений вызовом функции
PostMessage:
BOOL PostMessage(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM При вызове этой функции система определяет, каким потоком создано окно, идентифицируемое параметром
hwnd. Далее система выделяет блок памяти, сохраняет в нем параметры сообщения и записывает этот блок в очередь асинхронных сообщений данного потока. Кроме того, функция устанавливает флаг пробуждения QS_POST
MESSAGE (о нем — чуть позже. Возврат из
PostMessage происходит сразу после того,
как сообщение поставлено в очередь, поэтому вызывающий поток остается в неведении, обработано ли оно процедурой соответствующего окна. На самом деле вполне вероятно, что окно даже не получит это сообщение. Такое возможно, если поток,
создавший это окно, завершится до того, как обработает все сообщения из своей очереди.

650
Ч АС Т Ь VI
ОПЕРАЦИИ С ОКНАМИ
Сообщение можно поставить в очередь асинхронных сообщений потока и вызовом Какой поток создал окно, можно определить с помощью
GetWindowThreadPro
cessId:
Асинхронное сообщение
Асинхронное сообщение
Сообщение ввода
Сообщение ввода
Синхронное сообщение
Синхронное сообщение
Ответное сообщение
Ответное сообщение
Сообщение ввода
THREADINFO
Указатель очереди виртуального ввода Переменные, отражающие локальное состояние ввода
Указатель очереди асинхронных сообщений
Указатель очереди ответных сообщений
Указатель очереди синхронных сообщений
Флаги пробуждения
Асинхронное сообщение
Асинхронное сообщение
Сообщение ввода
Сообщение ввода
Синхронное сообщение
Синхронное сообщение
Ответное сообщение
Ответное сообщение
Сообщение ввода
THREADINFO
Указатель очереди виртуального ввода Переменные, отражающие локальное состояние ввода
Указатель очереди асинхронных сообщений
Указатель очереди ответных сообщений
Указатель очереди синхронных сообщений
Флаги пробуждения
Асинхронное сообщение
Асинхронное сообщение
Сообщение ввода
Сообщение ввода
Синхронное сообщение
Синхронное сообщение
Ответное сообщение
Ответное сообщение
Сообщение ввода
THREADINFO
Указатель очереди виртуального ввода Переменные, отражающие локальное состояние ввода
Указатель очереди асинхронных сообщений
Указатель очереди ответных сообщений
Указатель очереди синхронных сообщений
Флаги пробуждения
ПРОЦЕСС
Рис. 26-1.
Три потока и соответствующие им структуры THREADINFO

651
Г ЛАВА 26
Оконные сообщения GetWindowThreadProcessId(
HWND hwnd,
PDWORD Она возвращает уникальный общесистемный идентификатор потока, который создал окно, определяемое параметром
hwnd. Передав адрес переменной типа DWORD в параметре
pdwProcessId, можно получить и уникальный общесистемный идентификатор процесса, которому принадлежит этот поток.
Но обычно такой идентификатор ненужен, и мы просто передаем Нужный поток идентифицируется первым параметром,
dwThreadId. Когда сообщение помещено в очередь, элемент
hwnd структуры MSG устанавливается как Применяется эта функция, когда приложение выполняет какую то особую обработку в основном цикле выборки сообщений потока, — в этом случае он пишется так, чтобы после выборки сообщения функцией
GetMessage (или PeekMessage) код в цикле сравнивал
hwnd си, выполняя эту самую особую обработку, мог проверить значение элемента
msg структуры MSG. Если поток определил, что сообщение не адресовано какому либо окну,
DispatchMessage не вызывается, и цикл переходит к выборке следующего сообщения.
Как и
PostMessage, функция PostThreadMessage возвращает управление сразу после того, как сообщение поставлено в очередь потока. И вновь вызывающий поток остается в неведении о дальнейшей судьбе сообщения.
И, наконец, еще одна функция, позволяющая поместить сообщение в очередь асинхронных сообщений потока PostQuitMessage(int Она вызывается для того, чтобы завершить цикл выборки сообщений потока. Ее вызов аналогичен вызову, WM_QUIT, nExitCode, Нов действительности
PostQuitMessage не помещает сообщение нив одну из очередей структуры THREADINFO. Эта функция просто устанавливает флаг пробуждения (о нем я тоже расскажу чуть позже) и элемент
nExitCode структуры THREAD
INFO. Так как эти операции не могут вызвать ошибку, функция
PostQuitMessage не возвращает никаких значений (VOID).



Поделитесь с Вашими друзьями:
1   ...   60   61   62   63   64   65   66   67   68


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал