А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
В этой проблеме виновата VCL, которая зачем-то назначает компоненту TUpDown
стиль csCaptureMouse
. Данный компонент реализуется не средствами VCL, — это стандартное окно системного класса UPDOWN_CLASS
, а компонент TUpDown
— это только оболочка для него. Поэтому все необходимые перехваты мыши выполняются самой системой. VCL нет нужды в это вмешиваться. Чтобы избавиться от проблемы, нужно убрать csCaptureMouse
из списка стилей компонента. Делается это так:
UpDown1.ControlStyle := UpDown1.ControlStyle - [csCaptureMouse];
Этот код достаточно выполнить один раз (например, в обработчике события OnCreate
формы), и проблемы с зацикливанием исчезнут (в примере UpDownDlg эта строка закомментирована).
Отметим, что в Windows предусмотрено специальное сообщение — WM_CANCELMODE
, — посылаемое при открытии диалогового окна тому окну, которое захватило мышь, чтобы оно ее освободило. Один из способов решения проблемы — добавление в UpDown1
обработчика этого сообщения (для этого можно написать наследника TUpDown
или же воспользоваться свойством WindowProc
— см. разд. 1.1.8 ), который отменит захват мыши. Отсутствие этого обработчика — тоже явная ошибка VCL.
3.4.3. Access violation при закрытии формы с перекрытым методом WndProc
Чтобы увидеть этот "подводный камень", создадим проект, содержащий две формы: главную Form1
и вспомогательную Form2
. В Form1
добавим код, который по нажатию кнопки открывает Form2
.
Во второй форме напишем обработчик события OnClose
таким образом, чтобы он устанавливал по закрытию действие caFree
. Добавим поле строкового типа, перекроем конструктор и метод WndProc
так, чтобы окончательный код выглядел следующим образом (листинг 3.52, пример CloseAV на компакт- диске).
TForm2
type
TForm2 = class(TForm)
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
S: string;
protected
procedure WndProc(var Message: TMessage); override;
public
constructor Create(AOwner: TComponent); override;
end;
....
constructor TForm2.Create(AOwner: TComponent);
begin
S := 'abc';
inherited;
end;
procedure TForm2.WndProc(var Message: TMessage);
begin
inherited;
S[2] := 'x'; { * }
end;
procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action := caFree;
end;
Обратите внимание, что в конструкторе сначала присваивается значение полю S
, и лишь потом вызывается унаследованный конструктор. Это сделано потому, что по умолчанию S
содержит пустую строку, т.е. nil
, а уже при вызове унаследованного конструктора окно получит сообщения, для обработки которых будет вызван метод WndProc
. Если в этот момент S
будет по-прежнему nil
, попытка обратиться ко второму символу строки вызовет Access violation. Поэтому еще до начала работы унаследованного конструктора поле S
должно получить подходящее значение.
Запустим программу и попытаемся закрыть второе окно. Возникнет исключение Access Violation: Write of address 00000001. Проблема будет в строке, отмеченной {*}
. При этом любые другие манипуляции с окном никаких исключений вызывать не будут.
При Action = caFree
после завершения работы метода FormClose VCL вызывает метод TCustomForm.Release
. Проблема именно в нем: если попытаться закрыть Form2
с помощью Release
, возникнет то же самое исключение. В справке Release
позиционируется как безопасный способ удаления формы из ее собственного метода. К сожалению, в действительности это не так: реализация этого удаления оставляет желать лучшего и может приводить к попыткам работать с объектом тогда, когда его уже не существует.
При вызове Release
в очередь помещается сообщение CM_RELEASE
, адресатом которого является сама удаляемая форма. В очередном цикле петли сообщений CM_RELEASE
извлекается из очереди и передается на обработку. Так как сообщение адресовано форме, она же его и обрабатывает. Рассмотрим более подробно, как это происходит. (Детально механизм обработки сообщений в VCL описан в разд. 1.1.8 ; мы здесь рассмотрим только ту часть, которая относится к обработке CM_RELEASE
.)
Система передает управление оконной процедуре. Для каждого экземпляра визуального компонента VCL создает свою оконную процедуру с помощью MakeObjectInstance
. Эта процедура вызывает метод объекта MainWndProc
, передающий управление тому методу, на который указывает свойство WindowProc
. По умолчанию это WndProc
. WndProc
не обрабатывает CM_RELEASE
самостоятельно, а передает его методу Dispatch
. Dispatch
пытается найти для этого сообщения специальный обработчик (метод с директивой message
) и, т.к. в TCustomForm
такой обработчик описан (он называется CMRelease
), передаёт управление ему.
И здесь начинается самое интересное. CMRealease
просто вызывает Free
, удаляя тем самым объект, т.е. объект удаляется из метода самого объекта, что делать запрещено. Таким образом, после выполнения Free
управление вновь получает CMRealease
. Из него управление возвращается в Dispatch
, оттуда — в WndProc
, затем — в MainWndProc
, далее — в оконную процедуру, и только после этого управление получает код, который никак не связан с конкретным экземпляром компонента. Мы видим, что после обработки CM_RELEASE
и удаления объекта его методы продолжают работать. Методы уже не существующего объекта!
В принципе, методы несуществующего объекта могут вполне нормально завершить свою работу, если не будут обращаться к его полям или иным образом использовать указатель Self
, который к этому моменту будет уже недействительным. Но стоило нам только вставить в один из этих методов код, задействующий поле объекта, как возникла ошибка.
В данном примере получается следующее: сначала CM_RELEASE
передаётся стандартному обработчику, который вызывает деструктор. При работе деструктора финализируются все поля объекта, для которых это требуется. В нашем случае это означает, что в поле S
заносится nil
(освобождения памяти при этом не происходит, потому что S
до этого ссылалась на литерал, хранящийся в кодовом сегменте, а не в динамической памяти). После этого начинает работать наш код, который пытается изменить второй символ в строке. Программа пытается обратиться к ячейке с адресом nil
+ 1, т.е. 00000001, что и приводит к ошибке Access violation.
Обращение в аналогичной ситуации к нефинализируемым полям (целым, вещественным, логическим и т. п.) обычно не приводит к исключению. Это связано с тем, что менеджер памяти Delphi обычно не сразу отдаёт системе ту память, которую освобождает объект, поэтому программа, с точки зрения системы, имеет полное право ею пользоваться. Поля объекта не очищаются, и его образ продолжает храниться в памяти, просто менеджер памяти помечает эту область как неиспользуемую и может в любой момент выделить ее для хранения другого объекта. Это создает иллюзию того, что объект продолжает существовать и позволяет работать с уже несуществующим объектом. Но это все равно некорректно, потому что любое перераспределение памяти в данной ситуации может привести к непонятной ошибке.
Читать дальшеИнтервал:
Закладка: