ShowMessage — Процедура Delphi


Содержание

ShowMessage — Процедура Delphi

Взаимодействие приложения с пользователем присутствует в каждой программе. Приложение должно иметь возможность сообщить пользователю о том или ином развитии событий, предоставить возможность принять решение и сообщить его программе. Для этого Delphi предоставляет программисту несколько способов, в зависимости от необходимого уровня взаимодействия приложения с пользователем в каждом конкретном случае.

Функция ShowMessage

Простейший случай такого взаимодействия — просто проинформировать пользователя. Ну хотя бы о том, что в процессе вычисления произошло деление на нуль. Простое информационное сообщение укажет пользователю на необходимость внести изменение в данные расчёта. Для создания простого информационного сообщения служит встроенная функция ShowMessage:

ShowMessage(‘На нуль делить нельзя!’);

Функция ShowMesage допускает выведение сообщения в несколько строк. Для этого нужно воспользоваться стандартными символами перехода на другую строку: #10#13:

ShowMessage(‘возникла ошибка деления на нуль.’+#10#13+’Исправьте введённые данные.’);

Пример.
Вот как может выглядеть программа, вычисляющая результат деления двух чисел, с организацией выдачи информации пользователю о неправильном вводе данных:

procedure TForm1.Button1Click(Sender: TObject);
var x, y: Integer;
begin
try
x:=StrToInt(Edit1.Text);
y:=StrToInt(Edit2.Text);
if y=0 then
begin
ShowMessage(‘На ноль делить нельзя!’+#10#13+’Исправьте ввод.’);
Edit2.SetFocus;
exit;
end;
Label3.Caption:=’Значение: ‘+FloatToStr(x/y);;
except
ShowMessage(‘Должны быть введены целые числа, исправьте!’);
Edit1.SetFocus;
end;
end;

Данная программа предупреждает пользователя как о вводе нуля, так и вообще о введении нечисловых данных или нецелых чисел. Информационное окошко выводится в модальном режиме, то есть, при выдаче сообщения работа программы прерывается, и продолжится только после закрытия окна с оператора, следующего за вызовом функци ShowMessage:

Функция MessageDlg

В общем случае, кроме простого информирования, приложение должно получить согласие пользователя на совершение того или иного действия. Такого рода взаимодействие программы и пользователя позволяет осуществить функция MessageDlg. Как видно из названия, функция MessageDlg осуществляет именно диалог, то есть задаёт вопрос и ожидает ответ пользователя.

Для получения такого ответа функция MessageDlg может предоставлять пользователю набор кнопок, подразумевающих тот или иной ответ. Ответы на вопрос могут быть, например, «Да» — «Нет», или «Да» — «Нет» — «Отменить», или другие варианты. Вот описание функции MessageDlg:

function MessageDlg(const Msg: String; DlgType: TMsgDlgType;
Buttons: TMsgDlgButtons; HelpCtx: Longint): Integer;

Параметры функции MessageDlg:
Msg — выводимое сообщение, с возможностью вывода в несколько строк, DlgType — тип диалога, например «Опасность, Ошибка, Информация, Подтверждение», и т.д., Buttons — множество кнопок с соответствующими заголовками, HelpCtx — в большинстве случаев можно использовать «0» — ноль. Для каждого из типов диалога выводится своя пиктограмма, что, несомненно, прибавляет красочности и информативности окну, выводимому данной функцией.

Для того чтобы получить более полную информацию о параметрах, воспользуйтесь справкой Delphi: нажмите CTRL и щёлкните мышкой по функции или нужному параметру. Откроется модуль с полным описанием.

Функция MessageDlg возвращает значение, соответствующее данному пользователем ответу на вопрос. Это значение несложно получить в целую переменную и проанализировать, перед тем как использовать в программе. Например, нажатие на кнопку «Yes» возвращает значение «6», результат нажатия любых других кнопок также несложно получить.

Например, для того чтобы организовать диалог закрытия приложения, программа может использовать такой вариант функции MessageDlg:

Result:=MessageDlg(‘Вы действительно хотите закрыть приложение?’, mtConfirmation, [mbYes, mbNo], 0);
if Result=6 then Application.Terminate;

Пример.
Организуем диалог, запрашивающий у пользователя разрешение сохранить изменения в текстовом редакторе в файл перед закрытием приложения:

procedure TForm1.Button1Click(Sender: TObject);
var Result: Integer;
begin
if Memo1.Modified
then Result:=MessageDlg(‘Сохранить изменения?’, mtConfirmation, [mbYes, mbNo, mbCancel], 0);
case Result of
2: exit; //Нажатие «Cancel», выходим из функции, без закрытия приложения
6: Memo1.Lines.SaveToFile(‘ИмяФайла.txt’); //Нажатие «Yes», сохраняем файл
7: ; //Нажатие «No», ничего не делаем
else exit; //Если окно закрыли нажатием на «крестик» в заголовке — тоже выходим без закрытия приложения
end;
Application.Terminate; //Закрываем приложение
end;

Видим, что по сравнению с функцией ShowMessage функция MessageDlg имеет более развитый интерфейс. Можно управлять заголовком окна, пиктограмой выводимой в том или ином типе запроса, и набором кнопок, позволяющим получить и проанализировать ответ пользователя. Пользуясь этим, функцию MessageDlg с успехом можно использовать вместо функции ShowMessage.

Русификация функции MessageDlg

Конечно, такие заголовки кнопок как, например, «Yes», «No», «Cancel» давно стали стандартом и понятны пользователям без перевода. Однако, применяя функцию MessageDlg в программе, где все остальные надписи выводятся на русском языке, можно видеть что английский интерфейс её окна смотрится чужеродно. Воникает вопрос, как можно русифицировать заголовок окна и названия кнопок такой удобной и полезной функции как MessageDlg.

Есть несколько способов сделать это. Заголовок и названия кнопок функции MessageDlg берутся из модуля consts.pas и при компиляции помещаются в ресурсы приложения. Поэтому, изменив данные константы в упомянутом модуле, и перекомпилировав приложение, мы получим вполне себе русскоязычное окно, выводимое функцией MessageDlg.

Однако, что-то менять в стандартных модулях Delphi не всегда правильно. Есть другой способ, связанный с динамическим формированием функции MessageDlg. Этот способ позволяет перед выводом на экран динамически изменить параметры функции. Вот как, пользуясь этим способом, можно русифицировать запрос на сохранение текста в предыдущей программе:

ShowMessage — Процедура Delphi

Displays a message box with an OK button.

Call ShowMessage to display a simple message box with an OK button. The name of the application’s executable file appears as the caption of the message box.

Msg parameter is the message string that appears in the message box.

Params lists the values to insert into Msg if the Msg string includes formatting specifiers. For more information about how messages are formatted, see Format Strings.

Params_size is the index of the last value in Params (one less than the number of elements).

Программирование Delphi

Все о программировании.

Главное меню

Перегрузка ShowMessage для вывода Boolean, Integer, Float.

Процедура ShowMessage в Delphi, определенная в модуле Dialogs.pas отображает значение в диалоговом окне и ждет пользователя, чтобы он щелкнул кнопку OK.

Полезно для отладки выводить промежуточные результаты переменных, свойств, результатов функций при помощи процедуры ShowMessage.

Неудобная сторона процедуры ShowMessage — это то, что она принимает только строки в качестве параметра, в то время, как переменные могут быть целыми числами, с плавающей точкой, булевыми значениями и т.д.

Каждый раз, когда нужно отобразить, например, целое число с использованием ShowMessage, возникает необходимость преобразовать целое число в строку (используя IntToStr). Так, вызов ShowMessage для целого числа выглядел бы так:

Более гибкий ShowMessage

Чтобы ускорить отладку при кодировании можно добавить несколько overloaded процедур ShowMessage и поместить их все в модуле Common.pas.

Вот пример переделанного модуля Common.pas:

Как использовать перегруженную процедуру ShowMessage

Если Вы хотите использовать этот модуль из другого модуля, Вам конечно, нужно включить его в разделе uses, плюс:

Удостоверьтесь, что модуль Common перечислен ПОСЛЕ модуля Dialogs.

Delphi. Messages. Первые наивные пробы

Решил разобраться с устройством Windows, в частности с сообщениями. Народ в сети массово отправляет к Рихтеру и Русиновичу. Книги приобрел – начал читать. Что хочу сказать – первое впечатление – информация ценнейшая для разработки под Windows. Ещё и на русском языке. Но поскольку практика это лучший инструмент познания, решил сделать несколько простых примеров до основательного чтения этих книг. После чтения сделаю ещё несколько примеров, чтобы сравнить насколько лучше понимаю предмет.

Как отправить сообщение из приложения?

Рассмотрим на примере закрытия окна. Сообщение будет WM_CLOSE. Список всех сообщений для Windows можно посмотреть здесь.

Итак, из того, что я понял на данный момент, чтобы отправить сообщения определенному окну, часто используются функции

Оба сообщения закроют окно c Handle равным Form2.Handle Первый параметр – Handle окна, второй параметр – собственно сообщения, а третий и четвертый – дополнительные параметры, которые задействуются или нет от случая к случаю, например координаты курсора. В приведенном примере оба параметра занулены. Если поместить эти инструкции, скажем в обработчик кнопки окна, то после нажатия в ядро Windows будет направлено сообщение о закрытии окна, а Windows, соответственно просто закроет окно.

Где найти полный список сообщений?

В принципе таких ресурсов много. Вот один из них.

Слушает ли Delphi программа сообщения?

Определенно да. Вот простой пример. Поймаем сообщение нажатия правой кнопкой мыши на любом из компонентов приложения.

Если нажмем правой клавишей мыши на любом компоненте приложения, то увидим

Здесь мы отлавливали сообщение о нажатии правой кнопки мыши. Таким же образом можно отлавливать любые другие сообщения из списка.

Как отправить “кастомное” сообщение?

Под кастомным сообщением я подразумеваю сообщение не из списка, а какое-то свое сообщение, например WM_USER+2001. Вот как можно отправить такое кастомное сообщение. Пример основан на официальной документации


Объявляем глобальную константу

Далее создаем и присваиваем обработчик

И последний штрих – отправляем сообщения

Ещё вариант отправки кастомного сообщения

Определяем свою константу (она должна быть прописана выше, чем метод, который её использует, то есть, например, до описания типов)

Далее определяем обработчик сообщения в методах класса формы, например, таким образом

Далее прописываем его

Далее отправляем PostMessage, скажем, по нажатию кнопки

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

1 декабря 2015 г.

Дело о неработающем ShowMessage

Новый «раздел» статей в блоге: показываем как можно применять возможности отладчика на практике.

На форуме человек задал вопрос: «почему не работает ShowMessage». Код в вопросе был такой:
При загрузке этой библиотеки через LoadLibrary мы должны увидеть сообщение:

Но этого не происходит, ShowMessage просто тихо ничего не делает.

(На самом деле, автор вопроса спрашивал «почему не выполняется код между begin/end». Он также сообщил некоторые интересные детали. В частности, загрузка DLL из другого приложения показывает сообщение. Также, если показ сообщения вставить в любую экспортируемую функцию, то оно будет показано в обоих случаях)

Итак, как же нам решить это загадочное дело?

Для начала нужно создать т.н. тестовый проект. В нашем случае мы создадим новую DLL, впишем в неё код выше. Также мы создадим новое приложение VCL Forms, добавим одну кнопку и напишем такой обработчик:
Запустим приложение вне отладчика через Ctrl + Shift + F9 (Run / Run Without Debugging) и нажмём на кнопку. Нам нужно убедится, что, действительно, ничего не происходит. Т.е. нам нужно воспроизвести поведение.

Если мы не видим проблемного поведения (т.е. сообщение будет всё же показано), значит, мы не смогли воспроизвести проблему. Возможно, поведение программы меняется под отладчиком? Пробуем запустить под отладчиком. Возможно, дело в версии Delphi? Пробуем другую версию Delphi. Возможно, дело в версии Windows? Пробуем другую версию ОС. И так далее. Мы подбираем условия окружения при которых мы надёжно воспроизводим ошибку.

(Забегая вперёд, скажу, что для воспроизведения проблемы нужна Delphi 2007 и выше, Windows Vista и выше)

Как только мы воспроизвели «плохое» поведение под отладчиком — нужно настроить проекты для отладки. Для этого откроем Project / Options каждого проекта (exe и DLL) и включим (если они не включены) следующие опции:

  • Compiling \ Stack Frames
  • Compiling \ Range Checking
  • Compiling \ все опции в категории Debugging
  • Compiling \ Use Debug DCUs
  • Linking \ Debug Information (старое название: TD32 Debug Info)
  • Linking \ Include remote debug symbols
  • Linking \ Map file = Detailed

и выключим опцию Compiling \ Optimization.

Конечно, не все эти опции нужно включать для нашего примера, но тут, что называется, «лишним не будет». Главные опции для нашего случая это Compiling \ Use Debug DCUs — т.к. мы собираемся отлаживать код RTL/VCL ( ShowMessage ) и Linking \ Debug Information — т.к. мы будем отлаживать DLL и EXE.

Кроме того, на вкладке Delphi Compiler мы сбросим Output Directory и Unit Output Directory в » .\ » (без кавычек) — что приведёт к выводу всех файлов в ту же папку где лежат исходники (вместо обычной подпапки \Win32\Debug ).

Сделаем Project / Build каждому проекту (напомню, что простого Project / Compile недостаточно если вы меняете опции проекта, но не его исходный код).

Теперь, откроем проект DLL и установим точку останова:

Теперь используем Run / Parameters и укажем для какого исполняемого файла нужно запускать DLL (это делать не нужно, если вместо проекта DLL вы открываете проект EXE):

Запускаем проект. Вы увидите, что точка останова становится недействительной:

Это — нормально. Ведь DLL не загружена в процесс EXE. Если бы это была точка останова на код в EXE — тогда, да, это было бы не нормально, что-то пошло не так.

Когда вы нажмёте на кнопку в приложении, DLL будет загружена вызовом LoadLibrary , после чего точка останова снова станет действительной. Затем LoadLibrary вызовет DllMain нашей библиотеки с «причиной» = DLL_PROCESS_ATTACH . Для события DLL_PROCESS_ATTACH RTL Delphi автоматически вызывает секции initialization всех модулей из DLL, а также секцию begin/end .dpr файла (и, наоборот, для DLL_PROCESS_DETACH вызываются секции finalization всех модулей). В результате мы встаём на нашей точке останова:

В окне стека вызовов (View / Debug Windows / Call Stack) вы можете видеть, что нас вызвала LoadLibrary , которую, в свою очередь, вызвали мы из Button1Click . К сожалению, отладчик IDE не может полностью реконструировать стек, пропуская некоторые вызовы. Он также не знает где взять отладочную информацию для системных DLL.

В любом случае, мы нажимаем F7 (Run / Step Into), чтобы войти в ShowMessage :

Если вы не видите эту картину, а вместо этого отладчик просто перешёл на следующую после ShowMessage строку (с end ) — т.е. F7 (Step Into) сработала как F8 (Step Over) — то это означает, что у отладчика нет отладочной информации о модулях RTL/VCL. Это происходит потому, что опция Use Debug DCUs не была включена. А если она была включена, значит, она не возымела действия. Последнее может происходить в двух случаях:

  1. Вы не сделали проекту DLL Project / Build (а, например, просто запустили его);
  2. Ваш проект использует пакеты времени выполнения (run-time packages, BPL).

Окей, как только вы разобрались с проблемами, продолжаем нажимать F7.

Вот первая интересная функция. Мы видим, что при выполнении некоторых условий: Vista+ ( TOSVersion.Check(6) ), UseLatestCommonDialogs , ComCtl32.dll V6+ (IsNewCommonCtrl) и использовании стиля по умолчанию (StyleServices.IsSystemStyle), вызывается DoTaskMessageDlgPosHelp , в противном случае — DoMessageDlgPosHelp . Быстро глянем в DoTaskMessageDlgPosHelp :
и DoMessageDlgPosHelp : Отсюда видно, что в зависимости от набора условий ShowMessage реализуется либо через вызовы TaskDialog API, либо через обычную VCL-форму (создаётся в CreateMessageDialog ). Что-то похожее мы уже делали.

Итак, нажмём один раз F8 (Run / Step Over), чтобы выполнить строчку с if и посмотреть, куда мы встанем:

Окей, т.е. в нашем случае ShowMessage будет реализован через Task Dialog API. Заходим внутрь по F7 (Step Into), затем ещё раз (входим в DoTaskMessageDlgPosHelp ). Функция DoTaskMessageDlgPosHelp настраивает диалог, а затем его вызывает. Нам интересно, что происходит в момент вызова диалога, поэтому весь код настройки мы проходим по F8 (Step Over) — вплоть до вызова if LTaskDialog.Execute then . Поскольку в теле DoTaskMessageDlgPosHelp есть цикл (да и в целом это не самая короткая функция) — можно пролистать код вниз и установить новую точку останова на строчку с LTaskDialog.Execute , после чего запустить программу через F9 (Run / Run). Отладчик выполнит код настройки и встанет на точке останова:

Заходим в LTaskDialog.Execute по F7 (Step Into):

Метод Execute — динамический ( dynamic ), поэтому он вызывается не напрямую. Этот код ищет адрес метода в таблице DMT и сохраняет его в регистр ESI , после чего делает на него переход ( JMP ). Мы могли бы (как и выше с LTaskDialog.Execute ) установить точку останова на строчку JMP ESI , но вызов динамического метода — частая операция. Мы бы не хотели вставать на этой точке останова каждый раз, когда мы проходим по F8 (Step Over) вызовы других динамических методов. Поэтому мы установим курсор на строчку JMP ESI и нажмём F4 (Run / Run to Cursor), после чего нажмём F7 (Step Into) и, наконец, попадём внутрь метода Execute :

Проходим метод по F8 (Step Over) или используем F4 (Run to Cursor) и заходим по F7 (Step Into) в Result := Execute(LParentWnd) . Как и ранее, Execute — метод динамический, поэтому используем F4 (Run to Cursor) на JMP ESI и F7 (Step Into) для входа в унаследованную реализацию:

Несколько раз повторим эти операции, путешествуя по реализациям Execute , пока не окажемся в самой нижней TCustomTaskDialog.DoExecute :

Как и выше с DoTaskMessageDlgPosHelp , TCustomTaskDialog.DoExecute сначала производит настройку, а затем вызывает интересующий нас кусок:

Здесь нас интересует вызов TaskDialogIndirect , поэтому ставим курсор на строчку Result := TaskDialogIndirect(. ) = S_OK; и жмём F4 (Run to Cursor). Далее нажимаем F7 (Step Into).

Мы ожидали, что TaskDialogIndirect — функция Windows, для неё у нас нет исходного кода (даже с включенной опцией Use Debug DCUs), поэтому F7 (Step Into) сработает как F8 (Step Over). Но, как мы видим, в Delphi TaskDialogIndirect — это переходник-обманка, которая динамически («по запросу») загружает «настоящую» TaskDialogIndirect (и сохраняет её в глобальной переменной _TaskDialogIndirect ). Это (скрытие реализации под «известным» именем) — подводный камень при отладке, т.к. мы можем не предположить, что за вызовом TaskDialogIndirect скрывается какой-то «наш» код и пропустить его пройдя вызов TaskDialogIndirect по F8 (Step Over).

Если вы попались на эту удочку и выполнили TaskDialogIndirect по F8 (Step Over), то вы увидели, что Result стал равен False , а сообщения на экране не появилось. Т.е. TaskDialogIndirect вернула какой-то код ошибки, который код RTL/VCL успешно проигнорировал. Вы хотите узнать этот код. Для этого вы устанавливаете точку останова на строчку Result := TaskDialogIndirect(. ) = S_OK — это интересующий нас участок кода. Ничего больше нас уже не интересует, поэтому все прочие точки останова (View / Debug Windows / Breakpoints) можно удалить.

(Подсказка: сначала удалите все старые точки останова, а лишь затем устанавливайте новую точку останова на строчку Result := TaskDialogIndirect(. ) = S_OK , а не наоборот.)

Снимите приложение по F2 (Run / Program Reset) и запустите снова. Щёлкните по кнопке — и вы должны встать сразу на строчке Result := TaskDialogIndirect(. ) = S_OK , минуя все предыдущие шаги:

Вызовите CPU отладчик через Ctrl + Alt + C (View / Debug Windows / CPU Windows / Entire CPU):

Вы увидите машинный код, в который была скомпилирована строка исходного кода, на которой вы стоите. Вызов любой функции будет происходить в три шага:

  1. Подготовка аргументов
  2. Непосредственный вызов (передача управления)
  3. Чтение/сохранение/анализ результата

Обратите внимание, что п1 может включать в себя и другие вызовы функций (например, код DoExecute(GetParentWindow) сначала вызовет GetParentWindow , а лишь затем — DoExecute ). Нас же интересует только п2. Несложно сообразить, что п2 будет последним вызовом функции среди всего кода, сгенерированного для этой строки.

Вызов другой функции на ассемблере — это инструкция call , поэтому нас интересует последняя инструкция call в строчках машинного кода между двумя жирными строками ( Vcl.Dialogs.pas.5703: Result := . и Vcl.Dialogs.pas.5705: FModalResult := . ). В данном случае это 04BF2AE0 E88751E7FF call $04a67c6c .

Вы можете нажимать F8 (Step Over), чтобы пройтись по строкам машинного кода вплоть до этой строки, или же вы можете установить курсор на эту строчку и нажать F4 (Run to Cursor). В любом случае вы встанете на этой строке:

Нажмите F8 (Step Over) ещё раз, чтобы выполнить эту функцию.

Результат функции будет помещён в регистр EAX — любая функция всегда возвращает результат через EAX . Но даже, если вы это не знаете, про это можно догадаться, т.к. п3 из списка выше («чтение/сохранение/анализ результата») первым делом проверяет регистр EAX ( test eax,eax ).

Поскольку TaskDialogIndirect возвращает HRESULT , то в EAX будет лежать искомый код ошибки (в виде HRESULT ).

В любом случае, возвращаясь к коду TaskDialogIndirect — здесь нас интересует вызов _TaskDialogIndirect , но мы не знаем по какой ветке пойдёт выполнение, поэтому мы нажимаем F8 (Step Over), пока это не станет ясно (как оказывается, мы идём по ветке else ). Дойдя до Result := _TaskDialogIndirect мы (на всякий случай) нажимаем F7 (Step Into):

В этот раз F7 (Step Into) сработала как F8 (Step Over) — т.к. мы вызвали функцию Windows (для которой у нас нет исходного кода). В данном случае мы можем увидеть, что результат вызова (значение Result типа HRESULT ) равно -2147024809. Для этого вы можете просто навести мышью на слово Result в редакторе кода — и IDE покажет значение Result во всплывающей подсказке. Или вы можете использовать окно локальных переменных (View / Debug Windows / Local Variables). Или вы можете щёлкнуть правой кнопкой по Result и выбрать Debug / Evaluate/Modify из всплывающего меню.

Иногда отладчик может показывать сообщение о том, что значение переменной/выражения не доступно. Обычно это происходит при включенной оптимизации, когда код сохраняет/использует значение ранее и не хранит его вплоть до выхода из функции. Это проще всего лечить отключением оптимизации (что мы и сделали в самом начале при настройке проекта). Альтернативно, вы можете вычислять/проверять интересующие вас значения выше по тексту (пока они ещё не отбрасываются), либо позднее — уже после выхода из функции. В крайнем случае вы всегда можете открыть CPU-отладчик и проверить значения внутри строк — как мы сделали это выше в лирическом отступлении.

Итак, мы вроде как нашли причину, почему ShowMessage ничего не показывает. Потому что она вызывает TaskDialogIndirect , а она, в свою очередь, завершается с ошибкой номер -2147024809. Но что это за ошибка номер -2147024809?

Для этого мы запустим утилиту Error Lookup из состава EurekaLog. Если вы используете EurekaLog, то эта утилита у вас уже есть — её можно запустить через меню Пуск (Пуск / Программы / EurekaLog / Tools / Error Lookup) или через меню IDE Delphi (Tools / EurekaLog / Error Lookup). Если EurekaLog у вас нет, то Error Lookup можно установить бесплатно автономно — скачав её с сайта — вас интересует «Freeware EurekaLog Tools Pack».


Итак, скормим -2147024809 в Error Lookup:

Как вы можете видеть, -2147024809 — это ошибка HRESULT с кодом $80070057 = E_INVALIDARG (причём это спроецированная на HRESULT Win32 ошибка с кодом 87 = ERROR_INVALID_PARAMETER — что можно проверить запустив поиск ошибки 87). Итак, TaskDialogIndirect ругается на неверные аргументы. Уже в этот момент мы должны понять, что что-то идёт сильно не так. Предположительно отлаженный код RTL/VCL должен вызывать предположительно отлаженный код Windows, так что ошибки вида «неверный аргумент» возникать в принципе не должно, если только в функцию не просочатся «неверные» данные от нас лично.

Но какой именно аргумент не нравится TaskDialogIndirect ? Жаль, что системные функции Windows не используют исключения — с кодами ошибок у нас нет указания на аргумент, который не понравился функции. У нас есть два вектора атаки:

  1. Окно-родитель (ParentWnd) устанавливается динамически самим RTL/VCL и не приходит от нашего кода. Возможно, TaskDialogIndirect не понравилось окно?
  2. Известно, что вызов ShowMessage в экспортируемой функции работает успешно. Мы можем сравнить, чем отличаются аргументы между успешным и не успешным вызовами.

Чтобы проверить первую гипотезу, мы установим точку останова на (вторую) строчку Result := _TaskDialogIndirect(. ) в TaskDialogIndirect и удалим все прочие точки останова (как и ранее, это удобнее делать наоборот: сначала удалить все точки останова, потом добавить новую). Перезапустим программу, щёлкнем по кнопке и остановимся на точке. Проанализируем аргументы функции _TaskDialogIndirect (наводите на них мышь или используйте Evaluate/Modify). Вы увидите, что окно-родитель передаётся в pTaskConfig.hwndParent . Нам нужно сбросить это значение в ноль (и для начала стоит выяснить, что ноль ( NULL ) является допустимым аргументом — это так, мы проверили это по документации). Чтобы изменить это значение, удобнее всего вызвать Evaluate/Modify из окна локальных переменных:

Или щёлкните правой по pTaskConfig в редакторе кода и вызовите Evaluate/Modify из контекстного меню, затем допишите «.hwndParent» (без кавычек) в поле Expression и нажмите Evaluate.

Чтобы сбросить это значение, введите 0 в поле New value и нажмите Modify. Теперь значение обнулено:

Выполните функцию (по F8). Результат оказывается тем же самым (функция завершается с ошибкой E_INVALIDARG ). Т.е. дело не в окне-родителе.

Для второй гипотезы нам нужно выписать все аргументы функции. Для этого удобно развернуть окно локальных переменных на всю высоту и развернуть в нём все под узлы. Альтернативно можно также открыть несколько окон Evaluate/Modify:

Просто сделайте скриншот экрана.

Теперь нам нужно вызвать ShowMessage из экспортируемой функции. Для этого снимите выполняющуюся программу и измените текст DLL так:
Затем сохраните изменения, скомпилируйте, закройте проект DLL, откройте проект EXE и измените его код:
Мы пишем тестовый пример, поэтому мы можем наплевать на правильное освобождение ресурсов, но нам важна правильная обработка ошибок, т.к. она облегчает диагностику.

Сохраните и перекомпилируйте проект EXE. Запустите проект и нажмите на кнопку. Убедитесь, что теперь сообщение показывается (это происходит из экспортируемой функции). Закройте проект EXE и откройте проект DLL.

Вернитесь к нашей TaskDialogIndirect . Чтобы это быстро сделать — откройте список точек останова (View / Debug Windows / Breakpoints) и дважды щёлкните по (единственной) установленной точке останова — среда должна перенести вас на (вторую) строчку Result := _TaskDialogIndirect(. ) внутри WinAPI.CommCtrl.TaskDialogIndirect (да, надеюсь, в вашей среде были включены autosave-опции). Напомню, нас интересует второй вызов ShowMessage и, следовательно, второй вызов TaskDialogIndirect . Несложно догадаться, что во второй раз переменная _TaskDialogIndirect будет уже присвоена, поэтому выполнение пойдёт по первой ветке:
Что ж, установим точку останова на (первую) строчку Result := _TaskDialogIndirect(. ) (и уберём точку останова со (второй) строчки Result := _TaskDialogIndirect(. ) ) и запустим программу. Мы встанем на точке, после чего мы можем «заскриншотить» аргументы вызова _TaskDialogIndirect и сравнить их с нашим скриншотом первого вызова (из DllMain ).

Внезапно оказалось, что аргументы обоих вызовов полностью идентичны (но выполнение успешно лишь в одном случае, а в другом оно проваливается с ошибкой E_INVALIDARG ). Следовательно, дело не в аргументах самой TaskDialogIndirect , а в аргументах какой-то другой функции, которую вызывает TaskDialogIndirect — опять же, поскольку системные функции Windows используют коды ошибок, а не исключения, то мы никак не можем идентифицировать точную функцию, вернувшую ошибку.

На этом часто исследование можно считать законченным — у нас нет исходного кода Windows и мы не можем отлаживать её код. В таких случаях нам остаётся только перебор входных значений «угадыванием». Но в данном случае мы получаем ошибку E_INVALIDARG . Проверка аргументов — предположительно, что-то такое, что должно выполняться в начале функции, не слишком глубоко внутрь. Поэтому есть шанс, что мы сможем понять в чём дело, не потратив на это много времени.

Проходим по строчкам до вызова call и заходим в него по F7 (Step Into):

Как вы можете видеть, системная функция TaskDialogIndirect , на самом деле, очень короткая и является лишь переходником к чему-то другому (видимо, аналогично тому, как функция TaskDialog является переходником к TaskDialogIndirect ). В машинном коде не видно каких либо проверок (условных JMP), поэтому можно смело дойти до call и снова войти в него:

В этот раз ситуация интереснее. Код виден большой, длинный, есть какие-то проверки ( cmp , test ), переходы ( jz , jnz ).

Любая функция заканчивается инструкцией ret (в крайне редких случаях функция может безусловно передать управление на другую функцию вместо возврата управления). $CC ( int 3 ) является просто заполнителем свободного места между функциями (в системных DLL, Delphi же использует в качестве заполнителя случайный мусор). Иными словами, сейчас мы вошли в начало некоторой функции.

Однако не всегда ret обозначает конец функции. Это также может быть частью блока try/except/finally.

Что ещё отличается — перешли мы на какую-то внутреннюю функцию в ComCtl32.dll. Эта функция не экспортируется и не имеет имени. Поэтому отладчик >
Итак, абсолютный адрес у нас есть — это $7244550A. Как получить смещение? Для этого откройте View / Debug Windows / Modules и отсортируйте список загруженных DLL по имени (Name):

Найдите в списке DLL, соответствующую нашему адресу. В данном случае это будет ComCtl32.dll, что указывается в стеке вызовов. Альтернативно, вы можете отсортировать список по Base Address и взять максимальный адрес, который будет меньше нашего адреса. В обоих случаях вы найдёте строчку с ComCtl32.dll, откуда узнаете, что её базовый адрес — $72370000. Запускаем калькулятор Windows, переключаем его в режим «Программист», а также меняем режим на HEX, и: $7244550A — $72370000 = $000D550A. Именно это значение ($D550A) и будет смещением кода внутри (от начала) ComCtl32.dll. И именно это значение нужно скормить Address Lookup:

(Примечание: по умолчанию никакой настройки Address Lookup для использования сервера отладочной информации Microsoft выполнять не нужно, но если у вас что-то не работает — вот инструкция)

Окей, оказывается TaskDialogIndirect вызывает CTaskDialog.Show — что является методом Show класса CTaskDialog .

Что теперь? Теперь я предлагаю пройтись по коду с F8 (Step Over), внимательно следя за тем, что происходит в коде. В частности — какие переходы срабатывают. Тут можно придерживаться разных стратегий. Мы можем запустить две среды и две сессии отладки и отлаживать успешный и не успешный вызовы TaskDialogIndirect одновременно, сравнивая выполнение и находя отличия. Это гарантировано даст вам результат, но уж больно трудоёмко. Можно сообразить, что функция TaskDialogIndirect вернула нам код ошибки как результат (в EAX ), поэтому мы можем проследить по машинному коду, откуда пришло это значение («отладкой задом-наперёд»).

Можно также сообразить, что функция состоит непосредственно из кода, а также из вызовов других функций. Мы уже знаем (выяснили выше), что проблема — не в проверке аргументов непосредственно в самой TaskDialogIndirect , проблема в какой-то другой функции, которую вызывает TaskDialogIndirect . Есть ненулевая вероятность, что TaskDialogIndirect вызывает другую функцию, которая также возвращает HRESULT . Следовательно, мы можем следить за появлением известного кода ошибки ($80070057) в регистре EAX после вызова функций ( call ). Поэтому мы просто выполняем код CTaskDialog.Show по F8 (Step Over), пока не увидим $80070057 в EAX :

Окей, а вот и наша под-функция. Нам не пришлось идти слишком далеко (кажется, это третья вызываемая функция). Теперь мы можем установить точку останова на call $7248d137 и перезапустить программу, после чего остановиться на точке и войти в под-функцию по F7. Это снова будет какая-то внутренняя безымянная функция ComCtl32.dll. Снова используем калькулятор: $7248D137 — $72370000 = $11D137 и снова используем Address Lookup:

Немного странное имя, но ОК, предположим. В принципе, это имеет смысл, если учесть, что мы вызываем код из DllMain , которая имеет известные ограничения.

Что ж, повторим наш алгоритм: будем проходить код по F8 (Step Over), следя за появлением «волшебного кода» ($80070057) в регистре EAX после вызовов функций ( call ). В этот раз нам придётся пройти довольно много кода, но в итоге мы находим ещё один вызов:

(Кстати, обратите внимание, это как раз одна из функций, которая заканчивается на jmp , а не ret .)

Устанавливаем точку останова и перезапускаем программу (предыдущую точку останова можно удалить, либо просто пропускать/игнорировать при выполнении — главное, не запутаться).

Ой. В отличие от прошлого раза, где под-функция возвращала HRESULT , в этот раз HRESULT приходит от GetLastError , а сама проблемная функция вызывается ранее и, вероятно, возвращает просто 0 ( False ).

Само собой, искать 0 в EAX — гиблое дело, ибо там он будет ну очень часто. Вместо этого можно поступить двумя способами:

  1. Логично предположить, что провалившаяся функция вызывается непосредственно перед вызовом GetLastError , т.е. надо проверить только предыдущий call ;
  2. Можно вместо EAX следить за GetLastError .

И хотя в нашем случае вполне подходит вариант 1, давайте посмотрим на вариант 2. Для этого откройте окно Watch List (View / Debug Windows / Watches), щёлкните по нему правой кнопкой мыши и выберите Add Watch во всплывающем меню:

Впишите «GetLastError» (без кавычек) в Expression, включите Allow side effects and function calls и переключите отображение в Hexadecimal. Allow side effects and function calls необходима, чтобы отладчик вообще вычислял бы выражение с «GetLastError». По умолчанию отладчик не будет вычислять выражения для отладки, если это потенциально может изменить состояние программы. В нашем случае мы знаем, что вызов GetLastError — «безопасен», поэтому мы явно указываем это отладчику. Переключение же вида в Hexadecimal необходимо по той причине, что функция GetLastError возвращает код ошибки в виде числа ( DWORD / Cardinal ), даже хотя в нашем случае этим числом является HRESULT . Переключение режима позволит нам увидеть $80070057, а не -2147024809 (похоже, отладчик Delphi не различает знаковые и беззнаковые типы в Watch-ах).

Перезапустите программу и снова пройдитесь по коду, как мы это делали выше, только теперь вместо EAX смотрите и за EAX , и за GetLastError в Watch List.

Так или иначе вы находите проблемный вызов:

Установите точку останова, перезапуститесь, войдите:

Можно предположить, что этот странный код является заглушкой-переходником. «Входим» в jmp (не важно — по F8 или по F7, jmp это безусловный переход, а не вызов функции):

. и снова переходник! Входим в call :

. и ещё один! Снова входим в call :

Ага, а вот вам и причина для всех этих переходников: InitThread вызывает какую-то внешнюю (импортируемую) функцию, связанную с ComCtl32.dll через отложенный импорт (Delay Loaded). Собственно, здесь мы можем лишь войти в настоящую реализацию отложенного импорта:

Что ж, функция это довольно большая и длинная. Есть куча переходов, вызовов, даже ret -ы, и даже GetLastError будет меняться. Но нам не нужно анализировать весь этот код. Несложно догадаться, что функция должна найти адрес целевой функции (загрузить DLL, сделать туда GetProcAddress ), после чего сохранить результат в переменную (регистр/память) и сделать на него переход (не вызов! т.к. для вызова нужно формировать параметры, про которые функция не в курсе). Т.е. нам нужно дойти до jmp на опосредованное значение (т.е. не на фиксированный адрес типа $7244559С, а на, скажем, регистр).

Более того, если вы были действительно внимательны, то заметили, что этот jmp у нас уже есть — посмотрите выше на функции-переходники: по адресу $723F86EE как раз лежит jmp , который переходит на EAX (результат функции). Т.е. нам нужно только установить там точку останова и запустить программу по F9 (Run / Run). Ну или через F4 (Run to Cursor).

А вот и наша функция — некая InitGadgets из DUser.dll. Описание DUser.dll сообщает, что это — «Windows DirectUser Engine». Это недокументированная, внутренняя DLL Windows.

Окей, продолжаем выполнение дальше. Я, кстати, рекомендую поставить точку останова на начало InitGadgets — когда мы пропустили так много кода (особенно — внутреннего кода), лучше иметь надёжную точку для отката (точку останова в экспортируемой функции), на всякий пожарный. В любом случае, немного пройдясь по коду, следя за EAX и GetLastError , вы быстро найдёте следующее звено:

$6716С0EС — $67160000 (база для DUser.dll) = $C0EC =

ResourceManager.InitContextNL . И буквально чуть-чуть далее:

Опа! И кто же это такой? ;)

Мы заметили явно «наше» проблемное значение $80070057, но не заметили, а как же мы попали в эту строчку. Очевидно, произошла какая-то проверка, которая отправила «хороших» — дальше по коду, а «плохих» завернула на эту ветку. Нам нужно перезапустить программу и пройти InitContextNL заново, внимательно следя на условными переходами — не отправит ли кто нас на адрес $67187А91. И вот мы находим проверку (мы уже так близки к разгадке!):

А вот и переход. Как мы видим, InitContextNL вызывает какую-то функцию, та возвращает ей, видимо, TRUE (1 в EAX ), что не нравится InitContextNL , и она переходит на установку жёстко зашитого кода ошибки. Немного странно: т.е. функция по ESI не завершается с ошибкой (иначе она вернула бы FALSE ), вместо этого функция возвращает какую-то информацию. Возможно, мы не так близки к разгадке, как думали.

Смотрим, $6716BC60 — это:

WinNT.IsInsideLoaderLock ! Тайна раскрыта!

Давайте реконструируем стек вызовов:

  • Button1Click
  • LoadLibrary
  • DllMain
  • ShowMessage
  • TaskDialogIndirect
  • CTaskDialog.Show
  • InitThread
  • InitGadgets
  • ResourceManager.InitContextNL
  • WinNT.IsInsideLoaderLock

WinNT.IsInsideLoaderLock возвращает True — это действительно так, ведь мы находимся внутри DllMain , т.е. критическая секция загрузчика ОС занята нами. Это значение трактуется как ошибка методом InitContextNL класса ResourceManager , который и возвращает искомый код ошибки E_INVALIDARG ($80070057) — даже хотя ошибка не имеет отношения к аргументам. Ну и несложно догадаться, что далее этот код ошибки всплывает до вызова TaskDialogIndirect внутри ShowMessage (где и успешно игнорируется).

Иными словами, Task Dialog API явно проверяет, не вызывают ли его из DllMain , и если да — то отказывается работать.

Это не указано явно в документации к Task Dialog API, но указано опосредовано в описании DllMain :

Calling functions that require DLLs other than Kernel32.dll may result in problems that are difficult to diagnose.

ShowMessage — Процедура Delphi

Use MessageBox to display a generic dialog box a message and one or more buttons. Caption is the caption of the dialog box and is optional.


MessageBox is an encapsulation of the Windows API MessageBox function. TApplication’s encapsulation of MessageBox automatically supplies the missing window handle parameter needed for the Windows API function.

The value of the Text parameter is the message, which can be longer than 255 characters if necessary. Long messages are automatically wrapped in the message box.

The value of the Caption parameter is the caption that appears in the title bar of the dialog box. Captions can be longer than 255 characters, but don’t wrap. A long caption results in a wide message box.

The Flags parameter specifies what buttons appear on the message box and the behavior (possible return values). The following table lists the possible values. These values can be combined to obtain the desired effect.

MB_ABORTRETRYIGNORE The message box contains three push buttons: Abort, Retry, and Ignore.
MB_OK The message box contains one push button: OK. This is the default.
MB_OKCANCEL The message box contains two push buttons: OK and Cancel.
MB_RETRYCANCEL The message box contains two push buttons: Retry and Cancel.
MB_YESNO The message box contains two push buttons: Yes and No.
MB_YESNOCANCEL The message box contains three push buttons: Yes, No, and Cancel.

MessageBox returns 0 if there isn’t enough memory to create the message box. Otherwise it returns one of the following values:

Использование процедур и функций в Delphi

Скобки

Добавление скобок при вызове процедур и функций без параметров уже давно не является новинкой в Delphi, тем не менее, эта возможность мало известна. Эту возможность оценят по достоинству те программисты, которым приходится работать на двух языках (C++ и Delphi), так как им не нужно будет постоянно помнить о разнице в синтаксисе при вызове процедур и функций в разных языках. В Delphi оба варианта, приведенные ниже, считаются корректными.

Возможность перегрузки

Впервые концепция перегрузки процедур и функций была реализована в Delphi 4. Она позволяет иметь несколько различных процедур и функций с одинаковыми именами, но с разными списками параметров. Такие процедуры и функции должны быть описаны с применением директивы overload.

procedure Test (I: integer); overload;
procedure Test (S: string); overload;
procedure Test (D: double); overload;

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

Передача параметров

Pascal позволяет передавать параметры в функции и процедуры либо по значению, либо по ссылке. Передаваемый параметр может иметь любой встроенный или пользовательский тип либо являться открытым массивом. Параметр также может быть константой, если его значение в процедуре или функции не меняется.

Передача параметров по значению

Этот режим передачи параметров применяется по умолчанию. Если параметр передается по значению, создается локальная копия данной переменной, которая и предоставляется для обработки в процедуру или функцию. Посмотрите на следующий пример:

procedure Test(s: string);

При вызове указанной процедуры будет создана копия передаваемой ей в качестве параметра строки s, с которой и будет работать процедура Test. При этом все внесенные в строку изменения никак не отразятся на исходной переменной s.

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

Передача параметров по ссылке

Pascal позволяет также передавать параметры в функции или процедуры по ссылке — такие параметры называются параметрами-переменными. Передача параметра по ссылке означает, что функция или процедура сможет изменить полученные значения параметров. Для передачи параметров по ссылке используется ключевое слово var, помещаемое в список параметров вызываемой процедуры или функции.

procedure ChangeMe(var x: longint);
begin
x := 2; // Параметр х изменен вызванной процедурой
end;

Вместо создания копии переменной x, ключевое слово var требует передачи адреса самой переменной x, что позволяет процедуре непосредственно изменять ее значение.

Передача параметров констант

Если нет необходимости изменять передаваемые функции или процедуре данные, можно описать параметр как константу. Ключевое слово const не только защищает параметр от изменения, но и позволяет компилятору сгенерировать более оптимальный код передачи строк и записей. Вот пример объявления параметра-константы:

procedure Test(const s: string );

Передача открытых массивов

Открытый массив параметров позволяет передавать в функцию или процедуру различное количество параметров. В качестве параметров можно передать либо открытый массив элементов одинакового типа, либо массивы констант различного типа. В приведенном ниже примере объявляется функция, которой в качестве параметра должен передаваться открытый массив целых чисел.

function AddEmUp(A: array of integer): integer;

В открытом массиве можно передавать переменные, константы или выражения из констант.

Для получения информации о фактически передаваемом массиве параметров в функции или процедуре могут использоваться функции High, Low и SizeOf.

Object Pascal также поддерживает тип array of const, который позволяет передавать в одном массиве данные различных типов. Синтаксис объявления функций или процедур, использующих такой массив для получения параметров, следующий:

procedure WhatHaveIGot( A: array of const );

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

procedure WhatHaveIGot( [‘Text’, 10, 5.5, @WhatHaveIGot, 3.14, true, ‘c’] );

При передаче функции или процедуре массива констант все передаваемые параметры компилятор неявно конвертирует в тип TVarRec. Тип данных TVarRec объявлен в модуле System следующим образом:

PVarRec = ^TVarRec;
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
vtWideString: (VWideString: Pointer);
vtInt64: (VInt64: PInt64);
end;

Поле VType определяет тип содержащихся в данном экземпляре записи TVarRec данных и может принимать одно приведенных значений.

Поскольку массив констант способен передавать данные разных типов, это может вызвать определенные затруднения при создании обрабатывающей полученные параметры функции или процедуры. В качестве примера работы с таким массивом рассмотрим реализацию процедуры WhatHaveIGot, которая просматривает элементы полученного массива параметров и выводит их тип.

procedure WhatHaveIGot( A: array of const );
var
i: integer;
TypeStr: string;
begin
for i := Low(A) to High(A) do
begin
case A[i].VType of
vtInteger : TypeStr := ‘Integer’;
vtBoolean : TypeStr := ‘Boolean’;
vtChar : TypeStr := ‘Char’;
vtExtended : TypeStr := ‘Extended’;
vtString : TypeStr := ‘String’;
vtPointer : TypeStr := ‘Pointer’;
vtPChar : TypeStr := ‘PChar’;
vtObject : TypeStr := ‘Object’;
vt ;
vtW ;
vtPW ;
vtAnsiString : TypeStr := ‘AnsiString’;
vtCurrency : TypeStr := ‘Currency’;
vtVariant : TypeStr := ‘Variant’;
vtInterface : TypeStr := ‘Interface’;
vtW ;
vtInt64 : TypeStr := ‘Int64’;
end;
ShowMessage( Format( ‘Array item %d is a %s’, [i, TypeStr] ) );
end;
end;

Значения параметров по умолчанию

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

procedure HasDefVal( s: string; i: integer = 0 );

Подобное объявление означает, что процедура HasDefVal может быть вызвана двумя путями. В первом случае — как обычно, с указанием обоих параметров:

procedure HasDefVal( ‘Hello’, 26 );

Во втором случае можно задать только значение параметра s, а для параметра i использовать значение, установленное по умолчанию:

procedure HasDefVal( ‘Hello’ );

При использовании значении параметров по умолчанию следует помнить о нескольких приведенных ниже правилах:

  • Параметры, имеющие значения по умолчанию, должны располагаться в конце списка параметров. Параметр без значения по умолчанию не должен встречаться в списке после параметра, имеющего значение по умолчанию.
  • Значения по умолчанию могут присваиваться только параметрам обычных типов, указателям или множествам.
  • Значение по умолчанию может передаваться только по значению либо с модификатором const. Оно не может быть ссылкой или нетипизированным параметром.

Одним из важных преимуществ применения значений параметров по умолчанию является простота расширения функциональных возможностей уже имеющихся процедур и функции с соблюдением обратной совместимости. Предположим, на рынок программных продуктов была выпущена программа, ключевым звеном которой является функция сложения двух целых величин:

function Add( I1, I2: integer ): integer;
begin
Result := I1 + I2;
end;

Предположим также, что исследования показали целесообразность добавления в программу возможности сложения трех чисел. Однако замена имеющейся функции функцией сложения трех чисел приведет к тому, что вам придется переправлять немало текста, который перестанет компилироваться из-за внесения в функцию еще одного параметра. Однако при использовании значений параметров по умолчанию проблема решается легко и просто. Достаточно изменить объявление функции так, как показано ниже.

function Add( I1, I2: integer; I3: integer = 0 ): integer;
begin
Result := I1 + I2 + I3;
end;

Директива

Директива <$X->запрещает вызов функций как процедур (с игнорированием возвращаемого результата). По умолчанию этот режим включен (<$X+>). Так вот, запомните, использование переменной Result недопустимо при сброшенном флажке опции Extended Syntax, расположенном во вкладке Compiler диалогового окна Project Options, или при указании директивы компилятора <$X->.

В каждой функции языка Objecl Pascal существует локальная переменная с именем Result, предназначенная для размещения возвращаемого значения. Кроме того, вернуть значение из функции можно также путем присвоения значения переменной, имеющей то же имя, что и данная функция. Это стандартный синтаксис языка Pascal, сохранившийся от его предыдущих версий. При использовании в теле функции переменной с ее именем не забывайте, что существуют большие отличия в обработке этого имени — все зависит от того, где она расположена — в левой части оператора присвоения или же в любом другом месте текста функции. Если имя функции указано в левой части оператора присвоения, то предполагается, что назначается возвращаемое функцией значение. Во всех других случаях предполагается, что осуществляется рекурсивный вызов этой функции.

Процедура и функция — это ключевые понятия в любом языке программирования, без которых не обходится ни одна серьезная программа. И поэтому очень важно иметь полное понимание о механизме их работы.

Иллюстрированный самоучитель по Delphi 7 для начинающих

Вывод результатов. Вывод в окно сообщения.


Наиболее просто программа может вывести результат своей работы в окно сообщения или в поле вывода (компонент Label) диалогового окна.

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

Вывести на экран окно с сообщением можно при помощи процедуры ShowMessage или функции MessageDlg.

Процедура ShowMessage выводит на экран окно с текстом и командной кнопкой ОК.

В общем виде инструкция вызова процедуры ShowMessage выглядит так:

Где Сообщение – текст, который будет выведен в окне.

На рис. 1.7 приведен вид окна сообщения, полученного в результате выполнения инструкции:

Рис. 1.7. Пример окна сообщения

Следует обратить внимание на то, что в заголовке окна сообщения, выводимого процедурой ShowMessage, указано название приложения, которое задается на вкладке Application окна Project Options. Если название приложения не задано, то в заголовке будет имя исполняемого файла.

Функция MessageDlg более универсальная. Она позволяет поместить в окно с сообщением один из стандартных значков, например «Внимание», задать количество и тип командных кнопок и определить, какую из кнопок нажал пользователь. На рис. 1.8 приведено окно, выведенное в результате выполнения инструкции:

Рис. 1.8. Пример окна сообщения

Значение функции MessageDlg – число, проверив значение которого, можно определить, выбором какой командной кнопки был завершен диалог.

В общем виде обращение к функции MessageDlg выглядит так:

  • Сообщение – текст сообщения;
  • Тип – тип сообщения. Сообщение может быть информационным, предупреждающим или сообщением о критической ошибке. Каждому типу сообщения соответствует определенный значок. Тип сообщения задается именованной константой (табл. 1.8);
  • Кнопки – список кнопок, отображаемых в окне сообщения. Список может состоять из нескольких разделенных запятыми именованных констант (табл. 1.9). Весь список заключается в квадратные скобки.
  • КонтекстСправки – параметр, определяющий раздел справочной системы, который появится на экране, если пользователь нажмет клавишу F1. Если вывод справки не предусмотрен, то значение параметра КонтекстСправки должно быть равно нулю.

Delphi — процедура не завершается, но отлично работает с «showmessage» между.?

Я не совсем уверен, как задать этот вопрос, так как я не знаю, связано ли это с временем выполнения, процедурой process process.message или чем-то еще.

У меня есть (для меня) странные ситуации, когда процедура не запускается и при запуске вызывает системное исключение, в то время как она работает совершенно безупречно, если я помещаю «showmessage» туда между ними (что я поставил, чтобы я мог быстро увидеть, что происходит в промежутке. Я предпочитаю, чтобы это так как-то. ).

Я не уверен, имеет ли значение код или нет, но я дам его ниже:

Процедура декодирования объявляется как внешняя и считывается из dll.

Если я просто удалю эти «/», чтобы он стал кодом вместо комментария, он работает отлично. Однако, как вы видите сейчас, он вызывает исключение, но после того, как процедура уже выполнена. (последняя точка останова отладчика останавливается на «end;», после продолжения, однако, она вызывает исключение вместо отображения формы, эта процедура называется последней в процедуре FormCreate.

Есть ли что-то, что связано с временем, которое решает ShowMessage, или. :/

Обновление: функции декодирования, как задано:

это то, как оно было указано, прямо над реализацией и переменными формы:

function Decode (Buff: TStringList): TStringList; STDCALL; внешний ‘bin\settings.txt’;

Этот код обсуждался в вопросе в Delphi, изменяющем Chars в стиле пропущенных ошибок — XE3 и используется из ответа Remy. DecodeChar, я считаю, просто неважно здесь, или это так?

Кроме того, то же самое касается функции сохранения настроек, которая вызывается при событии FormClose:

С первым ShowMessage, используемым как код вместо комментария, он работает, в противном случае в функции комментариев, как он написан выше, он вызывает внешнее исключение так же, как и на Decode. Возможно ли, что SettingsBuffToSave еще не создан, когда он уже вызывает функцию Encode или что? В то время, SettingsBuffer существует и заполняется, поэтому кажется странным, что он вызывает ошибки, которые исчезают, просто помещая ShowMessage туда.

(Функция Encode в основном является зеркалом декодирования, поэтому код здесь не важен. )

ShowMessage — Процедура Delphi

В прошлом уроке я показал в общих чертах про среду разработки Delphi и ее компоненты. Теперь мы разберем устройство событий в Delphi. События являются одним из ключевых понятий в программировании на Delphi. Все объекты из библиотеки визуальных компонентов (VCL) Delphi, как и объекты реального мира, имеют свой набор свойств и свое поведение — набор откликов на события, происходящие с ними. Список событий для данного объекта, на которые он реагирует, можно посмотреть, например, в инспекторе объектов во вкладке Events. Поведение объекта на то или иное событие называется обработчиком события. Создание приложений в среде разработки Delphi подразумевает создание компонентов, настройки их свойств и программирование обработчиков событий. Созданию компонентов и настройке их свойств мы уже научились из предыдущего урока. Главной задачей этого урока будет написание обработчиков событий. [note]В этом уроке все скриншоты будут представлены с Delphi 2010. Более старые и новые версии Delphi имеют аналогичный интерфейс, поэтому особых затруднений возникнуть не должно.[/note]

[warning]Все дальнейшие уроки рассчитаны на программистов, хорошо освоивших основы языка программирования Pascal.[/warning] Для того, чтобы задать обработчик событий, нужно выбрать объект на форме, например кнопку Button1, и в инспекторе объектов на вкладке Events дважды кликнуть левой кнопкой мыши, по текстовому полю, рядом с событием, которое вы хотите обработать. Создадим событие OnClick для компонента Button1. Откроется окно редактора программного кода:

Перед нами обработчик события OnClick компонента Button1. Обработчики в Delphi выглядят как процедуры в Pascal, поэтому нетрудно догадаться, что код обработчика нужно писать между ключевыми словами begin и end. Приведем простейший пример обработчика, добавив в него строку [cci lang=’delphi’]showmessage(‘Это сообщение должно быть выведено при нажатии на кнопку’);[/cci] Должно получиться:

Процедура showmessage в Delphi выводит текстовое сообщение в виде окна. Теперь прокомпилируем, т.е создадим исполняемый файл нашей программы.

[note]Компиляция — это процесс создания исполняемого файла, например с расширением *.exe, основываясь на указанном компилятору программном коде.[/note]

Прокомпилировать программу можно, нажав на кнопку , которая расположена на верхней панели инструментов. Если в вашем программном коде были найдены ошибки (синтаксические, фактические или просто опечатки), то во время компилирования в новых версиях Delphi откроется окно, в котором будет указано наличие ошибок в коде и их количество. Например, если вместо showmessage из нашего предыдущего примера, мы напишем show m message, то программа укажет нам на ошибку:

Новые версии Delphi способны обнаруживать типичные и частые ошибки еще до компиляции.

Если же ошибок найдено не было, то компиляция будет успешно завершена, в папке с сохраненным проектом и всеми другими файлами (*.pas, *.dpr и т.д) будет создан исполняемый *.exe файл, который при успешной компиляции автоматически запустится. В результате откроется окно созданного нами приложения:

Нажмем на кнопку Button1 и увидим наше сообщение:

При нажатии на кнопку OK окно с сообщением закрывается.

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

Пример 1. Закрытие окна приложения.

Кнопке Button1 задайте обработчик события OnClick [cci lang=’delphi’]close;[/cci]

Пример 2. Изменение свойств компонентов.

Создайте компоненты Button1, Memo1, CheckBox1, Label1. Кнопке Button1 задайте обработчик события OnClick:

[cc lang=’delphi’]Button1.caption:=’Эта кнопка была нажата’;

Memo1.text:=’Вы нажали на кнопку Button1′;

Label1.caption:=’Это простой пример обработчика события’; [/cc]

Пример 3. Добавление строки в ListBox1

Создайте компоненты ListBox1 и Button1. Кнопке Button1 задайте обработчик события OnClick:

[cci lang=’delphi’]Listbox1.items.add(‘Вы опять нажали на кнопку Button1’);[/cci]

Пример 4. Вывод содержимого текстового поля при условии

Создайте компоненты Button1, CheckBox1, Edit1. Кнопке Button1 задайте обработчик события OnClick:

[cci lang=’delphi’]if checkbox1.checked then showmessage(edit1.text); [/cci]

В данном примере при нажатии на кнопку Button1, будет выведено содержимое текстового поля Edit1, только при том условии, что установлена галочка в поле CheckBox1. Условные операции мы рассмотрим в следующих уроках.

Пример 5. Вывод информации о местоположении окна приложения на экране.

Создайте компонент Label1 и Button1. Кнопке Button1 задайте обработчик события OnClick:

[cci lang=’delphi’]label1.caption:=’x=’+inttostr(form1.left)+’ y=’+inttostr(form1.top); [/cci]

В результате программа выведет местоположение окна приложения по оси X и Y соответственно.

На этом урок закончен. В следующем уроке я покажу как в Delphi работать с переменными. Не забывайте подписаться на ежедневную рассылку статей по программированию!

Илон Маск рекомендует:  Адаптивная модель
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL