Чтение онлайн

на главную - закладки

Жанры

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

И здесь начинается самое интересное.

CMRealease
просто вызывает
Free
, удаляя тем самым объект, т.е. объект удаляется из метода самого объекта, что делать запрещено. Таким образом, после выполнения
Free
управление вновь получает
CMRealease
. Из него управление возвращается в
Dispatch
, оттуда — в
WndProc
, затем — в
MainWndProc
, далее — в оконную процедуру, и только после этого управление получает код, который никак не связан с конкретным экземпляром компонента. Мы видим, что после обработки
CM_RELEASE
и удаления объекта его методы продолжают работать. Методы уже не существующего объекта!

В принципе, методы несуществующего объекта могут вполне нормально завершить свою работу, если не будут обращаться к его полям или иным образом использовать указатель

Self
, который к этому моменту будет уже недействительным. Но стоило нам только вставить в один из этих методов код, задействующий поле объекта, как возникла ошибка. 

В данном примере получается следующее: сначала

CM_RELEASE
передаётся стандартному обработчику, который вызывает деструктор. При работе деструктора финализируются все поля объекта, для которых это требуется. В нашем случае это означает, что в поле
S
заносится
nil
(освобождения памяти при этом не происходит, потому что
S
до этого ссылалась на литерал, хранящийся в кодовом сегменте, а не в динамической памяти). После этого начинает работать наш код, который пытается изменить второй символ в строке. Программа пытается обратиться к ячейке с адресом
nil
+ 1, т.е. 00000001, что и приводит к ошибке Access violation.

Обращение в аналогичной ситуации к нефинализируемым полям (целым, вещественным, логическим и т. п.) обычно не приводит к исключению. Это связано с тем, что менеджер памяти Delphi обычно не сразу отдаёт системе ту память, которую освобождает объект, поэтому программа, с точки зрения системы, имеет полное право ею пользоваться. Поля объекта не очищаются, и его образ продолжает храниться в памяти, просто менеджер памяти помечает эту область как неиспользуемую и может в любой момент выделить ее для хранения другого объекта. Это создает иллюзию того, что объект продолжает существовать и позволяет работать с уже несуществующим объектом. Но это все равно некорректно, потому что любое перераспределение памяти в данной ситуации может привести к непонятной ошибке.

Посмотрим. что будет, если строку

S[1] := 'x'
заменить на
S := IntToStr(Msg.Msg)
. Как мы уже выяснили, после уничтожения объекта в той области памяти, где хранилось значение
S
, будет
nil
. Указатель на вновь созданную строку будет помещен в эту область памяти. Но к ней уже не будет применяться финализация, т.к. менеджер памяти будет считать эту область памяти финализированной. Произойдет утечка памяти.

Отметим, что для вновь созданной строки память может быть выделена таким образом, что она наложится на те ячейки, в которых хранились значения полей уничтоженной формы, в том числе значение

S
. В этом случае попытка обратиться к такому полю приведет к непредсказуемым результатам.

Аналогичная проблема может появляться не только при перекрытии

WndProc
, а вообще при любом способе внедрения своего кода в цепочку обработки так, чтобы он выполнялся после
CMRelease
.

Совершенно непонятно, почему разработчики VCL реализовали такой заведомо некорректный механизм работы

Release
. Чтобы избежать всех описанных проблем, достаточно было
бы просто посылать
CM_RELEASE
не самой форме, а окну, создаваемому объектом
Application
, а указатель на освобождаемую форму передавать через параметры этого сообщения. Тогда деструктор формы вызывался бы из метода объекта
Application
, и никаких проблем не было бы. 

Эта проблема обнаружена во всех версиях Delphi с 3-й по 2007-ю (в других версиях не проверялась). Самый простой способ ее преодоления — отмена опасных действий, если получено сообщение

CM_RELEASE
. Например, в описанном случае безопасным будет следующий код (листинг 3.53).

Листинг 3.53. Безопасный вариант метода
WndProc

procedure TForm2.WndProc(var Message: TMessage);

begin

 inherited;

 if Msg.Msg <> CM_RELEASE then s[2] := 'x';

end;

Другой способ заключается в том. чтобы перенести обработку

CM_RELEASE
в объект
Application
с помощью его события
OnMessage
. Проблема заключается лишь в том, что адрес удаляемой формы будет неизвестен, но его легко найти по дескриптору окна. Например, в данном случае можно положить на первую форму
TApplicationEvents
и в его обработчике
OnMessage
написать следующий код (листинг 3.54; в примере CloseAV этот код закомментирован).

Листинг 3.54. Обработка сообщения
CM_RELEASE
объектом
Application

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);

var

 I: Integer;

begin

 if Msg.Message = CM_RELEASE then

for I := 0 to Screen.FormCount - 1 do

if Screen. Forms[I].Handle = Msg.hwnd then

begin

Screen.Forms[I].Free;

Handled := True;

Exit;

end;

end;

Событие

OnMessage
позволяет перехватить сообщения до того, как они будут диспетчеризованы окну-адресату, соответственно, форма будет уничтожена раньше, чем начнет обрабатывать
CM_RELEASE

3.4.4. Подмена имени оконного класса, возвращаемого функцией GetClassInfo

Создадим новый проект в Delphi, поместим на форму кнопку и метку и создадим следующий обработчик нажатия кнопки (листинг 3.55, пример ClassName на компакт-диске).

Листинг 3.55. Подмена имени оконного класса

procedure TForm1.Button1Click(Sender: TObject);

var

 CI: TWndClass;

 S: string;

 procedure DoGetClassInfo;

 begin

GetClassInfo(hInstance, PChar('TForm' + IntToStr(1)), CI);

Поделиться:
Популярные книги

Релокант. По следам Ушедшего

Ascold Flow
3. Релокант в другой мир
Фантастика:
фэнтези
попаданцы
рпг
5.00
рейтинг книги
Релокант. По следам Ушедшего

Маверик

Астахов Евгений Евгеньевич
4. Сопряжение
Фантастика:
боевая фантастика
постапокалипсис
рпг
5.00
рейтинг книги
Маверик

Измена. Он все еще любит!

Скай Рин
Любовные романы:
современные любовные романы
6.00
рейтинг книги
Измена. Он все еще любит!

Адепт: Обучение. Каникулы [СИ]

Бубела Олег Николаевич
6. Совсем не герой
Фантастика:
фэнтези
попаданцы
9.15
рейтинг книги
Адепт: Обучение. Каникулы [СИ]

Мама из другого мира. Делу - время, забавам - час

Рыжая Ехидна
2. Королевский приют имени графа Тадеуса Оберона
Фантастика:
фэнтези
8.83
рейтинг книги
Мама из другого мира. Делу - время, забавам - час

Низший - Инфериор. Компиляция. Книги 1-19

Михайлов Дем Алексеевич
Фантастика 2023. Компиляция
Фантастика:
боевая фантастика
5.00
рейтинг книги
Низший - Инфериор. Компиляция. Книги 1-19

На границе империй. Том 7. Часть 5

INDIGO
11. Фортуна дама переменчивая
Фантастика:
боевая фантастика
космическая фантастика
попаданцы
5.00
рейтинг книги
На границе империй. Том 7. Часть 5

Кодекс Охотника. Книга XXII

Винокуров Юрий
22. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
5.00
рейтинг книги
Кодекс Охотника. Книга XXII

Темный Лекарь 3

Токсик Саша
3. Темный Лекарь
Фантастика:
фэнтези
аниме
5.00
рейтинг книги
Темный Лекарь 3

Я тебя не предавал

Бигси Анна
2. Ворон
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Я тебя не предавал

Измена. Право на счастье

Вирго Софи
1. Чем закончится измена
Любовные романы:
современные любовные романы
5.00
рейтинг книги
Измена. Право на счастье

Не грози Дубровскому! Том 11

Панарин Антон
11. РОС: Не грози Дубровскому!
Фантастика:
фэнтези
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Не грози Дубровскому! Том 11

Виконт. Книга 2. Обретение силы

Юллем Евгений
2. Псевдоним `Испанец`
Фантастика:
боевая фантастика
попаданцы
рпг
7.10
рейтинг книги
Виконт. Книга 2. Обретение силы

Авиатор: назад в СССР 14

Дорин Михаил
14. Покоряя небо
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Авиатор: назад в СССР 14