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


Содержание

Диалоговые окна в Дельфи (процедуры и функции, реализующие диалоговые окна)

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

Процедура ShowMessage, функции MessageDlg и MessageDlgPos отображают окно (панель) вывода сообщений, а функции InputBox и InputQuery — окно (панель) для ввода информации.

Процедура ShowMessage

Процедура ShowMessage (const Msg: String) отображает окно сообщения с кнопко ОК. Заголовок содержит название исполняемого файла приложения, а строка Msg выводится как текст сообщения.

Функция MessageDlg

Функция MessageDlg(const Msg: String; AType: TMsgDlgType; AButtons: TMsgDlgButtons; Helpctx: Longint) : word отображает окно сообщения в центре экрана и позволяет получить ответ пользователя. Параметр Msg содержит отображаемое сообщение.

Окно сообщения может относиться к различным типам и наряду с сообщением содержать картинки. Тип окна сообщения определяется параметром АТуре, который может принимать следующие значения:

  • mtWarning (окно содержит черный восклицательный знак в желтом треугольнике и заголовок Warning);
  • mtError (окно содержит белый косой крест в красном круге и заголовок Error);
  • mtInformation (окно содержит синюю букву «i» в белом круге и заголовок Information);
  • mtConfirmation (окно содержит синий знак «?» в белом круге и заголовок Confirmation);
  • mtCustom (окно не содержит картинки, в заголовке выводится название исполняемого файла приложения).

Параметр AButtons задает набор кнопок окна и может принимать любые комбинации следующих значений:

  • mbYes (кнопка Yes);
  • mbAbort (кнопка Abort);
  • mbNo (кнопка No);
  • mbRetry (кнопка Retry);
  • mbOk (кнопка OK);
  • mbIgnore (кнопка Ignore);
  • mbCancel (кнопка Cancel);
  • mbAll (кнопка All);
  • mbHelp (кнопка Help);

Для значения параметра AButtons имеются две константы — mbYesNoCancel и mbOKCancel, задающие предопределенные наборы кнопок:

  • mbYesNoCancel = [mbYes, mbNo, mbCancel];
  • mbOKCancel = [mbOK, mbCancel]

При нажатии любой из указанных кнопок (кроме кнопки Help) диалоговое окно закрывается, а результат (свойство ModalResult) возвращается функцией MessageDlg.

Параметр HelpCtx определяет контекст (тему) справки, которая появляется во время отображения диалогового окна при нажатии пользователем клавиши . Обычно! значение этого параметра равно нулю.

Пример использования функции MessageDlg:

При нажатии кнопки Button1 производится простейшая проверка даты. Код даты вводится в поле редактирования DateEdit1, размещенное в форме. Если длина даты меньше допустимой, выдается предупреждение с запросом на автоматическую коррекцию (см. скриншот). При утвердительном ответе пользователя в поле даты записывается текущая дата, при отрицательном — фокус передается полю ввода даты.

Функция MessageDlgPos

Функция MessageDlgPos(const Msg: String; AType: TMsgDlgType; AButtons: TMsgDlgButtons; HelpCtx: Longint; X, Y: Integer) : Word отличается от функции MessageDlg наличием параметров Х и Y, управляющих положением окна на экране.

Функция InputBox

Функция InputBox(const ACaption, APrompt, ADefault: String): String отображает диалоговое окно для ввода строки текста. Окно выводится в центре экрана и содержит поле ввода с надписью, а также кнопки ОК и Cancel.

Параметр ACaption задает заголовок окна, а параметр APrompt содержит поясняющий текст к полю ввода. Параметр ADefault определяет строку, возвращаемую функцией при отказе пользователя от ввода информации (нажатие кнопки Cancel или клавиши ).

Пример использования функции InputBox:

Приведенная процедура отображает окно запроса на ввод фамилии пользователя (см. скриншот).

По умолчанию предлагается Иванов.

Функция InputQuery

Функция InputQuery (const ACaption, APrompt: String; var Value: String): Boolean отличается от функции InputBox тем, что вместо третьего параметра— строки по умолчанию— используется параметр Value, который в случае подтверждения ввода содержит введенную пользователем строку.

В качестве результата функция возвращает логическое значение, позволяющее определить, каким образом завершен диалог. Если нажата кнопка ОК, то функция возвращает значение True, если нажата кнопка Cancel или клавиша — значение False.

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

Кроме рассмотренных диалоговых окон, в Delphi имеется ряд других специализированных диалоговых окон, например диалоговое окно выбора каталога, вызываемое функцией Select Directory модуля FileCtrl.

Vcl.Dialogs.ShowMessage

Contents

Properties

Description

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).

Note: To display a message in a message box with other buttons, or with an icon, use the MessageDlg function.

Note: If the user types Ctrl+C in the message box, the text of the message is copied to the clipboard.

Блог 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.

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

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


Главное меню

Функции и процедуры

Краткий обзор как использовать различные типы процедур и функций Delphi, указателей на функции и процедуры, методы и указатели методов, анонимные методы.

Автономные процедуры

Более или менее все RTL процедуры — так называемые «автономные» процедуры. Они объявлены на уровне модулей и чтобы вызвать такую процедуру, Вы должны знать, где она объявлена (имя модуля и список параметров для передачи).

