Output — Переменная Delphi

Содержание

Output — Переменная Delphi

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

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

Место описания данных в программе — вне логических блоков begin / end. В модуле перед ключевым словом implementation есть блок описания:

Именно здесь, начиная со следующей строки, удобно объявлять глобальные переменные и константы. Как видим, одна (Form1) уже есть!

Команда объявления переменных в языке Delphi:

var имя_переменной : тип_переменной ;

Слово var — ключевое. Именем может быть любой идентификатор, если он не был описан ранее и не является одним из ключевых или зарезервированных слов языка Delphi. Если нужно описать несколько переменных одного типа, то их перечисляют, отделяя запятой:

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

Постоянную величину иначе называют константой. Конечно, в программе можно использовать числа и строки непосредственно: 3.1415 или ‘Это значение числа пи’ , но иногда удобнее присвоить их идентификатору. Описание констант аналогично описанию переменных, но используется ключевое слово const, за именем идентификатора следует тип, затем знак равенства и его значение. Причём тип константы допускается не указывать:

const pi= 3.1415 ;
ZnakPi : String = ‘Это значение числа пи’ ;

К слову, константа Pi встроенная в Delphi, то есть для того чтобы использовать в Delphi число 3,1415. в расчётах, нужно просто присвоить встроенную константу Pi переменной типа Real или просто использовать непосредственно в выражениях.

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

Строкой называется последовательность символов, заключённая в одиночные кавычки:
‘это текстовая строка’ Если текст должен содержать сам символ кавычки, то его надо повторить дважды:
‘это » — символ одиночной кавычки’ Строка может быть и пустой, не содержащей символов. Тогда она состоит из двух идущих друг за другом без пробела кавычек. Естественно, строка может состоять и только из одних пробелов.
Самый популярный строковый тип — String. Строка типа String может содержать переменное количество символов объёмом до 2 Гбайт. Если нужно ограничить размер строки фиксированным значением, то после ключевого слова String в квадратных скобках указывается число, определяющее количество символов в строке: String[50]. Более полно работа со строками Delphi описывается далее.
Одиночный символ имеет тип Char и записывается в виде знака в одиночных кавычках: ‘a’. Есть символы, которые на экране отобразить невозможно, например, символ конца строки (равен #13), символ переноса строки (равен #10). Такие символы записываются в виде их числового кода (в кодировке ANSI), перед которым стоит знак #. Например, #0.
Наконец, существуют так называемые нуль-терминированные строки. Отсчёт символов в таких строках начинается с нуля, а заканчивается символом с кодом (#0). Такие строки имеют тип PChar.

Числа бывают целые и дробные.
В следующей таблице перечислены стандартные типы целых чисел и соответствующие им дипазоны допустимых значений.

Integer -2147483648 .. +2147483647
Cardinal 0 .. 4294967295
Shortint -128 .. +127
Smallint -32768 .. +32767
Int64 -2 63 .. +2 63 -1
Byte 0 .. +255
Word 0 .. +65535
Наиболее удобным для использования в программах является тип Delphi Integer. Другие целые типы используются для уменьшения места, занимаемого данными в памяти компьютера.

Дробные числа имеют дробную часть, отделяемую десятичной точкой. Допускается использование символа e (или E), за которым следует число, указывающее, что левую часть нужно умножить на 10 в соответствующей степени: 5e25 — пять умножить на десять в двадцать пятой степени.
Ниже приведены стандартные типы дробных чисел и соответствующие им диапазоны допустимых значений. Для большинства типов указан диапазон положительных значений, однако допустимым является аналогичный диапазон отрицательных значений, а также число .

Real 5*10 -324 .. 1.7*10 308
Real48 2.9*10 -39 .. 1.7*10 38
Singl 1.5*10 -45 .. 3.4*10 38
Double 5*10 -324 .. 1.7*10 308
Extended 3.6*10 -4951 .. 1.1*10 4932 -1
Comp -2 63 .. +2 63 -1
Currency 922337203685477.5807
Наиболее удобным для использования в программах является тип Delphi Real. Ему эквивилентен тип Double, но в будущем это может быть изменено. Вычисления с дробными числами выполняются приближённо, за исключением типа Currency (финансовый), который предназначен для минимизации ошибок округления в бухгалтерских расчётах.

Следующим типом данных является логический Boolean, состоящий всего из двух значений: True (Истина) и False (Ложь). При этом True > False.

Теперь, используя компоненты, их свойства и события, вводя собственные переменные, можно конструировать программы, содержащие вычисления. Осталось узнать, как вычисленное значение вывести на экран.
Про консольные программы я здесь не говорю! А в нормальных оконных Windows-приложениях это значение нужно поместить в какой-нибудь компонент, имеющий свойства Text или Caption. Это, например, такие компоненты как Label и Edit, да и сама Форма имеет свойство Caption, куда тоже можно выводить информацию. Однако, в Delphi информацию перед выводом, как правило, необходимо преобразовывать. Так как присвоение возможно только между переменными одного типа, то такая программа (не пытайтесь её исполнять):

var A, B, C: Integer ;
begin
A := 5 ;
B := 10 ;
C := A+B ;
Label1.Caption := C ;
end ;

вызовет ошибку, так как свойство Caption имеет текстовый тип String, а использованные переменные — цифровой тип Integer. Значит, нужно преобразовать значение переменной C в текстовый тип. Для этого есть встроенная функция IntToStr. Строка в нашей «программе», вызывавшая ошибку, должна выглядеть так:

Такая программа, кроме показа числа 15, ни на что не способна. Мы должны научиться вводить в программу другие числа. Используем компоненты Edit. Введённые числа будут содержаться в свойстве Text этих компонентов. Расположим на форме два компонента Edit, один компонент Label и кнопку Button, по нажатию на которую и будем проводить вычисления. В компоненты Edit1 и Edit2 будем вводить числа для суммирования. Чтобы переместиться в редактор кода, щёлкнем дважды по нашей кнопке Button1. Мы попадём прямо в сформированную для нас средой Delphi заготовку обработчика нажатия на кнопку, непосредственно между операторами begin и end. Напишем такой простой код:

procedure TForm1.Button1Click(Sender: TObject);
var A, B, C: Integer; //Не забудьте описание переменных
begin
//Начало кода:
A := Edit1.Text;
B := Edit2.Text;
C := A+B;
Label1.Caption := IntToStr(C);
//Конец кода
end ;

При попытке исполнить этот код Delphi покажет ошибки по аналогичной причине — переменные A и B имеют цифровой тип Integer, а свойство Text — текстовый тип String. Исправить ошибки поможет встроенная функция StrToInt, выполняющая обратное преобразование — текст в целое число. Операторы присвоения переменным A и B должны выглядеть так:

A := StrToInt(Edit1.Text);
B := StrToInt(Edit2.Text);

В данном случае переменные A, B, C использовались для наглядности. Можно обойтись одной строчкой:

Аналогично, имеются функции и для преобразования в строку и обратно действительных чисел c плавающей (Floating англ.) запятой, имеющих тип Real. Для преобразования в строку — FloatToStr, обратно — StrToFloat.
Часто результаты вычислений, имеющие тип Delphi Real, имеют после запятой длинный «хвост» цифр. При выводе такой переменной в текстовом виде необходимо ограничить количество цифр после запятой. Как это можно сделать, описывается также в Уроке Delphi Работа со строками Delphi.

Типы переменных Delphi. Глобальные переменные Обучающий материал

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

Глобальные переменные Delphi

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

Следует заметить, что нежелательно использовать в качестве имени переменной счетчик цикла, который затем будет использоваться в программе. Имеется ввиду, что если существует глобальная переменная Delphi “i:Integer”, то эту “i” не нужно использовать в цикле. Такое применение может вызвать замедление цикла и некорректность в работе. К тому же следует помнить, что если нет крайней необходимости в использовании глобальных переменных, то нужно воздержаться от их объявления.

Локальные переменные Delphi

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

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

Динамический тип переменных Delphi

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

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

Delphi/Переменные

Переменная — область оперативной памяти, в которой лежит какое-то значение. Основные типы переменных в Delphi:

  • Integer — целые числа.
  • Real — целые и дробные числа.
  • Boolean — логический тип.
  • Char — символьный тип данных.
  • String — строковой тип данных.

Переменные указываются после ключевого слова var (variable — переменная). Общий вид указывания переменных:

В Delphi есть оператор присваивания — := .Общий вид присваивания:

Но с типом string и char, особое дело. Общий вид присваивания с типом string и char:

Output — Переменная Delphi

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

Вот список основных типов переменных в Delphi:

  • Integer — целые числа из диапазона: -2147483648..+2147483647
  • Shortin — целые числа из диапазона: -128..+127
  • Byte — целые числа из диапазона: 0..+255
  • Real — как целые так и дробные числа из диапазона: 5e-324..1.7e+308
  • Double — схож с типом Real
  • String — строковый тип данных
  • Char — символьный тип данных
  • Bollean — логический тип данных. Может принимать значение True — истина или False — ложь

С теорией мы закончили, теперь давайте откроем Delphi 7 и создадим новый проект. После этого кидаем на форму знакомый нам компонент Button и еще не знакомый Label. Компонент Label эта такая полезная вещь, в которую можно записать какую-нибудь подпись. Например подпись для другого компонента или просто записать в него автора программы. Попробуйте отыскать компонент Label сами, наводя на все компоненты в вкладке Standard и читая всплывающую подсказку. Кому сложно найти, то это четвертый компонент слева, не считая значка курсора.

Я всё сделал и у меня получилось вот так:

Сейчас нам нужно создать событие OnClick на кнопке, я надеюсь, что вы помните, как это делать.
Переменные объявляются между ключевыми словами procedure и begin. Объявление начинается с ключевого слова var, потом пишется имя переменной и через двоеточие её тип. Заканчивается все как всегда точкой с запятой.

Создадим переменную S типа String в процедуре OnClick: После этого между ключевыми словами begin end присвоим переменной значение равное ‘Моя первая переменная’. Присвоение пишется следующим образом. Пишем имя переменной, оператор присвоения := и значение. Если мы записываем информацию типа String, то информация заключается в одинарные кавычки.

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

Синтаксис такой: Разберем этот код подробно. Сначала мы написали Label1, потом пишем точку и в Delphi появляется огромный список со свойствами данного компонента. Можно конечно порыться и отыскать там Caption, но мы будем умнее! Мы, после того как поставили точку, напишем еще букву C и Delphi как бы отсортирует все свойства и найдет все, которые начинаются с буквы C. Первым в списке как раз будет свойство Caption.

Выбираем его из списка и нажимаем на Enter. Заметьте, что мы писали Label1.C, но после того, как нажали Enter, Delphi сам дописал название свойства. Далее опять же идет оператор присвоения и наша переменная.

Вы наверняка спросите: «Зачем же переменная, если можно было написать Label1.Caption:=’Моя первая переменная’;?». Ответ простой. Это нужно затем, что мы изучаем переменные :).
Нет, на самом деле так присваивать тоже можно, но представьте такую ситуацию, что вы написали очень большую, популярную программу и у вас, там в программе, пятидесяти компонентам присваивается одно и тоже значение и вот перед вами встала задача: «Поменять это значение на более универсальное и понятное для пользователя».

Что вы будете делать?

  • В первом случае у вас всем этим компонентам присваивается одна и та же переменная и чтобы изменить всем этим пятидесяти компонентам значение вам просто нужно поменять значение в переменной.
  • Во втором случае вы сидите 20 минут и всё копируете и копируете значение всем пятидесяти компонентам.
Илон Маск рекомендует:  Что такое код globalcompact

Вывод делайте сами.

И так, продолжаем! В общем виде должно быть так: Компилируем нашу программу и нажимаем на Button (батон/кнопку). Сразу же компонент Label вместо Label1 будет показывать Моя первая переменная.

На этом хотелось бы закончить, так как я уже устал писать урок :), но я еще не познакомил вас с типом Integer и как присваивать переменную с таким типом. Вы думаете, что присваивать ее нужно точно так же как и переменную типа String, но вы ошибаетесь.
Дело в том, что свойству Caption вообще у всех компонентов можно присвоить только текстовые значения. Как мы будем присваивать числовой тип если можно только текстовой? Всё проще некуда. Между типами переменных можно как бы переключаться, то есть можно из числового типа сделать текстовой и присвоить его компоненту Label. Этим мы сейчас и займемся.

Для начала нужно начать сначала :). Объявим переменную с именем I и типом Integer, дописав ее к переменной S. Код: Далее присвоим переменной I значение 21. Заметьте, что числовое значение записывается без одинарных кавычек! Теперь присвоим свойству Caption значение переменной I, для этого нужно воспользоваться оператором IntToStr(). Он как бы конвертирует числовой тип в текстовой. В скобках указывается переменная, которую нужно конвертировать.

Общий вид кода: Скомпилируйте программу и вы увидите, что Label будет отображать значение переменной I, то есть 21.

Ну вот и всё! Удачи!
Встретимся в следующем уроке!

Источник: www.thedelphi.ru
Автор: Савельев Александр
Опубликовано: 22 Апреля 2012
Просмотров:

Зарегистрируйтесь или авторизуйтесь, чтобы добавлять комментарии.

Блог GunSmoker-а

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

19 апреля 2009 г.

Настройки проектов в Delphi с точки зрения поиска ошибок

О чём идёт речь

Сначала, давайте посмотрим на них: открываем Project/Options. Нас будут интересовать вкладки Compiling и Linking (в старых версиях Delphi они назывались Compiler и Linker):

На вкладке «Compiler» нас будут интересовать опции «Stack Frames», группа «Debug information», «Local Symbols» и «Symbol reference info», «I/O Checking», «Overflow checking» и «Range checking». На «Linking» — «Map file», «Debug Information» (известная в предыдущих версиях Delphi как «Include TD32 debug info») и «Include remote debug symbols».

Давайте посмотрим, за что отвечают эти опции. А затем — как их лучше бы всего расставить. При этом мы будем рассматривать такие ситуации: обычное приложение и приложение с механизмом диагностики исключений.
Кроме того, настройки проекта могут отличаться, компилируете ли вы приложение для себя или для распространения. В новых версиях Delphi появились профили настроек (Debug и Release, соответственно). Вы можете задать свой набор настроек в каждом профиле, а затем переключаться между ними. В старых Delphi есть только один профиль настроек и вам нужно менять каждую настройку вручную.

Напомним, что при смене любой из опций необходимо сделать полный Build проекту (а не просто Compile).

Что означают эти опции?

Самыми важными настройками являются группа опций «Debug information», «Local Symbols» и «Symbol reference info».

Программа представляет собой набор машинных команд. Текст программы представляет собой текстовый файл. Вопрос: как отладчик узнаёт, когда надо остановиться, если вы поставили бряк на строку в тексте? Где же соответствие между текстовым файлом и набором байт в exe-файле? Вот для такой связи и служит отладочная информация. Это, грубо говоря, набор инструкций типа: «машинные коды с 1056 по 1059 относятся к строке 234 модуля Unit1.pas». Вот с помощью такой информации и работает отладчик. Указанные выше опции отвечают за генерацию отладочной информации для ваших модулей.

Отладочная информация сохраняется вместе с кодом модуля в dcu-файле. Т.е. один и тот же Unit1.pas может быть скомпилирован как с отладочной информацией, так и без неё — в разные dcu файлы. Отладочная информация увеличивает время компиляции, размер dcu-файлов, но не влияет на размер и скорость работы полученного exe-файла (т.е. отладочная информация не подключается к exe-файлу) (*).

Бывают ситуации, когда наличие отладочной информации в файле или (хотя бы) рядом с файлом является необходимым. Например, если вы выполняете удалённую отладку или отладку внешнего процесса. Или если вам нужен читаемый стек вызовов в вашем средстве диагностики исключений.

Подключение отладочной информации к приложению осуществляется несколькими способами: либо это опции проекта (а именно: «Map File», «Debug information» (Linker)/«Include TD32 Debug info» или «Include remote debug symbols»), либо это возможности всевозможных экспертов (типа EurekaLog, JCL или madExcept), которые добавляют отладочную информацию в программу в своём формате.

Итак, опции отладочной информации:

  • «Debug information» (директива <$D+>или <$D->) – это собственно и есть отладочная информация. Т.е. соответствие между текстом программы и её машинным кодом. Вы должны включить эту опцию, если хотите ставить бряки, выполнять пошаговую отладку, а также иметь стек с именами для своего кода. Часто эту опцию включают автоматически различные эксперты типа EurekaLog.
  • «Local symbols» (директива <$L+>или <$L->) – является дополнением к отладочной информации. Она отвечает за соответствие между данными в exe-файле и именами переменных. Если вы включаете эту опцию, то отладчик позволит вам просматривать и изменять переменные. Также окно «Call Stack» будет способно отражать переданные в процедуры параметры.
  • «Reference info» – это дополнительная информация для редактора кода, которая позволяет ему отображать более подробную информацию об идентификаторах. Например, где была объявлена переменная.

Эти три опции тесно связаны и обычно нет смысла включать/выключать их по одной.

  • «Use Debug DCUs» — эта опция переключает сборку вашей программы с отладочными модулями Delphi или с обычными. Если вы внимательно посмотрите на установку Delphi, то увидите, что pas файлы из папки Source никогда не используются для компиляции — а только для отладки. Для компиляции же используются уже готовые dcu-файлы из папки Lib. Это уменьшает время компиляции. Поскольку dcu могут быть скомпилированы с отладочной информацией и без неё, то в папке Lib есть два набора dcu-файлов: обычные и отладочные. Переключая эту опцию, вы указываете, какие из них использовать. Если вы выключите эту опцию, то не сможете заходить по F7 в стандартные функции и методы Delphi (т.к. для них будет отсутствовать отладочная информация). Также при выключенной опции вы не будете видеть информацию о стандартном коде в стеке вызовов.
  • «Stack Frames» — эта опция отвечает за генерацию стековых фреймов. Если опция выключена, то стековый фрейм не генерируется без необходимости. Если она включена -то фрейм генерируется всегда. Стековые фреймы используются при построении стека вызовов по фреймам (построение методом raw-сканирование не нуждается в стековых фреймах). В обычном приложении стековые фреймы генерируются практически всегда (**).
  • «Range checking» — служит помощником в поиске проблем при работе, например, с массивами. Если её включить, то для любого кода, который работает с массивами и строками, компилятор добавляет проверочный код, который следит за правильностью индексов. Если при проверке обнаруживается, что вы вылезаете за границы массива, то будет сгенерировано исключение класса ERangeError. При этом вы можете идентифицировать ошибку обычной отладкой. Если же опция выключена, то никакого дополнительного кода в программу не добавляется. Включение опции немного увеличивает размер программы и замедляет её выполнение. Рекомендуется включать эту опцию только в отладочной версии программы.
  • «Overflow checking» — похожа на опцию «Range checking», только проверочный код добавляется для всех арифметических целочисленных операций. Если результат выполнения такой операции выходит за размерность (происходит переполнение результата), то возбуждается исключение класса EIntOverflow. Пример – к байтовой переменной, равной 255, прибавляется 2. Должно получиться 257, но это число больше того, что помещается в байте, поэтому реальный результат будет равен 1. Это и есть переполнение. Эта опция используется редко по трём причинам. Во-первых, самый разный код может рассчитывать на то, что эта опция выключена (часто это различного рода криптографические операции, подсчёт контрольной суммы и т.п., но не только). В связи с этим при включении этой опции могут начаться совершенно различные проблемы. Во-вторых, в обычных ситуациях работают с четырёхбайтовыми знаковыми величинами, и работа около границ диапазонов представления происходит редко. В-третьих, арифметические операции с целыми – достаточно частый код (в отличие от операций с массивами), и добавление дополнительной работы на каждую операцию иногда может быть заметно (в смысле производительности).
  • «I/O Checking» — эта опция используется только при работе с файлами в стиле Паскаля, которые считаются устаревшими. По-хорошему, вы не должны использовать их и, соответственно, эту опцию.

Замечу также, что эти опции можно выставлять и локально — как для целого модуля, так и для отдельной функции/процедуры (а для некоторых опций — даже для участка кода). Делается это обычными директивами компилятора, узнать которые вы можете, нажав F1 в окне настроек. Например, «Stack Frames» регулируется <$W+>и <$W->.

Кроме того, помимо настроек компилятора (Compiling) есть ещё настройки компоновщика (Linking):

  • «Map file» — включение опции заставляет линкёр Delphi создавать вместе с проектом map-файл. Различные установки опции отвечают за уровень детализации и обычно имеет смысл ставить только Off или Detailed. Map файл обычно используется всевозможными утилитами типа EurekaLog, JCL или madExcept в качестве первичного источника для создания отладочной информации в своём формате. Поэтому руками устанавливать эту опцию вам придётся крайне редко — эксперты включают её самостоятельно по необходимости.
  • «Debug Information» (Linker)/«Include TD32 debug info» — внедряет в приложение отладочную информацию для внешнего отладчика в формате TD32. Обычно эта опция включается, если вы отлаживаете проект через Attach to process и Delphi не может найти отладочную информацию. При включении этой опции размер самого приложения увеличивается в 5-10 раз (при условии, что опция «Place debug information in separate TDS file» выключена). Поэтому, если вам нужна отладочная информация в распространяемом приложении — лучше рассмотреть другие варианты (лучше всего подходит отладочная информация в специализированных форматах — EurekaLog, JCL, madExcept).
  • «Include remote debug symbols» — заставляет линкёр создать rsm-файл вместе с проектом, в который записывается информация для удалённого отладчика Delphi. Вам нужно включать эту опцию, если вы хотите выполнить удалённую отладку. Полученный rsm-файлик нужно копировать вместе с приложением на удалённую машину.

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

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

Обычное приложение без механизма диагностики исключений

Общие настройки для любых профилей

Все опции отладки («Debug information» (Compiler), «Local symbols», «Reference info») вообще на готовый модуль не влияют, т.к. отладочная информация в exe/DLL не хранится, ну и нам жить не мешают => поэтому смысла их выключать я не вижу («Use Debug DCUs» — устанавливайте по вкусу, смотря по тому, хотите вы отлаживаться в стандартных модулях или нет).

«Stack Frames» вообще включать незачем.

Генерацию map-файла выключаем.

Профиль Debug

Включаем «Range checking» и (по вкусу) «Overflow checking».

«Include TD32 debug info» — включаем только для отладки внешнего процесса.

Соответственно, «Include remote debug info» — включаем только для удалённой отладки.

Профиль Release

Выключаем «Range checking», «Overflow checking», «Include TD32 debug info» и «Include remote debug info».

Приложение с механизмом диагностики исключений (типа EurekaLog, JCL или madExcept)

Общие настройки любых профилей

Все опции отладки («Debug information» (Compiler), «Local symbols», «Reference info») держать включёнными, т.к. в противном случае не будет доступна отладочная информация. Соответственно, ваши информационные механизмы пойдут лесом.

«Stack Frames» — вообще вЫключать незачем.

Генерацию map-файла включаем, если об этом не позаботился эксперт (маловероятно).

Профиль Debug

«Use Debug DCUs» — по вкусу.
Включаем «Range checking» и (по вкусу) «Overflow checking».

«Include TD32 debug info» — включаем только для отладки внешнего процесса.

«Include remote debug info» — включаем только для удалённой отладки.

Профиль Release

Включаем «Use Debug DCUs».

Выключаем «Range checking», «Overflow checking», «Include TD32 debug info» и «Include remote debug info».

Примечание: если вы используете мало операций с индексами в своей программе (так что дополнительные проверки не замедлят её), то будет хорошей идеей всегда держать опцию «Range checking» включённой.

Что может пойти не так, если настройки будут заданы неверно?

Ну, во-первых, это невозможность отладки (например, отсутствие информации для удалённого отладчика или выключенная опция «Debug information» (Compiler)), большой размер приложения (например, случайно забыли выключить «Debug information» (Linker)/«Include TD32 debug info»), медленная работа (например, компиляция с отладочным кодом), отсутствие или неполный стек вызовов в средствах диагностики исключений (например, выключили «Debug information» (Compiler)). В очень редких и запущенных случаях переключение опций может сказаться на работоспособности программы (например, установка Stack frames может снизить максимально возможную глубину рекурсии). Ну и недочёты по мелочи.

Кстати, если вы разрабатываете компоненты, то надо учитывать, что у Delphi есть именно два набора DCU-файлов, которые отличаются настройками компиляции. Вообще говоря, простое переключение опций, ответственных за генерацию отладочной информации, никак не влияет на интерфейс и реализацию модуля. Но в общем случае код может использовать условные директивы. Поэтому может быть ситуация, когда два набора DCU файлов (скомпилированных с разными опциями) не совместимы друг с другом — потому что они использовали какую-либо директиву и содержат разный код (а вот и пример).
Поэтому вам тоже надо иметь два набора DCU-файлов: один — скомпилированный с опцией «Use Debug DCUs», другой — без. Причём, не важно включена или выключена опция «Debug information» (Compiler) в ваших настройках в обоих случаях.

Примечания:
(*) Слова «отладочная информация увеличивает время компиляции, размер dcu-файлов, но не влияет на размер и скорость работы полученного exe-файла» некоторые понимают неправильно. В частности, многие замечают, что если переключить профиль приложения с Debug на Release (или наоборот), то размер приложения изменится (незначительно или же намного — зависит от настроек проекта и его исходного кода). Позвольте, но ведь я говорю об отладочной информации в чистом виде (мы говорим про опции «Debug Information», «Local Symbols» и т.п. с вкладки «Compiling»), а вы меняете профиль компиляции целиком. Это не только смена опций отладочной информации (которые, кстати, могут даже вообще не меняться при смене профиля), а также и множество других настроек.

Например, в вашем коде может быть тьма конструкций вида <$IFDEF DEBUG>какой-то код <$ENDIF>. Разумеется, когда вы собираете программу в Release, этот код в программу не попадёт, а когда вы собираете его в Debug, то — попадёт. Потому что по умолчанию профиль Debug содержит символ условной компиляции «DEBUG». В результате размер .exe действительно получится разный. Но повлияла ли на этот размер отладочная информация? Неа. Фактически, вы собрали в разных профилях разных код — неудивительно, что он разный по размеру.

