А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
procedure TForm1.Button2Click(Sender: TObject);
var
A1, A2: Extended;
begin
X := 2;
A1 := X / GetValueAndModifyX;
X := 2;
A2 := GetValueAndModifyX / X;
Label1.Caption := FloatToStr(A1);
Label2.Caption := FloatToStr(A2);
end;
В результате выполнения этого кода A1
получает значение 0.5, A2
— 2, т.е. и здесь сначала вычисляется функция, а потом берется значение переменной X
.
Если бы функция GetValueAndModifyX
не имела побочных эффектов (т.е. только возвращала бы результат и больше ничего не меняла), порядок вычисления аргументов был бы нам безразличен. Вообще, функции, имеющие побочные эффекты, считаются потенциальным источником ошибок, поэтому их написание нежелательно. Но в некоторых случаях (например, в функции Random
) обойтись без побочных эффектом невозможно.
Побочные эффекты в функциях настолько небезопасны, что в некоторых языках они полностью запрещены. Например, в Аде изменять значения глобальных переменных могут только процедуры, но не функции.
Ради интереса посмотрим, что будет, если вторым аргументом тоже будет функция, зависящая от X
, (листинг 3.49).
function GetX: Integer;
begin
Result := X;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
A1, A2: Integer;
begin
X:= 2;
A1 := GetX + GetValueAndModifyX;
X := 2;
A2 := GetValueAndModifyX + GetX;
Label1.Caption := IntToStr(A1);
Label2.Caption := IntToStr(A2);
end;
Здесь A1
получит значение 4, A2
— 3, т.e. интуитивно ожидаемые. Тем не менее полагаться на интуицию все же не стоит: в более сложных случаях она может подвести. Дело в том, что стандарт языка Паскаль разрешает разработчикам конкретной реализации языка самим выбирать порядок вычисления операндов [5]. Поэтому, даже если вам удалось добиться желаемого порядка вычисления, в следующих версиях Delphi (или при переносе на другую платформу) программа может начать работать неправильно. Таким образом, разработчик не имеет права делать какие-то предположения о том, в каком порядке будут вычисляться операнды, а когда изменение этого порядка может повлиять на результат, код должен быть написан таким образом, чтобы исключить эту возможность. В частности, пример со сложением должен быть переписан так (листинг 3.50).
procedure TForm1.Button1Click(Sender: TObject);
var
A1, A2: Integer;
begin
X := 2;
A1 := X;
Inc(A1, GetValueAndModifyX);
X := 2;
A2 := GetValueAndModifyX;
Inc(A2, X);
Label1.Caption := IntToStr(A1);
Label2.Caption := IntToStr(A2);
end;
Такой код, несмотря на побочные эффекты функции GetValueAndModifyX
, даст ожидаемые значения при любом порядке вычисления операндов, т.к. здесь вычисление операндов разнесено по разным операторам, а порядок выполнения операторов четко определен.
Другие компиляторы могут использовать иной порядок вычисления операндов. Так, FreePascal вычисляет их в том порядке, в каком они встречаются в выражении, т.е. в первом примере А1
получит значение 4, А2
— 3.
3.4.2. Зацикливание обработчика TUpDown.OnClick при открытии диалогового окна в обработчике
Для демонстрации этого "подводного камня" нам потребуется проект, на форме которого находится компонент TUpDown
со следующим обработчиком события OnClick
(листинг 3.51, пример UpDownDlg на компакт-диске).
OnClick
компонента UpDown1
procedure TForm1.UpDown1Click(Sender: TObject; Button: TUDBtnType);
begin
Application.MessageBox('Text', 'Caption', MB_OK);
end;
Теперь, если запустить программу и нажать на верхнюю кнопку UpDown1
, откроется окно с сообщением (при нажатии на нижнюю кнопку окно не будет открываться потому, что по умолчанию у компонент TUpDown
свойства Position
и Min
равны нулю, поэтому нажатие на нижнюю кнопку не приводит к изменению значения Position
, и событие OnClick
не возникает; если изменить значение свойства Min
или Position
, то тот же эффект будет наблюдаться и при нажатии на нижнюю кнопку). Если закрыть это окно, то щелчок мышью в любом месте формы снова приведет к срабатыванию события OnClick
и открытию окна, и так до бесконечности: любой щелчок по форме в любом ее месте будет снова и снова приводить к появлению сообщения. Эффект наблюдается и в том случае, когда вместо стандартного сообщения в обработчике показывается любая другая модальная форма. Кроме того, тот же эффект будет, и если использовать события OnChanging
или OnChangingEx
вместо OnClick
, но мы далее для определенности будем говорить только об OnClick
.
Если этот код пройти по шагам в отладчике, то никакого зацикливания не возникает: OnClick
вызывается один раз, любое последующее нажатие кнопки мыши на форме не приводит ни к каким необычным результатам.
Причина этой проблемы в том, как VCL обрабатывает сообщения, которые система помещает в очередь. При нажатии на кнопку компонента TUpDown
в очередь сообщений помещаются два сообщения: WM_LBUTTONDOWN
и WM_NOTIFY
. Компонент TUpDown
по умолчанию имеет стиль csCaptureMouse
— это означает, что при обработке WM_LBUTTONDOWN
VCL захватывает мышь в монопольное пользование для данного компонента.
Монопольное использование мыши означает, что любые сообщения, связанные с мышью, будут поступать захватившему мышь окну даже если ее курсор в это время находится за пределами данного компонента. Примером захвата мыши может служить любая кнопка: щелкните мышью над любой кнопкой на экране и, не отпуская клавиши мыши, начните перемещать курсор. Когда курсор будет выходить за пределы кнопки, она будет отжиматься, находить на нее — снова нажиматься. Теперь отведите курсор за пределы кнопки, отпустите клавишу мыши и снова подведите его к кнопке. Кнопка не нажмется. Это происходит потому, что пока клавиша мыши удерживается нажатой, мышь захвачена кнопкой, и сообщение об отпускании клавиши мыши передаётся кнопке, независимо от того, над каким окном находится курсор. Это позволяет кнопке правильно реагировать на отпускание пользователем мыши, в том числе и за ее пределами.
Затем начинает обрабатываться событие WM_NOTIFY
, которое уведомляет программу о том, что пользователь нажал на кнопку компонента TUpDown
. Именно при обработке этого сообщения VCL вызывает событие TUpDown.OnClick
, в котором открывается модальное окно. Всё это происходит очень быстро, поэтому кнопку мыши пользователь отпускает тогда, когда модальное окно уже оказалось на экране. В результате сообщение WM_LBUTTONUP
либо попадает в очередь открывшегося диалогового окна, если мышь находилась над ним, либо вообще никуда не попадает, если мышь была вне модального окна. На время существования модального окна система "забывает" о том, что мышь захвачена для монопольного использования, но "вспоминает" об этом, как только модальное окно закрывается. Монопольное использование мыши компонентом TUpDown
должно отменяться при обработке сообщения WM_LBUTTONUP
, но оно, как было сказано ранее, в очередь не попадает, поэтому после закрытия окна мышь остается захваченной данным компонентом. Поэтому любое нажатие кнопки мыши воспринимается системой как относящееся к UpDown1
, и снова приводит к помещению в очередь сообщений WM_LBUTTONDOWN
и WM_NOTIFY
, которые обрабатываются описанным образом. Так получается порочный круг, из которого при нормальной работе программы нет выхода. Этот круг может быть разорван, например, отладчиком, который отменяет монопольное использование мыши компонентами программы, чтобы иметь возможность работать.
Интервал:
Закладка: