Обращение в аналогичной ситуации к нефинализируемым полям (целым, вещественным, логическим и т. п.) обычно не приводит к исключению. Это связано с тем, что менеджер памяти 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);
end;
begin
DoGetClassInfo;
S := 'abcde' + IntToStr(2);
Label1.Caption := CI.lpszClassName;
end;
Что будет выведено на экран в результате выполнения этого кода? Так как класс называется "TForm1", логично предположить, что именно это и будет выведено. На самом деле мы увидим abcde2— ту строку, которая присвоена переменной S
.
Разберемся, как значение переменной S
оказывается в поле CI.lpszClassName
. Согласно MSDN поле lpszClassName
имеет тип LPCTSTR(PChar)
, и в него функция GetClassInfo
заносит указатель на строку, содержащую имя оконного класса. Но нигде не сказано, в какой области памяти должна располагаться эта строка.
Читать дальше
Конец ознакомительного отрывка
Купить книгу