Пример — процедура ShowMessage, объявленная в модуле dialogs.pas, вот ее объявление:

Чтобы вызвать такую процедуру, Вы просто должны включить имя модуля, где объявлена эта процедура в раздел uses вызывающего модуля. Большинство общих модулей уже перечислено в пункте uses модуля формы, таким образом, Вы не должны вручную добавлять dialogs.pas, чтобы использовать ShowMessage.

Вложенные процедуры

Функции и процедуры иногда содержат другие функции и процедуры в пределах своих блоков:

Вышеупомянутые два типа объявления процедур не связаны с классом или объектом.

Методы

Процедуры, которые связаны с классом, называют методами. Большинство методов используют экземпляры класса — объекты. Некоторые методы, называемые методами класса, работают в классах.

Функция Add — метод класса TStringList. Например, метод Add объявляется как:

Процедурные типы и указатели процедур

Delphi позволяет обрабатывать подпрограммы (функции и процедуры) как значения, которые могут быть назначены переменным.

Рассмотрим отрывок кода:

В этом коде TDisplayerProc и TIntegerDisplayerFuncпроцедурные типы

Переменные dp и idp — указатели процедур. Обратите внимание, что функция DisplayDoubleInteger получает процедурный тип, как второй параметр (TDisplayerProc).

Указатели методов

Подобно указателям процедур, указатели методов ссылаются на методы экземпляра объекта.

Анонимные методы

Начиная с Delphi 2006, язык Delphi знает также анонимные методы.

Анонимный метод — процедура или функция, которая не связана с ассоциированным именем.

Пример анонимных методов: сортировка объекта TList.

Использование процедур и функций в 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, сохранившийся от его предыдущих версий. При использовании в теле функции переменной с ее именем не забывайте, что существуют большие отличия в обработке этого имени — все зависит от того, где она расположена — в левой части оператора присвоения или же в любом другом месте текста функции. Если имя функции указано в левой части оператора присвоения, то предполагается, что назначается возвращаемое функцией значение. Во всех других случаях предполагается, что осуществляется рекурсивный вызов этой функции.

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

ShowMessagePos — Процедура 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. Этот способ позволяет перед выводом на экран динамически изменить параметры функции. Вот как, пользуясь этим способом, можно русифицировать запрос на сохранение текста в предыдущей программе:

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 в основном является зеркалом декодирования, поэтому код здесь не важен. )

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

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

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

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

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

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

Есть ли что-то, что связано с синхронизацией, которую решает ShowMessage, или . : /

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

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

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

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

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

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

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

Delphi7, Showmessage command is executed before the application form appears

I was wondering why the command «showmessage» is executed before the application form appears, I mean, whenever I run the program, first appears the message, then the application form:

The first thing delphi does, it to show the message «Hi». Then it does the rest (Form appeared, hide buttons, load images etc). Even though showmessage is last, it is executed first. How do I make the message appear after the form is appeared, the buttons are hiden are the image is loaded?

1 Answer 1

The reason is that the form is created (hence, OnCreate is fired), before it is shown.

Solution 1

One solution is to post a window message to the form when the form is created. Try this:

Solution 2

A different solution is to make use of the OnActivate event, which is called every time the form obtains keyboard focus: Add a private field FMessageShown: boolean to the form class. Then, in OnActivate , if the flag is false (as it is by default, being a field of a class), then display your message and set the flag to true:

In practice, both methods work perfectly. The downside of the first solution is that it may rely somewhat on ‘implementation details’, while the downside of the latter one is quite obvious: you check a flag every time the form regets keyboard focus, even weeks after the form was initially created and the message was shown.

Solution 3

A solution that has neither disadvantage, but assumes that you won’t need the OnActivate event for some other purpouse, is simply to ‘unassign’ the event after its first (hence, only) execution:

(This approach, however, can be extended to cases where you do need the event for other purpouses too, if you replace OnActivate := nil by OnActivate := MySecondEventHandler .)

Работа с диалогами InputQuery, InputBox в Delphi 10.1 Berlin

Не прекращаю попыток в свободное от основной работы время разрабатывать небольшое приложение в Delphi для Andro >

Для начала, немного официальной документации. Что было помечено директивой deprecated и что предлагается использовать взамен:

Type Visibility Source Unit Parent
Deprecated Members New Members
  • FMX.Dialogs.ShowMessage
  • FMX.Dialogs.ShowMessageFmt
  • FMX.Dialogs.ShowMessagePos
  • ShowMessageAsync
  • ShowMessageSync
  • FMX.Dialogs.MessageDlg
  • FMX.Dialogs.MessageDlgPos
  • FMX.Dialogs.MessageDlgPosHelp
  • FMX.Platform.IFMXDialogService.MessageDialog
  • MessageDialogAsync
  • MessageDialogSync
  • FMX.Dialogs.InputBox
  • FMX.Dialogs.InputQuery
  • FMX.Platform.IFMXDialogService.InputQuery
  • InputQueryAsync
  • InputQuerySync

При этом методы ShowMessage и ShowMessageFmt, несмотря на их наличие в разделе deprecated members, не являются таковыми, однако нам рекомендуется использовать вместо них аналогичный синхронный или асинхронный вариант. Хорошо, посмотрим, как использовать нововведение.

Модуль FMX.DialogService

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

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