Более того, вы можете удалить из конфигурации Debug символ условной компиляции «DEBUG» — и тогда вы будете собирать один и тот же код в обоих профилях. Ну, по крайней мере, свой код. Сторонний, уже собранный код, конечно же, никак не будет затронут. Например, код RTL/VCL уже скомпилирован с фиксированными настройками и не меняется при пересборке проекта. Вон там чуть выше я упомянул, что в некоторых версиях Delphi отладочный и релизный варианты кода RTL/VCL незначительно отличаются — опять же, благодаря условной компиляции. Но никак не отладочной информации.

Кроме того, к иному коду приводит и включение/выключение опций вида Optimization, Stack Frames, Range Check Errors и др. Действительно, оптимизация может удалять код (оптимизировать ненужный) или увеличивать (вставлять inline вместо вызова), Stack Frames очевидно добавляет код (код установки фрейма), равно как и Range Check Errors (код проверки диапазонов). В результате, вы меняете профиль — вы меняете и код (при условии, что разные профили имеют разные настройки вышеупомянутых опций — что так и есть по умолчанию). Меняете код — меняете и размер. Имеет ли хоть какое-то отношение к этому изменению размера отладочная информация? Нет.

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

Также о внедрении отладочной информации можно попросить и среду. Например, вы можете включить «Include TD32 Debug Info» (эта опция называется «Debug Information» на вкладке «Linking» в последних версиях Delphi) и выключить опцию «Place debug information in separate TDS file». Тогда вся отладочная информация из .dcu файлов будет собрана в один большой файл и этот файл будет внедрён в .exe. Тогда да, отладочная информация повлияет на размер файла, но это происходит не из-за её свойств, а потому что мы явно попросили такое поведение: «добавьте отладочную инфу в мою программу».

Кстати говоря, в последних версиях Delphi здорово поменяли настройки профилей компиляции. Если раньше Release и Debug мало чем отличались, то теперь отличия существенны. Профиль Debug выключает Optimization, но включает Stack Frames (а профиль Release — делает наоборот). Профиль Debug включает отладочную информацию, а Release — отключает. Но оба профиля включают Assert-ы. Также оба профиля включают I/O checks, но выключают overflow и range. В настройках компоновщика (linking) профиль Debug включает TD32 («Debug information») и Remote debug symbols (не для всех платформ). Release, соответственно, выключает эти же опции. И оба профиля не включают Map file и отдельный файл для TD32.

(**) Например: Button1Click состоит всего из двух инструкций: «call A; ret;». Она очень короткая и не использует аргументы или локальные переменные. Поэтому, очевидно, что ей не нужен стековый фрейм. Когда опция «Stack frames» выключена, то для Button1Click стековый фрейм не создаётся (но он создаётся, если опция «Stack frames» будет включена).

Но, для более сложных процедур стековые фреймы будут генерироваться вне зависимости от установки опции «Stack frames».

Например, тоже очень короткая процедура B всегда имеет фрейм. Причина: использование типа String в ShowMessage. Компилятору нужно вставить неявную строковую переменную и неявный try/finally для её освобождения, поэтому процедуре нужен фрейм.

В реальных приложениях фреймы генерируются для 99% процедур. Подробнее: Фреймы на стеке.

Переменные Delphi

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

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

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

Заметка. Компилятор языка Delphi, как и компилятор языка Pascal, не различает использование прописных и строчных букв в идентификаторах переменных, то есть используя имена PROGRAM, Program, program, можно ввести обозначение одной и той же переменной. Обычно программисты обозначают переменные Delphi таким образом, чтоб ее имя было более-менее логически связано с ее непосредственным назначением.

Примеры переменных Delphi

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

логично присвоение следующих имен: корням квадратного уравнения — x1 и х2, а свободным коэффициентам — соответственно a, b и c.

Пример 2. Если программа содержит переменные, которым назначено хранение числовых данных о сумме покупки и величине скидки, то эти переменные можно обозначить идентификаторами (именами) соответственно totalsum и skidka. Чтобы использовать переменные в программе, написанной на любых языках программирования, в том числе и Delphi, ее необходимо объявить в разделе переменных var.

Объявление переменных Delphi

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

откуда: Имя является именем назначаемой переменной; Тип подразумевает тип данных; для их хранения как раз и используется переменная.

Пример 3:

Здесь двум переменным m и n присвоен тип real, а переменной k тип integer. В тексте исходной программы программист, как правило, объявляет каждую переменную, помещая ее в отдельной строке. Если в коде исходной программы присутствуют несколько переменных, которым присвоен один и тот же тип, то их имена перечисляют в одной строке, разделяя запятыми, и лишь после последней переменной, используя символ «:», указывают тип переменных:

Файловые переменные

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

Итак, в Паскале, в зависимости от самого файла, переменную можно описать следующим образом:

  1. Текстовый файл var f:text;
  2. Типизированный файл var имя:file of тип;
    Предварительно можно определить новый тип данных
  3. Бестиповый файл var имя:file.

Функции для обработки файлов:

Функция Описание
Assign(f,s) Процедура, связывающая файловую переменную f, с полным именем файла s.

Образец:
var f:file of real;
Begin
Assign(f,’c:TPabc.txt’);

Eof(f) Функция, для проверки достижения конца файла.

Образец:
While not EOF(f) do Begin

Reset(f) Файл f открыт для чтения и доступен его первый элемент, далее можно выполнять чтение и запись информации из файла.
Rewrite(f) Процедура, которая открывает и очищает файл для новой записи.
Close(f) Закрывает файл. После записи информации в файл его необходимо обязательно закрыть, при чтении закрывать файл не обязательно.
Rename(f,s) Процедура переименования файла, где f – файловая переменная, s – новое имя файла (строковая переменная).
Erase(f) Процедура для удаления файла связанного с переменной f. Для корректного выполнения удаления, файл должен быть закрыт.
Append(f) Процедура для добавления информации в конец файла.
Read(f,x1,x2,…,xn) Read(f,x) Операторы, которые последовательно считывают компонент из файла в указанные переменные. Процедура read не проверяет, достигнут ли конец файла. За этим нужно следить с помощью функции EOF.
Write(f,x1,x2,…,xn) Write(f,x) Оператор последовательно записывающий в файл значения переменных.

Типы файлов и переменных должны совпадать, кроме текстовых файлов, в них переменные могут быть целого, вещественного, символьного, строкового типа.

  • Для того, чтобы создать файл, необходимо:
  1. Описать файловую переменную.
  2. Связать ее с физическим файлом.
  3. Открыть файл для записи.
  4. Внести необходимую информацию в файл (пользуясь циклом FOR).
  5. Обязательно закрыть файл.
  • Для считывания информации из файла нужно:
  1. Описать файловую переменную.
  2. Связать ее с физическим файлом.
  3. Открыть файл для чтения.
  4. Считать необходимую информацию (проверить, достигнут ли конец файла).
  5. Закрыть файл.

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

Полный код программы, показывающий запись N действительных чисел в файл:

Полный код программы, иллюстрирующий вывод на экран содержимое файла:

Интересно, что если открыть этот полученный файл вещественного типа с помощью «Блокнот», то будет что-то вроде этого:

А вот если тип файла будет текстовым (FILE OF TEXT), то данные легко отобразятся в «Блокноте». Напомним, что при вещественном типе файла, значения можно вводить только с помощью чисел (в том числе и дробных), а вот если тип файла текстовый – вводить можно любой символ, который есть на клавиатуре.

Попробуйте поэкспериментировать с различными типами файла (real, integer, byte, text, word, longint, single, double, extended).

Реализация интерфейсов (interface) в Delphi.

В одном из постов я рассказывал о своей дипломной работе, которая частично перекочевала в программный комплекс для экологов. Сейчас появилась идея значительно усовершенствовать комплекс, как для самих пользователей (внедрение новых методик расчёта, анализ данных и т.д.), так и в плане новых возможностей для разработчиков – сделать поддержку плагинов (plug-in’s).
Так как до нынешнего момента я детально не разбирался с подобными задачами, хотя и использовал в работе OLE и пр. технологии, то решил более детально разобраться с тем, что представляет из себя COM, как реализуются интерфейсы в Delphi и как с помощью них можно реализовать поддержку плагинов.
Вначале немного теории, а именно разберемся с тем, что такое интерфейс.

Чтобы установить обмен данными между двумя разнородными системами (клиентом и сервером), необходимо создать нечто общее, т. е. заранее «объяснить» компонентам, как они будут общаться. Это осуществляется с помощью одного из центральных элементов модели СОМ, называемого интерфейсом.
Интерфейс в модели СОМ — это средство, которое позволяет клиенту правильно обратиться к объекту СОМ, а объекту позволяет ответить клиенту так, чтобы он (клиент) его (сервер) понял.
То есть интерфейс – это то посредством чего что-то соединяется с чем-то.
Каждый интерфейс обязательно должен иметь два атрибута. Первый атрибут —название интерфейса, составленное в соответствии с правилами языка программирования. Имя должно начинаться с буквы “I” — так принято, так же как в Delphi название любого класса должно начинаться с буквы «Т».
Второй атрибут представляет собой глобальный уникальный идентификатор (Globally Unique IDenitfier, GUID), который задается уникальным сочетанием символов. Для его генерации используются такие сильные алгоритмы, что однажды сгенерированное сочетание никогда не повторится в будущем ни на одном компьютере мира.
Как обратиться к методам объекта с помощью интерфейса?
Для этого необходимо получить указатель на соответствующий интерфейс, после чего клиент имеет право использовать службы объекта, вызывая его методы как методы обычного объекта.
Поскольку объект может иметь несколько интерфейсов, то при получении интерфейса для каждого из них будет получен собственный указатель.
Возникает закономерный вопрос — как быть, если клиент не знает, какие интерфейсы имеются у объекта? Для получения их перечня нужно использовать интерфейс IUnknown или IInterface, который есть у любого объекта СОМ.
IUnknown является базовым интерфейсом СОМ-объектов. Через этот интерфейс можно получить все остальные интерфейсы, которые поддерживает объект. В нем присутствуют всего три метода, но они играют самую важную роль в функционировании объекта:
1. Querylnterface
2. _AddRef
3. _Release
Метод Querylnterface возвращает указатель на интерфейс объекта по его идентификатору. Если передан идентификатор несуществующего интерфейса, то метод возвратит Null.
СОМ реализует автоматическое управление памятью СОМ-объектов, основанное на идее подсчета ссылок на объект. Объект существует, пока его использует хотя бы один клиент. Поэтому любой класс после создания объекта должен увеличить счетчик ссылок, а после завершения его использования уменьшить счетчик на единицу. Когда счетчик достигнет нулевого значения, СОМ-объект автоматически будет удален из памяти. Именно для этого предназначены метод _AddRef, увеличивающий счетчик на единицу, и метод _Release, уменьшающий его.
Используя Querylnterface для получения указателя на интерфейс в Delphi, метод запускает процедуру увеличения счетчика ссылок. Вызвать его вручную может потребоваться в том случае, если один клиент попытается передать другому указатель на интерфейс объекта. Тогда без вызова _AddRef счетчик будет хранить неверные сведения о количестве использующих его клиентов. То же справедливо и для метода _Release. При выходе переменной, ссылающейся на интерфейс, за область видимости (либо при присвоении ей другого значения) компилятор генерирует код для вызова метода _Release, информируя реализацию COM-объекта о том, что ссылка на нее больше не нужна. Поэтому нет надобности постоянно вызывать этот метод, за исключением особых случаев.
Что делать, если клиенту требуется получить интерфейс объекта, который еще не иcпoльзoвaлся и, следовательно, может быть и не создан. В этом случае клиенту необходимо обратиться к библиотеке СОМ. Она обеспечивает выполнение базовых функций и интерфейсов в операционной системе. К ней обращаются посредством специальных функций, имена которых согласно спецификации начинаются с приставки Co.
При установке СОМ-приложения в системный реестр записываются данные обо всех реализуемых им объектах.
CLSID (class identifier) — идентификатор класса, однозначно идентифицирует класс объекта в системе;
тип сервера объекта (внутренний, локальный, удаленный).
Поэтому для получения указателя на требуемый класс и интерфейс необходимо вызвать метод CoCreateInstance, передав в качестве параметров CLSID нужного класса, IID интерфейса (Interface Identifier) и тип требуемого сервера.
Возвращение указателя происходит по следующей схеме.
1. Библиотека через диспетчер управления службами обращается к системному реестру.
2. Находит информацию о сервере по идентификатору класса CLSID.
3. Запускает сервер.
4. Сервер создает экземпляр класса.
5. Объект возвращает библиотеке указатель на запрошенный интерфейс.
6. Библиотека СОМ передает указатель клиенту.
Теперь, немного разобравшись с теорией, попробуем реализовать простенький интерфейс.

Реализация интерфейса. Вариант №1 – использование TInterfacedObject

Реализацией интерфейса в Delphi всегда выступает класс. Поэтому в объявлении класса необходимо указать, какие интерфейсы он реализует.
Рассмотрим реализацию интерфейса, который имеет всего один метод Hello() в результате выполнения которого мы получим сообщение «Hello World!». Вначале, объявим сам интерфейс:

Delphi и COM

Delphi и COM

Введение

COM (Component Object Model) — модель объектных компонентов — одна из основных технологий, на которых основывается Windows. Более того, все новые технологии в Windows (Shell, Scripting, поддержка HTML и т.п.) реализуют свои API именно в виде COM-интерфейсов. Таким образом, в настоящее время профессиональное программирование требует понимания модели COM и умения с ней работать. В этой главе мы рассмотрим основные понятия COM и особенности их поддержки в Delphi.

Базовые понятия

Ключевым моментом, на котором основана модель COM, является понятие интерфейса. Не имея четкого понимания того, что такое интерфейс, успешное программирование COM-объектов невозможно.

Интерфейс

Интерфейс, образно говоря, является «контрактом» между программистом и компилятором.

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

Компилятор обязуется создать в программе внутренние структуры, позволяющие обращаться к методам этого интерфейса из любого поддерживающего те же соглашения средства программирования. Таким образом, COM является языково-независимой технологией и может использоваться в качестве «клея», соединяющего программы, написанные на разных языках.

Объявление интерфейса включает в себя описание методов и их параметров, но не включает их реализации. Кроме того, в объявлении может указываться идентификатор интерфейса — уникальное 16-байтовое число, сгенерированное по специальным правилам, гарантирующим его статистическую уникальность (GUID — Global Unique Identifier).

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

Таким образом, необходимо понимать следующее:

  • Интерфейс не является классом. Класс может выступать реализацией интерфейса, но класс содержит код методов на конкретном языке программирования, а интерфейс — нет.
  • Интерфейс строго типизирован. Как клиент, так и реализация интерфейса должны использовать точно те же методы и параметры, что указаны в описании интерфейса.
  • Интерфейс является «неизменным контрактом». Нельзя определять новую версию того же интерфейса с измененным набором методов (или их параметров), но с тем же идентификатором.

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

Реализация интерфейса — это код, который реализует эти методы. При этом, за несколькими исключениями, не накладывается никаких ограничений на то, каким образом будет выглядеть реализация. Физически реализация представляет собой массив указателей на методы, адрес которого и используется в клиенте для доступа к COM-объекту. Любая реализация интерфейса имеет метод QueryInterface, позволяющий запросить ссылку на конкретный интерфейс из числа реализуемых.

Автоматическое управление памятью и подсчет ссылок

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

Объявление интерфейсов

Для поддержки интерфейсов Delphi расширяет синтаксис языка Pascal дополнительными ключевыми словами. Объявление интерфейса в Delphi реализуется ключевым словом interface:

Для генерации нового значения GUID в IDE Delphi служит сочетание клавиш Ctrl+Shift+G.

IUnknown

Базовым интерфейсом в модели COM является IUnknown. Любой интерфейс наследуется от IUnknown и обязан реализовать объявленные в нем методы. IUnknown объявлен в модуле System.pas следующим образом:

Рассмотрим назначение методов IUnknown более подробно.

Последние два метода предназначены для реализации механизма подсчета ссылок.

Эта функция должна увеличить счетчик ссылок на интерфейс на единицу и вернуть новое значение счетчика.

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

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

Эта функция получает в качестве входного параметра идентификатор интерфейса. Если объект реализует запрошенный интерфейс, то функция:

  1. возвращает ссылку на него в параметре Obj;
  2. вызывает метод _AddRef полученного интерфейса;
  3. возвращает 0.

В противном случае — функция возвращает код ошибки E_NOINTERFACE.

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

В модуле System.pas объявлен класс TInterfacedObject, реализующий IUnknown и его методы. Рекомендуется использовать этот класс для создания реализаций своих интерфейсов.

Кроме того, поддержка интерфейсов реализована в базовом классе TObject. Он имеет метод

Если класс реализует запрошенный интерфейс, то функция:

  1. возвращает ссылку на него в параметре Obj;
  2. вызывает метод _AddRef полученного интерфейса;
  3. возвращает TRUE.

В противном случае — функция возвращает FALSE.

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

Реализация интерфейсов

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

Класс TMyClass реализует интерфейсы IMyInterface и IDropTarget. Необходимо понимать, что реализация классом нескольких интерфейсов не означает множественного наследования и вообще наследования класса от интерфейса. Указание интерфейсов в описании класса означает только то, что в данном классе реализованы все эти интерфейсы.

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

Рассмотрим более подробный пример.

Здесь класс TTest реализует интерфейс ITest. Рассмотрим использование интерфейса из программы.

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

Во-первых, оператор присваивания при приведении типа данных к интерфейсу неявно вызывает метод _AddRef. При этом количество ссылок на интерфейс увеличивается на единицу.

Во-вторых, код не содержит никаких попыток освободить память, выделенную под объект TTest. Тем не менее, если выполнить эту программу, на экран будет выведено сообщение о том, что деструктор был вызван. Это происходит потому, что при выходе переменной, ссылающейся на интерфейс, за область видимости (либо при присвоении ей другого значения) компилятор Delphi генерирует код для вызова метода _Release, информируя реализацию о том, что ссылка на нее больше не нужна.

Внимание! Если у класса запрошен хотя бы один интерфейс — не вызывайте его метод Free (или Destroy). Класс будет освобожден тогда, когда отпадет необходимость в последней ссылке на его интерфейсы. Если вы к этому моменту уничтожили экземпляр класса вручную — произойдет ошибка доступа к памяти.

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

Если вы хотите уничтожить реализацию интерфейса немедленно, не дожидаясь выхода переменной за область видимости, – просто присвойте ей значение NIL:

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

  1. При приведении типа объекта к интерфейсу вызывается метод _AddRef.
  2. При выходе переменной, ссылающейся на интерфейс, за область видимости либо при присвоении ей другого значения вызывается метод _Release.
  3. Единожды запросив у объекта интерфейс, в дальнейшем вы не должны освобождать объект вручную. Вообще начиная с этого момента лучше работать с объектом только через интерфейсные ссылки.

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

Например, следующий код будет успешно откомпилирован, но при выполнении вызовет ошибку «Interface not supported»:

В то же время код

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

Реализация интерфейсов (расширенное рассмотрение)

Рассмотрим вопросы реализации интерфейсов подробнее.

Объявим два интерфейса:

Теперь создадим класс, который будет реализовывать оба этих интерфейса:

Как видно, класс не может содержать сразу два метода Beep. Поэтому Delphi предоставляет способ для разрешения конфликтов имен, позволяя явно указать, какой метод класса будет служить реализацией соответствующего метода интерфейса.

Если реализация методов TTest2.Beep1 и TTest2.Beep2 идентична, то можно не создавать два разных метода, а объявить класс следующим образом:

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

Для делегирования реализации интерфейса другому классу служит ключевое слово implements.

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

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

Интерфейсы и TComponent

В базовом классе VCL TComponent имеется полный набор методов, позволяющих реализовать интерфейс IUnknown, хотя сам класс данный интерфейс не реализует. Это позволяет наследникам TComponent реализовывать интерфейсы, не заботясь о реализации IUnknown. Однако методы TComponent._AddRef и TComponent._Release на этапе выполнения программы не реализуют механизм подсчета ссылок, и, следовательно, для классов-наследников TComponent, реализующих интерфейсы, не действует автоматическое управление памятью. Это позволяет запрашивать у них интерфейсы, не опасаясь, что объект будет удален из памяти при выходе переменной за область видимости. Таким образом, следующий код совершенно корректен и безопасен:

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

Использование интерфейсов внутри программы

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

В качестве примера рассмотрим MDI-приложение, имеющее много различных форм и единую панель инструментов. Предположим, что на этой панели инструментов имеются команды «Сохранить», «Загрузить» и «Очистить», однако каждое из окон реагирует на эти команды по-разному.

Создадим модуль с объявлениями интерфейсов:

Интерфейс IToolBarCommands описывает набор методов, которые должны реализовать формы, поддерживающие работу с панелью кнопок. Метод SupportedCommands возвращает список поддерживаемых формой команд.

Создадим три дочерние формы — Form2, Form3 и Form4 — и установим им свойство FormStyle = fsMDIChild.

Form2 умеет выполнять все три команды:

Form3 умеет выполнять только команду Clear:

И наконец, Form4 вообще не реализует интерфейс IToolBarCommands и не откликается ни на одну команду.

На главной форме приложения поместим ActionList и создадим три компонента TAction. Кроме того, разместим на ней TToolBar и назначим ее кнопкам соответствующие TAction.

Наиболее интересен метод ActionList1Update, в котором проверяются поддерживаемые активной формой команды и настраивается интерфейс главной формы. Если нет активной дочерней формы либо она не поддерживает интерфейс IToolBarCommands, все команды запрещаются, в противном случае — разрешаются только поддерживаемые формой команды.

При активизации команд проверяется наличие активной дочерней формы, у нее запрашивается интерфейс IToolBarCommands и вызывается соответствующий ему метод:

Работа программы представлена на рисунке.

Того же эффекта можно добиться и другими методами (например, унаследовав все дочерние формы от единого предка либо обмениваясь с ними сообщениями), однако эти методы имеют ряд существенных недостатков.

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

Использование интерфейсов для реализации Plug-In

Еще более удобно использовать интерфейсы для реализации модулей расширения программы (Plug-In). Как правило, такой модуль экспортирует ряд известных главной программе методов, которые могут быть из него вызваны. В то же время зачастую ему необходимо обращаться к каким-либо функциям вызывающей программы. И то и другое легко реализуется при помощи интерфейсов.

В качестве примера реализуем несложную программу, использующую Plug-In для загрузки данных.

Объявим интерфейсы модуля расширения и внутреннего API программы.

Этот модуль должен использоваться как в Plug-In, так и в основной программе и гарантирует использование ими идентичных интерфейсов.

Plug-In представляет собой DLL, экспортирующую функцию CreateFilter, возвращающую ссылку на интерфейс ILoadFilter. Главный модуль сначала должен вызвать метод Init, передав в него имя файла и ссылку на интерфейс внутреннего API, а затем вызывать метод GetNextLine до тех пор, пока он не вернет FALSE.

Рассмотрим код модуля расширения:

Метод Init выполняет две задачи: сохраняет ссылку на интерфейс API главного модуля для дальнейшего использования и пытается открыть файл с данными. Если файл открыт успешно – выставляется внутренний флаг InitSuccess.

Метод GetNextLine считывает следующую строку данных и возвращает либо TRUE, если это удалось, либо FALSE — в случае окончания файла. Кроме того, при помощи API, предоставляемого главным модулем, данный метод информирует пользователя о ходе загрузки.

В деструкторе мы обнуляем ссылку на API главного модуля, уничтожая его, и закрываем файл.

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

Теперь полученную DLL можно использовать из основной программы.

Класс TAPI реализует API, предоставляемый модулю расширения. Функция ShowMessage выводит сообщения модуля в Status Bar главной формы приложения.

Подготавливаем TMemo к загрузке данных:

Получаем имя модуля с фильтром для выбранного расширения файла. Описания модулей хранятся в файле plugins.ini в секции Filters в виде строк формата:

Теперь попытаемся загрузить модуль и найти в нем функцию CreateFilter:

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

Загружаем данные при помощи созданного фильтра:

Перед выгрузкой DLL из памяти необходимо обязательно освободить ссылку на интерфейс Plug-In, иначе это произойдет по выходе из функции и вызовет Access Violation.

Выгружаем DLL и обновляем TMemo:

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

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

Внимание! Поскольку в EXE и DLL используются длинные строки, не забудьте включить в список uses обоих проектов модуль ShareMem. Другим вариантом решения проблемы передачи строк является использование типа данных WideString. Для них распределением памяти занимается OLE, причем делает это независимо от модуля, из которого была создана строка.

Динамические переменные

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

Выделение памяти для динамической переменной осуществляется вызовом процедуры new. У процедуры new один параметр — указатель на переменную того типа, память для которой надо выделить. Например, если р является указателем на тип real, то в результате выполнения процедуры new(p); будет выделена память для переменной типа real (создана переменная типа real), и переменная-указатель р будет содержать адрес памяти, выделенной для этой переменной.

У динамической переменной нет имени, поэтому обратиться к ней можно только при помощи указателя.

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

Например, если р — указатель на динамическую переменную, память для которой выделена инструкцией new(p), то инструкция dispose (р) освобождает занимаемую динамической переменной память.

Следующая процедура (ее текст приведен в листинге 8.3) демонстрирует создание, использование и уничтожение динамических переменных.

Листинг 8.3. Создание, использование и уничтожение динамических переменных

procedure TForm1.Button1Click(Sender: TObject); var

p1,p2,p3: Integer; // указатели на переменные типа integer

// создадим динамические переменные типа integer

// (выделим память для динамических переменных)

ShowMessage(‘Сумма чисел равна ‘ + IntToStr(р3^));

// уничтожим динамические переменные

// (освободим память, занимаемую динамическими переменными)

В начале работы процедура создает три динамические переменные. Две переменные, на которые указывают p1 и р2, получают значение в результате выполнения инструкции присваивания. Значение третьей переменной вычисляется как сумма первых двух.

Илон Маск рекомендует:  PHP фреймверки обзоры, сравнение, рейтинг лучших PHP фреймверков
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL