System в Delphi


Содержание

Библиотека времени выполнения .NET и FCL

Глава из книги “Delphi 2005. Для профессионалов”


Автор: М. Кэнту
Источник: Delphi 2005
Материал предоставил: Издательство »Питер»

Опубликовано: 23.07.2006
Версия текста: 1.0

Несколько последних глав (с 5 по 8) были посвящены библиотеке времени выполнения Delphi (RTL) и VCL, главным образом с точки зрения разработки для платформы Win32. Я упоминал о различиях между Win32 и .NET и приводил примеры кода для обеих платформ, однако практически не уделял внимание технологиям, специфическим для .NET.

Настоящая глава заполняет этот пробел. В ней рассматриваются три разные темы. В первой части будут описаны некоторые важные изменения в поддержке Delphi RTL для .NET. Во второй части рассматривается собственная библиотека .NET с обсуждением некоторых базовых классов FCL (Framework Class Library), объединяемых общим термином BCL (Base Class Library). В третьей части содержится обзор библиотеки форм и элементов WinForms и ее сравнение с VCL.

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

Библиотека времени выполнения Delphi для .NET

Начнем с анализа различий между библиотеками времени выполнения Delphi для .NET и Win32. Компания Borland приложила немалые усилия для того, чтобы низкоуровневые процедуры и основные классы Delphi для .NET оставались совместимыми со своими аналогами для Win32. Но даже если объявления классов остались прежними, их реализация обычно существенно изменяется.

В некоторых случаях в Delphi для объединения интерфейсов стандартных классов Delphi (таких, как TObject и TComponent) с их аналогами для .NET (System.Object и System.ComponentModel.Component) применяются классы-помощники. Везде, где это возможно, основные классы Delphi RTL для .NET создаются как “обертки” для существующих классов FCL.

Модуль System в Delphi для .NET

Переход от Delphi для Win32 к Delphi для .NET сопровождался многочисленными изменениями в модуле System — большими, чем в каком-либо другом модуле RTL. Большинство изменений обусловлено коренными различиями в реализации, и компании Borland пришлось изрядно потрудиться, чтобы обеспечить высокую совместимость с существующим кодом Delphi.

Как уже было показано в главе 4, тип TObject теперь объявляется как псевдоним для типа System.Object, корневого класса .NET Framework. Также вы видели, что классы-помощники (TObjectHelper) позволили интегрировать большинство стандартных методов класса TObject в класс System.Object. А это означает, что такие методы доступны для любых классов, в том числе и не откомпилированных в Delphi для .NET.

Впрочем, не все аспекты TObject можно перенести в .NET. Приведу короткое сравнение методов, разделив их на группы:

  • Методы, относящиеся к классам (ClassType, ClassName, ClassNameIs, ClassInfo, ClassParent и InheritsFrom), доступны и работают так же, как раньше.
  • Низкоуровневые методы для работы с памятью (MethodAddress, MethodName и FieldAddress) доступны, но работают по-другому, потому что в новой версии они не базируются на указателях.
  • Низкоуровневые методы конструирования и уничтожения объектов (InitInstance, CleanupInstance, NewInstance и FreeInstance) не поддерживаются, как и конструкторы/деструкторы второго уровня (AfterConstruction и BeforeDestruction).
  • Также не поддерживаются методы, относящиеся к интерфейсам (GetInterface, GetInterfaceEntry и GetInterfaceTable), а также метод SafeCallException.
  • Метод InstanceSize использоваться не может, потому что размер объекта определяется не компилятором, а во время загрузки на стадии выполнения. Любые допущения относительно размера объекта в .NET считаются ошибками проектирования, потому что они могут нарушить работу кода на других платформах .NET, не базирующихся на Win32.
  • Виртуальный метод Dispatch остался доступным, хотя он имеет нетипизованный параметр (var Message); виртуальный метод DefaultHandler не поддерживается.
  • Наконец, метод Free остался доступным, хотя теперь вместо деструктора он вызывает метод IDisposable.Dispose (см. главу 4).

Превратившись в псевдоним System.Object, класс TObject приобрел ряд новых методов — таких, как Equals, GetHashCode, ToString и GetType. Они будут описаны позднее, в разделе “Класс System.Object”.

Кроме объявления TObject, между модулем Win32 System и модулем .NET Borland.Delphi.System существуют и другие различия. Вот лишь некоторые из них:

  • Ссылка на класс TClass работает иначе (см. главу 4).
  • Класс Exception был перемещен из модуля SysUtils и превратился в псевдоним для класса .NET System.Exception.
  • Базовый интерфейс IInterface не содержит методов (так как в .NET подсчет ссылок не применяется, а поддержка интерфейсов напрямую проверяется CLR при преобразованиях типов).
  • Функция Assigned имеет параметр типа GCHandle, тогда как в Win32 ее параметр был нетипизованным.
  • В .NET существуют многочисленные типы атрибутов.
  • В .NET некоторые обычные типы данных, включая AnsiString, Currency и TDateTime, оформляются в виде записей. Такие записи содержат методы и определяют операторы, в том числе операторы преобразования.
  • Запись TMethod содержит несколько новых методов.
  • В .NET исключена вся поддержка управления памятью.

Borland.Delphi.DLL

Другое принципиальное отличие от прошлых версий Delphi состоит в том, что в .NET язык поставляется с DLL-библиотекой времени выполнения. В Win32 базовая функциональность времени выполнения (определяемая в модуле System) включалась в каждый исполняемый файл независимо от использования пакетов времени выполнения. Впрочем, это приводило к крайне незначительным дополнительным затратам ресурсов, а некоторые функции (скажем, поддержка универсального типа Variant) включались в исполняемый файл только в случае ее фактического использования.

В .NET по умолчанию функциональность времени выполнения также включается в исполняемый файл. Тем не менее на этой платформе также можно выбрать режим использования внешней среды времени выполнения из библиотеки Borland.Delphi.DLL. Библиотека занимает совсем немного места (99,5 Кбайт) и включается в GAC программой установки Delphi. Вы можете поместить ее копию в локальную папку компиляции, для этого следует выделить сборку в списке References и выполнить команду Copy Local в меню проекта.

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

Например, минимальное приложение MiniSizeNet (см. главу 5) уменьшается с 28 Кбайт до 12 Кбайт. Впрочем, в реальных приложениях средний выигрыш получается не столь заметным, как в этом крайнем случае.

Borland.VclRtl

По аналогии с пакетами, составляющими Win32 VCL, библиотека Delphi условно делится на две части: библиотеку глобальных функций и низкоуровневых классов (см. главу 5) и библиотеку визуальных компонентов (см. главу 6). То же относится и к библиотеке Delphi для .NET, состоящей из сборок Borland.VclRtl и Borland.Vcl. Здесь важно то, что часть VCL почти не изменилась (поскольку она работает параллельно с классами .NET Framework), а часть RTL интегрируется с FCL.

И все же общий подход, использованный в Delphi RTL, отличается от подхода FCL, так как в Delphi определяются сотни глобальных функций (в главе 4 рассказано о том, как глобальные функции отображаются на статические члены классов, сгенерированных компилятором Delphi для .NET). Компания Borland избрала этот путь для сохранения совместимости с существующим кодом Delphi (что бы по этому поводу ни говорили блюстители “чистоты” ООП).

Некоторые модули RTL практически не изменились, в том числе ConvUtils, DateUtils, Math, StrUtils и SyncObjs из пространства имен Borland.Vcl. В ряде других модулей присутствуют ограниченные изменения:

  • Borland.Vcl — определения записей TPoint, TSize и TRect, которые в .NET имеют множество методов и конструкторов.
  • Borland.Vcl.Variants и Borland.Vcl.VarConv unitVarConv — интерфейс в целом остался прежним, но реализация полностью изменилась, поскольку ранее она базировалась на конкретной поддержке Win32 (части архитектуры COM). По тем же причинам исчез модуль VarUtils. Обратите внимание: модуль Borland.Vcl.Variants (или просто Variants, если по умолчанию используется пространство имен Borland.Vcl) теперь необходимо вручную включать в директиву uses программы, использующей универсальный тип, потому что его поддержка была исключена из базовой системной функциональности.

Также в Delphi для .NET появился ряд новых модулей RTL:

  • Borland.Vcl.Complex — Complex, модульопределение записи для представления комплексных чисел дает полный пример перегрузки операторов. Заменяет (все еще существующий) модуль Borland.Vcl.VarCmplx.
  • Borland.Vcl.Convert — Convert, модульопределение записи TConvert, обеспечивающей поддержку интерфейса .NET IComparable и IConvertible для механизма преобразования, определенного в модуле ConvUtils.
  • Borland.Vcl.WinUtils — WinUtils, модульвспомогательные функции, заменяющие или дополняющие такие функции Windows API, как MakeObjectInstance или AllocateHWnd; определения функций преобразования стандартных типов данных Delphi в типы данных .NET (скажем, преобразования низкоуровневых буферов в массивы); доступ к глобальным данным, включая HInstance и GetCmdShow.

Наконец, в .NET отсутствуют модули DelphiMM и ShareMem.

Объявления функций Windows API в Borland.Vcl.Windows

Модуль Borland.Vcl.Windows, наряду с другими аналогичными модулями, предоставляет интерфейс к классическим функциям Win32 API из приложений .NET. Определения этих функций API существенно изменились, как в следующем случае:

Атрибут DLLImport используется для реализации вызовом неуправляемых (unmanaged) функций, реализованных в стандартных DLL-библиотеках Win32. Атрибут предоставляет информацию, необходимую для правильного оформления вызова. Хотя функции API отображаются на один физический уровень (базовые Windows DLL), объявление этих функций в .NET значительно отличается от их аналогов для Win32

На вызовы неуправляемых функций Win32 распространяются те же правила компиляции и условия времени выполнения. Например, все параметры на стороне CLR должны быть безопасными по отношению к типам. По этой причине все строковые параметры PChar теперь определяются как относящиеся к строковому типу (с расширенной кодировкой!). В момент вызова CLR выполняет необходимые преобразования формата параметров.

Как правило, на уровне исходного кода вносятся относительно небольшие изменения — такие, как удаление преобразований PChar из вызовов и прямая передача строковых параметров. Однако задача создания вызовов функций Win32, которые бы компилировались с единым исходным кодом на платформах Win32 и .NET, оказывается весьма нетривиальной.

Рассмотрим два фрагмента из примеров, упоминавшихся в предыдущих главах. В первом фрагменте директива IFDEF распространяется на весь вызов функции API, а во втором — только на конкретный параметр:

При объявлении функций API для Win32 и .NET также следует помнить, что один из параметров функций Win32 API может относиться к разным типам в зависимости от значений других параметров. Классическим примером служит функция API CreateWindow, в которой параметр может использоваться как для передачи идентификатора главного меню, так и идентификатора дочернего окна MDI. У других функций API (например, FindResource) в одном параметре также может передаваться как строка, так и целое число. С учетом возможных комбинаций параметров объявление функции приобретает следующий вид:

Другой распространенный случай — передача указателей на структуры данных, которые могут быть равны nil. С заменой указателей записями (структурные типы не могут иметь неопределенные значения) понадобится перегруженная версия для передачи пустого параметра:

Borland.Vcl.SysUtils

Многочисленные изменения произошли в модуле SysUtils, который в Delphi 2005 также был доработан в версии для Win32. В него были включены такие возможности .NET, как преобразования формата даты между строковым форматом Delphi и строковым форматом CLR (ConvertDelphiDateTimeFormat и ConvertClrDateTimeFormat) и заменители функций Win32 API для выполнения операций инкремента/декремента, безопасных по отношению к программным потокам (InterlockedIncrement и InterlockedDecrement, а также InterlockedExchange).

В Delphi для .NET функция SameStr, функцияSameStr заменяет CompareMem, файловые идентификаторы базируются на модуле System.IO.FileStream, а функции FileRead и FileWrite имеют перегруженные версии, безопасные по отношению к типам.

Наконец, из модуля были исключены все операции прямого управления памятью, в том числе AllocMem, строковые функции на базе PChar и функция AppendStr (которая считалась устаревшей еще в Delphi 7).

Классы Borland.VclRtl

Большинство классов RTL сохранило прежний интерфейс, хотя в некоторых случаях реализация существенно изменилась. В частности, среди модулей пространства имен Borland.Vcl практически не изменились модули Contnrs, HelpIntfs, IniFiles и Registry.

Поддержка COM была сведена к минимуму, поэтому в модулях ComObj и ActiveX почти не осталось содержания, а модули ComServ, VCLCom и StdVCL были исключены (наряду с CorbaVcl и модулем ZLib, обеспечивающим поддержку сжатия потоков).

Многие изменения в реализации связаны с заменой “родной” реализации вспомогательными классами .NET. Например, класс TMask (модуль Borland.Vcl.Mask) в Delphi для .NET представляет собой “обертку” для класса System.Text.RegularExpressions.RegEx (см. далее раздел “Регулярные выражения”).

Borland.Vcl.Classes

Основные классы Delphi RTL определяются в модуле Classes, модульClasses (или Borland.Vcl. Classes). Во многих важных случаях эти классы сохранили совместимость на уровне интерфейсов, но были переписаны заново для использования соответствующих классов библиотеки .NET. В табл. 9.1 перечислены важные соответствия между классами.

Другие распространенные классы, определяемые в этом модуле (в том числе TCollection, классTCollection, TStrings и TStringList), не изменились. Стоит заметить, что у классов потоков данных имеются аналоги в FCL, и некоторые потоковые классы Delphi отображаются на них. В частности, базовый класс потока TCLRStreamWrapper, классTCLRStreamWrapper отображается на потоковые средства CLR. В Delphi для .NET такие классы, как TFileStream, классTFileStream, наследуют от TCLRStreamWrapper. Также существует класс TStreamToCLRStream, классTStreamToCLRStream для выполнения обратной функции, то есть инкапсуляции потока Delphi в потоке CLR. Потоковые классы содержат много перегруженных методов чтения и записи для обеспечения безопасности типов.

Другая группа классов использует классы-помощники для интеграции с FCL без потери совместимости с существующим кодом. Прежде всего, это относится к классам TPersistent и TComponent:

Класс-“обертка” Delphi Класс .NET FCL
TList System.Collections.ArrayList
TThread System.Threading.Thread
TCLRStreamWrapper (THandleStream) System.IO.Stream
Таблица 9.1. Некоторые классы Delphi для .NET, инкапсулирующие соответствующие классы FCL

Что касается класса TComponent, классу-помощнику необходима дополнительная память для хранения владельца, списка компонентов и имени компонента. Поскольку класс-помощник не может определять новые поля, Delphi для .NET использует объект TComponentSite, управляемый через интерфейс ISite класса FCL Component (см. далее раздел “Класс Component”).

В классе TComponent в Delphi для .NET появился ряд новых возможностей: методы класса ChangeComponentName и SetComponentParent, глобальные функции SendNotification и DelegatesEqual, а также новое определение свойства Tag (теперь относящееся к типу Variant).

ПРИМЕЧАНИЕ

В VCL для Win32 свойство Tag относится к типу Integer; в VCL для .NET оно относится к типу Variant, а в WinForms — к типу System.Object. Такие разночтения могут породить путаницу и проблемы с совместимостью при переносе кода Delphi между библиотеками. Не забывайте, что тип Variant в Delphi для .NET определяется как System.Object, так что определения VCL для .NET и WinForms на самом деле идентичны.

BCLБиблиотека .NET FCL (Framework Class Library) представляет собой большой набор классов, относящихся к разным областям, от низкоуровневых операций до пользовательского интерфейса и веб-программирования. Компания Microsoft обычно обозначает некоторые основные классы FCL термином BCL (Base Class Library). В табл. 9.2 перечислены пространства имен BCL.пространства имен;BCL

Пространство имен Содержание
System Базовые типы, поддержка среды, математические функции и многое другое
System.CodeDom Поддержка создания кода, компиляции и запуска
System.Collections Контейнерные классы (списки, хеш-таблицы)
System.Diagnostics Регистрация событий, счетчики производительности и т. д.
System.Globalization Поддержка глобализации приложений .NET
System.IO Поддержка потоков, основанных на файловых системах
и последовательных портах
System.Resources Упрощение локализации приложений
System.Text Поддержка кодировок и класс для эффективного построения строк
System.Text.RegularExpressions Лексический разбор и поддержка регулярных выражений
Таблица 9.2. Пространства имен BCL

Именно эта часть FCL будет рассматриваться в данном разделе. Я ни при каких условиях не смог бы описать (или хотя бы просто перечислить) все классы BCL в книге, так как на эту тему можно легко написать целую книгу. Только пространство имен System содержит более 100 классов. Подробный справочник можно найти в .NET Framework SDK; его также можно открыть непосредственно из Delphi 2005 IDE. Я постараюсь описать лишь несколько важных классов, по возможности сравнивая их с аналогами из Delphi RTL.

Класс System.Object

Класс Object является общим предком для всех классов FCL (и Delphi для .NET). Он играет ту же роль, что и класс TObject в Delphi для Win32. Я уже упоминал, что TObject в Delphi для .NET является псевдонимом для System.Object, но не демонстрировал возможности этого класса.

Среди ключевых методов System.Object только GetType имеет аналог в объекте Delphi TObject, а остальные методы относятся к сравнению и представлению объектов:

  • Метод ReferenceEqual сравнивает две ссылки, переданные в параметрах, и проверяет, относятся ли они к одному объекту (то есть к одному адресу памяти). Это соответствует применению символа = к переменным, содержащим ссылки на объекты.
  • Метод Equals сравнивает содержимое объектов (поле за полем) и проверяет, совпадают ли хранящиеся в них данные; при этом сами объекты могут находиться по разным адресам памяти.
  • Метод GetType возвращает тип объекта. Результат относится к типу System.Type и с ним можно работать при помощи API-рефлексии.
  • Метод ToString преобразует объект в строковое представление, описывающее конкретный экземпляр и его данные. Метод является виртуальным и переопределяется в производных классах.
  • Метод GetHashCode возвращает хеш-код, идентифицирующий объект в хеш-таблицах. Метод также является виртуальным и часто переопределяется.

Для демонстрации некоторых методов (и особенно сравнения объектов) я создал пример FclSystemObject. Одна из кнопок примера вызывает разные методы класса System.Object для самой кнопки; результат показан на рис. 9.1.

Рис. 9.1. Кнопка Base в приложении FclSystemObject вызывает основные методы System.Object

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

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

Другой переопределенный метод Equals вызывается при нажатии кнопки Reference Equals после выделения двух объектов в полях со списками:

Обратите внимание: Equals является методом объекта и получает сравниваемый объект в параметре, тогда как Reference Equals является статическим методом класса и получает в параметрах оба сравниваемых объекта.

Хотя многие классы определяют или переопределяют ToString, во многих классах и базовых типах присутствует обратный метод (статический метод класса) Parse для преобразования строки в значение. Например, если имеется Double и строка, то преобразование между ними может выполняться кодом следующего вида:

Класс StringBuilder

System.Text.StringBuilder, классПри описании строк в .NET (см. главу 4) я упоминал, что при конкатенации строк рекомендуется использовать класс StringBuilder, потому что строки .NET неизменны, а добавление данных в существующую строку означает создание копии всей строки. Пример StringConcatSpeed из главы 4 наглядно продемонстрировал проблему и некоторые возможные решения.

Однако этим возможности класса System.Text.StringBuilder не ограничиваются. Этот класс позволяет создавать строки из разных типов данных (благодаря многократно перегруженным методам Append и AppendFormat). Наряду с присоединением символов класс позволяет организовать эффективное удаление (метод Remove), замену (метод Replace) и вставку (метод Insert). После того как построение строки будет завершено, строковые данные объекта выделяются стандартным методом ToString.

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

В программе StringBuilderDemo (рис. 9.2), являющейся приложением WinForms, определяются две строки: короткая и длинная. Пользователь выбирает строку при помощи переключателя и кнопок, выполняет приведенные фрагменты миллион раз и получает информацию о времени выполнения. Для короткой строки (около 20 символов) различия минимальны, но с увеличением объема строки (около 200 символов) версия StringBuilder начинает работать вдвое быстрее.

Рис. 9.2. Программа StringBuilderDemo демонстрирует различия в скорости при работе с обычными строками и объектами StringBuilder

В BCL включен удобный набор контейнерных классов — несомненно более полный, чем в “родной” Delphi RTL. Классы выполняют широкий спектр функций, от динамически расширяемых массивов объектов (ArrayList, классArrayList) до низкоуровневых поразрядных операций с целыми числами (BitArray, классBitArray), от контейнеров с быстрым доступом (таких, как HashTable, классHashTable) до специализированных контейнеров типа стеков или очередей.

Я также намерен уделить основное внимание паре классов, которые будут сравниваться с соответствующими “родными” решениями Delphi. Класс ArrayList отчасти напоминает TList (более того, реализация TList в Delphi для .NET базируется на ArrayList), однако между ними существуют и серьезные различия.

На мой взгляд, самое принципиальное различие — невозможность сортировки элементов списка или эффективного выполнения метода Find. В таких случаях приходится использовать отдельный класс SortedList, классSortedList. Это относится и к контейнерам для строк (скажем, TStringList).

Для сравнения эффективности двух реализаций было написано приложение VCL StringListSpeedNet. Оно демонстрирует различия в синтаксисе и скорости при поиске строки в строковом массиве, хранящемся в одном из двух контейнеров:

Две кнопки в верхней части формы (форма для версии .NET показана на рис. 9.3) заполняют списки случайными строками. Код практически идентичен — как, впрочем, и эффективность:

Рис. 9.3. Приложение StringListSpeedNet с результатами хронометража

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

И снова код почти не изменился. Впрочем, есть одна немаловажная подробность: для версии ArrayList используется другой класс. Более того, для поиска в строке вызывается другой метод с дополнительным параметром. Вызываемые методы выполняют операцию поиска тысячу раз; далее приводится их основной код:

Необходимость вызывать другой метод для производного класса мне не очень-то по душе, но при попытке вызова IndexOf для SortedList снова происходит возврат к медленному последовательному поиску. С другой стороны, “родное” решение .NET работает гораздо быстрее, чем код Delphi RTL. Различия весьма значительные; версия с классом TStringList работает почти втрое медленнее, как для простого поиска, так и для поиска в отсортированном списке. Дело в том, что специально для .NET Framework была написана оптимизированная версия кода. Если написать аналогичную программу для Win32, эффективность кода Delphi RTL будет примерно соответствовать эффективности реализации ArrayList для .NET.

ПРИМЕЧАНИЕ

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

Другой полезный контейнер .NET — HashTable — ускоряет поиск элементов в списках (в том числе и строковых). Этот класс относится к категории ассоциативных массивов , или словарей ; каждый объект в таком контейнере представлен ключом (используемый для идентификации объекта) и значением (фактическими данными объекта). При поиске объекта по ключу (который может быть объектом произвольного типа) класс HashTable определяет хеш-код объекта — как было показано ранее, эта возможность встроена в класс System.Object.

Приведу два фрагмента кода из приложения StringListSpeedNet:

Для списка, состоящего из 100 000 строк, поиск в HashTable занимает вдвое меньше времени, чем поиск в отсортированном списке (и в 10 раз меньше, чем при простом поиске). Правда, не стоит забывать, что в хеш-таблице не могут храниться дубликаты строк.

Регулярные выражения

Подмножество BCL библиотеки FCL содержит множество классов, и отобрать среди них несколько интересных нелегко. И все же нельзя не упомянуть поддержку регулярных выражений, обеспечиваемую классом Regex, классRegex (определяемым в пространстве именSystem.Text.RegularExpressions, класс System.Text.RegularExpressions). регулярные выраженияпоиск;регулярные выражениявыражения, регулярныеРегулярные выражения представляют собой чрезвычайно мощное, хотя и непростое средство поиска информации в строках с применением сложных шаблонов. Класс BCL Regex применяется для разнообразных целей, среди которых проверка ввода, разбиение строк, поиск и замена подстрок и т. д.

Пример AssortedBCL демонстрирует три разных случая. Первый — проверка входного текста на соответствие формату номера кредитной карты (четыре группы из четырех цифр, разделенные дефисами):

Во втором случае та же строка делится на четыре части методом Split, возвращающим массив строк:

Обратите внимание на применение цикла for..in. Наконец, третья кнопка ищет заданный символ в строке. Сначала ищется одно совпадение, а затем — все совпадения. Учтите, что в первом случае метод Match всегда возвращает объект класса Match с флагом, обозначающим исход операции (успех или неудача). Во втором случае при отсутствии совпадений метод Matches возвращает пустую коллекцию. Соответствующий код выглядит так:

Регулярные выражения не имеют прямых аналогов в Delphi RTL, хотя их упрощенная версия предоставляется классом TMask, классTMask из модуля Masks.

Потоки данных в .NET

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

При работе с потоками .NET можно использовать классы, специализированные для чтения и записи различных структур данных (строки, числа и т. д.) или же предназначенные для чтения и записи байтовых массивов, предоставляемых потоковыми классами. При работе с файлами в большинстве случаев достаточно использовать классы StreamWriter, классStreamReader, классStreamReader/StreamWriter для текстовых файлов и классы BinaryWriter, классBinaryReader, классBinaryReader/BinaryWriter для двоичных файлов.

ПРИМЕЧАНИЕ

Аналогами таких классов в Delphi RTL можно считать низкоуровневые классы TReader и TWriter, в основном используемые для чтения и записи свойств в DFM-подобных потоках.

Например, приложение AssortedBCL позволяет сохранить содержимое журнала в локальном файле:

Для повторной загрузки файла также можно было бы воспользоваться объектом StreamReader, но на этот раз я написал более подробный код с созданием файлового потока и его присваиванием объекту класса StreamReader:

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

Класс Component

Component, классЕще один класс, заслуживающий более подробного описания, — базовый класс библиотеки компонентов System.Component, классSystem.Component. Как упоминалось ранее, в Delphi для .NET класс TComponent представляет собой псевдоним этого класса, а класс TComponentHelper предоставляет дополнительные возможности для имитации класса компонента VCL для Win32. В обоих случаях класс Component/TComponent является базовым классом всех визуальных и невизуальных компонентов, используемых на стадии конструирования в среде разработки.

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

Напротив, в .NET FCL за уничтожение объектов отвечает система GC. Тем не менее логические связи между компонентами необходимы на стадии конструирования, поэтому вы можете определять контейнеры компонентов, размещать в них компоненты и ссылаться на интерфейс контейнера (IContainer) через свойство Container компонента (также имеется свойство Site для обращения к интерфейсу ISite, интерфейсISite, который также задается контейнером и позволяет получить доступ к дополнительной информации на стадии конструирования).

Хотя эти свойства предназначены для стадии конструирования, мне кажется, было бы неплохо заменить список Components в VCL чем-то аналогичным. Пример CompContain демонстрирует принципы работы с контейнерами. В начале программа включает несколько компонентов во вновь созданный контейнер:

Даже если объект контейнера не сохранен в переменной, к нему можно обратиться через любой из включенных в него объектов — как это сделано в следующем коде, выводящем строковое представление самого контейнера ToString, количество содержащихся в нем компонентов и некоторые данные о каждом из его компонентов (рис. 9.4):

Включение нескольких связанных компонентов в контейнер отчасти компенсирует потерю очень интересной возможности компонентов VCL — вызова метода Notification. В Win32 очень важно было знать, в какой момент другой компонент удаляется из конструктора, потому что при этом приходилось задавать равными nil любые ссылки на уничтожаемый объект. Однако данная возможность также использовалась для управления ссылками между объектами — в .NET у нее не существует прямого аналога.

Рис. 9.4. Приложение CompContain демонстрирует процесс включения компонентов в контейнер

WinForms

Последняя область FCL, которой я хочу уделить особое внимание, — визуальная составляющая библиотеки, основанная на традиционном графическом интерфейсе Windows. Библиотека WinForms похожа на визуальную часть VCL и работает параллельно с ней. Тем не менее в отличие от RTL компания Borland не пыталась объединить две библиотеки и сохранила VCL как альтернативу для WinForms от компании Microsoft. Обе библиотеки в конечном счете вызывают функции Win32 API.

Только код, без DFM-файлов

Библиотека WinForms, как и VCL, включает формы и визуальные элементы с похожими свойствами, методами и событиями. Среди важных различий следует отметить то, что в WinForms нет ничего похожего на DFM-файлы. По аналогии с Java, все операции с формой (или другим визуальным контейнером), выполняемые на стадии конструирования, автоматически отражаются в исходном коде программы. Сгенерированный код (листинг 9.1) получается довольно большим, но, на мой взгляд, его стоит привести хотя бы один раз.

Как видно из листинга, конструктор вызывает метод InitializeComponent. Метод начинает свою работу с создания всех задействованных компонентов, что позволяет компонентам (в большинстве случаев) ссылаться друг на друга независимо от порядка инициализации. В первой части кода также находятся методы, временно приостанавливающие перерисовку формы (метод SuspendLayout). Затем следуют определения свойств всех компонентов, за ними следует определение свойств формы и активизация перерисовки (метод ResumeLayout).

Инициализация каждого элемента включает его добавление в свойство Controls компонента, в котором он содержится. В данном примере большинство компонентов размещается на форме, а два — на панели (btnColor и btnParent). Для подключения обработчиков к событиям в коде инициализации используется функция Include: как говорилось в главе 4, в .NET используются групповые (multicast) события, поэтому одному событию могут назначаться несколько обработчиков.

“Двойники” и другие различия

При переходе с VCL на .NET возникает одна серьезная проблема: несмотря на сходство многих концепций, также существует масса ложных “двойников”, сбивающих с толку. Например, если вы знаете о поддержке стыковки в VCL, то вас удивит, что в WinForms ее не существует, хотя имеется свойство Dock, — однако оно соответствует свойству Align многих элементов VCL. Различия в выборе имен также относятся к обработчикам события;WinFormsсобытий, которые в .NET вызываются по именам (Click или KeyPress), а в VCL снабжаются префиксом On (например, OnClick или OnKeyPress). Когда Microsoft потребовалось выбрать одну из существующих схем записи, была выбрана стандартная схема Visual Basic. Ситуация немного усложняется тем, что имя OnClick существует и принадлежит виртуальному методу, инициирующему событие Click (тогда как в VCL Click — виртуальный метод, инициирующий событие OnClick).

Имеются и другие существенные различия:

  • Свойство Caption (иногда называемое Text, как в TEdit) в WinForms всегда называется Text. Наличие единого имени — хорошая мысль, но к нему надо привыкнуть.
  • Свойство Color элемента или формы в WinForms называется BackColor. Также имеется свойство ForeColor, определяющее цвет текста (который в VCL задается свойством Font.Color).
  • Как упоминалось ранее, свойство Tag относится к типу System.Object, классSystem.Object.
  • Все элементы обладают интегрированной поддержкой связи с данными. В WinForms нет разных элементов Edit и DBEdit, как в VCL, а существует единый элемент TextBox, который может связываться с данными. Связь осуществляется через свойство DataBindings (см. описание ADO.NET в главе 16).
  • Также существует общее свойство CauseValidation и два взаимосвязанных события (Validating и Validated), обусловленные тем фактом, что все элементы могут связываться с данными.
  • Специальные свойства AccessibleDescription, AccessibleName и AccessibleRole предназначены для пользователей с ослабленным зрением и слепых.
  • Свойство VCL PopupMenu в WinForms называется ContextMenu.
  • Для вывода простого окна сообщения вместо глобальной функции ShowMessage используется один из перегруженных методов класса MessageBox (например, MessageBox.Show).
  • Класс WinForms Application содержит метод DoEvents, заменяющий метод ProcessMessages класса VCL TApplication.

Элементы WinForms

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

В WinForms не существует понятия графического элемента (TGraphicControl в VCL); таким образом, у каждого элемента имеется идентификатор окна, хранящийся в свойстве Handle.

В свойствах элемента WinForms хранится информация о родителе (Parent) и список дочерних элементов (Controls) — точно так же, как в VCL. Элементы WinForms обладают атрибутами состояния (такими, как Enabled, Focused и Visible) и позиционными свойствами Size и Location (заменяющими свойства Left, Top, Height и Width).

В приложении CompContain некоторые элементы формы размещаются внутри панели, которая становится их родителем. Одна из кнопок отображает информацию о связях между родителями и потомками. Результат работы следующего кода показан на рис. 9.5:

Рис. 9.5. Отображение связей “родитель/потомок” в приложении CompContain

В VCL свойства вида Parent Xxx означают, что значение свойства Xxx совпадает со значением одноименного свойства родительского элемента (например, свойства Color и ParentColor). В WinForms для этой цели используются свойства окружения (Cursor, Font, BackColor и ForeColor); если такое свойство не определено, используется значение родительского элемента. Так, в примере CompContain панель окрашена в более светлый цвет. Чтобы вернуть ее к основному цвету формы, достаточно вместо конкретного цвета задать свойству специальное значение Empty:

Методы и события классов элементов WinForms в целом похожи на свои аналоги из VCL. Свойство TControl.ControlStyle заменено методами GetStyle и SetStyle.

Взглянув на конкретный набор элементов, доступных по умолчанию, вы найдете в них много общего с VCL. Впрочем, есть и различия, о которых следует упомянуть. Конечно, в обоих наборах присутствуют традиционные элементы Windows (Button, ListBox, TextBox и т. д.) и расширенные стандартные элементы Win32 (TreeView, ListView, RichEdit и т. д.).

Стоит отметить, что в WinForms отсутствует элемент Memo, так как в Windows он в действительности представляет собой многострочное текстовое поле. Следовательно, чтобы создать поле Memo в приложении WinForms, следует разместить элемент TextBox и задать его свойству MultiLine значение True. Кроме того, в WinForms отсутствуют элементы HotKey, Animate, HeaderControl и PageScroller.

Конечно, также отсутствуют все элементы, разработанные компанией Borland: RadioGroup, StringGrid, MaskEdit, Bevel, ValueListEditor, PaintBox, MediaPlayer и др. WinForms также определяет ряд собственных элементов, в том числе LinkLabel и PropertyGrid.

От GDI к GDI+

WinForms;GDI+Еще одно важное различие между VCL и WinForms связано с выводом на экран (или другое устройство). В VCL используется классическая библиотека GDI, а в WinForms — более новая библиотека GDI+. По сравнению с GDI она обладает многими новыми возможностями: исчезла концепция контекста устройства, объекты GDI (такие, как кисти и перья) передаются в параметрах функций вывода, система координат основана на универсальных преобразованиях и вещественных координатах, кисти поддерживают градиентную закраску, в структуру Color включена поддержка альфа-наложения, предусмотрена поддержка большего количества графических форматов (GIF, JPEG, TIFF и PNG) и т. д. У GDI+ есть только один недостаток: если в вашей операционной системе нет оптимизированного драйвера GDI+, то графические операции будут выполняться медленнее, чем в традиционной версии GDI. Это особенно заметно при работе со шрифтами и текстом.

ПРИМЕЧАНИЕ

Вас интересует, почему компания Borland еще не обновила VCL для поддержки GDI+? Дело в том, что GDI+ откомпилирована в библиотеку с интерфейсом C++, а Delphi поддерживает компоновку с библиотеками COM или DLL языка C. Это обстоятельство существенно усложняет поддержку GDI+ в Delphi для Win32 (хотя и не делает ее невозможной). Конечно, поддержка GDI+ в .NET доступна через WinForms.

Как упоминалось в предыдущем перечне, в GDI+ не используются контексты устройств. Вам уже не придется выбирать перо в контексте устройства и рисовать линию текущим пером — достаточно нарисовать линию, передавая перо в параметре. С другой стороны, при вызове графического метода может передаваться либо перо, либо кисть, тогда как при вызове функции GDI Rectangle можно нарисовать прямоугольник текущим пером и закрасить его текущей кистью.

Для примера я написал простое приложение, которое рисует случайные линии с альфа-наложением и выводит текст, закрашенный градиентной кистью. Пример WinFormLines содержит меню с двумя связанными командами. Обратите внимание: в отличие от VCL визуальный конструктор WinForms поддерживает предварительный просмотр меню и его создание “на месте”. Визуальный конструктор меню WinForms в Delphi 2005 IDE изображен на рис. 9.6.

Код рисования линий (созданное изображение является временным и пропадает при перерисовке) выполняется в цикле тысячу раз:

Рис. 9.6. Визуальный конструктор меню в WinForms

В цикле программа меняет цвет пера и рисует линию, передавая перо в качестве параметра. Обратите внимание: цвет линии зависит от альфа-канала, следующего за тремя основными составляющими RGB. Не забудьте вызвать Free (то есть Dispose) для пера и объекта класса Graphics, возвращаемого методом CreateGraphics формы.

Другая команда меню программы выводит крупный текст с градиентной закраской (рис. 9.7). Разобраться в нем несложно — достаточно найти правильные имена классов и параметры:

Рис. 9.7. Градиентная закраска текста в примере WinFormLines

Класс Forms

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

TForm (VCL) Forms (WinForms)
Событие OnCreate Событие Load
Свойство Constraints Свойства MaximumSize и MinimumSize
Свойство Button.Default Свойство AcceptButton
Свойство BorderIcons Свойство ControlBox
Свойства AlphaBlend и AlphaBlendValue Свойство Opacity
Свойства TransparentColor и TransparentColorValue Свойство TransparencyKey
Значение fsStayOnTop свойства FormStyle Свойство TopMost
Событие OnClick главного меню Событие MenuStart (также имеется событие MenuComplete)
Метод SelectNext Методы GetNextControl и SelectNextControl
Таблица 9.3. Различия в именах свойств, методов и событий форм в VCL и WinForms

Класс Form в WinForms обладает некоторыми дополнительными возможностями по сравнению со своим аналогом VCL. Свойство BackgroundImage задает фоновое изображение для формы, свойство Language активизирует поддержку локализации, а свойство ShowInTaskBar упрощает работу с панелью задач. Конечно, к этому следует прибавить и поддержку GDI+ при рисовании на форме.Load, событие (WinForms) MenuStart, события

ПРИМЕЧАНИЕ

Формы WinForms поддерживают MDI. Это довольно странно, учитывая, что компания Microsoft объявила интерфейс MDI устаревшим при выходе Windows 95.

Возможности VCL, отсутствующие в WinForms

Несмотря на все приятные новшества WinForms, у некоторых возможностей VCL нет аналогов в WinForms. Не вдаваясь в подробности, скажу, что мне не хватает концепции действий, обеспечиваемой компонентом ActionList и сопутствующей архитектурой. Так как механизм действий действительно востребован, некоторые сторонние разработчики компонентов Delphi заполняют этот пробел, продавая компоненты с поддержкой соответствующей архитектуры. Примером может послужить пакет CommandMaster от Component Science (www.componentscience.net).

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

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

Наконец, у WinForms имеется весьма раздражающая особенность, которая проявляется при удалении обработчиков событий (возможно, добавленных случайно при щелчке на компоненте). В отличие от приложений VCL обработчик нельзя оставить пустым; вы должны удалить объявление и определение метода и отключить обработчик события в коде, сгенерированном визуальным конструктором.

Что далее?

В этой главе рассматривались основные библиотеки, доступные в Delphi для .NET. Мы начали с адаптации “родной” RTL Delphi на платформу .NET, а затем перешли к библиотеке FCL (Framework Class Library), включенной Microsoft в .NET SDK. Я кратко описал некоторые важные классы FCL и часть библиотеки, связанной с пользовательским интерфейсом (WinForms).

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

Использование стандартных дженериков Delphi для работы с наборами данных

Автор: Alex. Опубликовано в Программирование 18 Июнь 2015 . просмотров: 36368

Начиная с версии 2009, в Delphi на уровне языка и компилятора появилась поддержка универсальных типов или дженериков (известных также как параметризованные типы), аналога шаблонов в C++. Вместе с этими изменениями появился юнит System.Generics.Collections, служащий для работы с массивами и группировки данных в словари, списки, стеки и очереди. Именно об этом юните и о работе с ним пойдёт здесь речь.

Статья рассчитана на читателей, имеющих представление о том, что такое универсальный тип или шаблон. Здесь я буду рассматривать только использование юнита System.Generics.Collections. Будут рассмотрены основные классы, которые в нём реализованы, и даны примеры использования. Все приведённые примеры сделаны для Delphi XE7 и их работоспособность в других версиях Delphi не гарантируется.

TArray

Класс TArray юнита System.Generics.Collections содержит статические методы для поиска (BinarySearch) и сортировки массива (Sort). При поиске с помощью функции BinarySearch используется бинарный поиск с использованием O(log n) алгоритма, где n — количество элементов массива. Давайте рассмотрим пример использования класса TArray.

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

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

TDictionary и TObjectDictionary

Словарь TDictionary или TObjectDictionary – это коллекция пар ключ-значение. Разница между этими двумя классами в том, что второй класс умеет автоматически удалять экземпляры ключей-объектов и/или значений-объектов, т.е. вы можете использовать в качестве ключей или значений экземпляры объектов.

Добавить ключ с соответствующим значением в словарь вы можете с помощью методов Add (вернёт ошибку, если попытаться добавить ключ повторно) или AddOrSetValue (заменит значение для ключа, если ключ уже есть в коллекции). Удалять элементы словаря можно с помощью Remove (удаление одного элемента) и Clear (полная очистка словаря). Полезными могут быть события OnKeyNotify и OnValueNotify, которые происходят при добавлении, изменении или удалении пары (следует учитывать, что для одной операции может произойти несколько событий).

Добавление или удаление пары ключ-значение, а также чтение значения по ключу являются эффективными, близкими к O (1), т.к. для хранения пар используется хэш-таблица. Ключи не могут быть nil, а значения – могут.

Узнать наличие в словаре ключа или значения можно с помощью методов TryGetValue (пытается считать значение по ключу), ContainsKey (проверяет наличие ключа) и ContainsValue (проверяет наличие значения). Прочитать значение по ключу можно с помощью свойства Items, узнать количество пар в словаре – с помощью свойства Count. Получить список всех ключей можно из свойства Keys, а значений – из свойства Values.

Рассмотрим несколько вариантов использования словарей.

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

TList и TObjectList

Список TList или TObjectList – это упорядоченный список, доступ к элементам которого происходит по индексу. Разница между этими классами в том, что второй класс умеет автоматически удалять экземпляры элементов при их удалении из списка.

В список можно добавлять или вставлять элементы, менять и удалять их. Можно добавлять nil. При изменении списка срабатывает событие OnNotify.

Список можно сортировать, используя стандартные или свои компараторы. Можно искать в нём и делать реверсию.

Свойство Count показывает количество элементов в списке, а Capacity – количество зарезервированных мест. Прочитать элемент по индексу можно с помощью свойства Items.

Вот пример использования объекта TList.

Использование объекта TObjectList очень похоже, поэтому я лишь покажу пример, который покажет дополнительные тонкие моменты.

TThreadList

TThreadList – это тоже список, но потокобезопасный, т.е. с ним можно смело работать сразу из нескольких потоков. На самом деле – это обёртка над классом TList. Набор методов для работы с элементами здесь очень скромный: Add (добавление элемента), Clear (очистка списка), Remove (удаление элемента) и RemoveItem (удаление элемента с указанием направления поиска). А чтобы работать со списком в полную силу (чтение всех элементов, поиск, сортировка), нужно получить доступ к списку TList, который хранится внутри TThreadList. Сделать это можно с помощью функции блокировки LockList, которая заблокирует список и вернёт указатель на список TList. После работы со списком TList, список нужно разблокировать с помощью метода UnlockList. Также здесь есть очень полезное свойство Duplicates (дубликаты), которое задаёт поведение списка при добавлении дубликатов: разрешать добавление дубликатов (dupAccept), игнорировать дубликаты, не добавляя их, (dupIgnore) или генерировать ошибку при добавлении дубликата (dupError). По умолчанию свойство Duplicates имеет значение dupIgnore.

Вот пример работы со списком TThreadList (для создания потоков я использую класс TTask, о котором я уже рассказывал в статье «Параллельное программирование в Delphi XE7»).

TStack и TObjectStack

Стек TStack или TObjectStack – это стек элементов, работающий по принципу «последним пришёл — первым вышел» (last in — first out). Т.е. добавленные в стек элементы, вытаскиваются из него в обратном порядке. Стеки TStack и TObjectStack отличаются друг от друга тем, что второй стек предоставляет механизм автоматического удаления объектов удаляемых из стека.

Стек может быть произвольного размера. В стек можно добавлять nil. При изменении стека срабатывает событие OnNotify. Свойство Count показывает общее количество элементов в стеке.

Пример использования стека TStack.

Использование стека TObjectStack аналогичное и рассматривать его я здесь не буду. Упомяну лишь, что здесь можно использовать метод Extract, вместо Pop, если не требуется автоматическое удаление извлекаемого элемента.


TQueue и TObjectQueue

Очередь TQueue или TObjectQueue позволяет вам добавлять элементы в конец, а вытаскивать их из начала. Т.е. из очереди элементы будут считываться в том же порядке, в котором они были туда добавлены. Разница между очередями TQueue или TObjectQueue состоит в том, что очередь TObjectQueue умеет автоматически удалять объекты при удалении элементов из очереди.

Свойство Count показывает количество элементов в очереди. При добавлении или удалении элемента вызывается событие OnNotify. В очередь можно добавлять nil.

Вот пример использования очереди TQueue.

Использование стека TObjectQueue аналогичное и рассматривать его я здесь не буду. Здесь, так же как и в классах TObjectList и TObjectStack, можно использовать метод Extract вместо метода Dequeue, если не требуется автоматическое удаление извлекаемого элемента.

TThreadedQueue

TThreadedQueue — это ещё одна реализация очереди, но в отличие от TQueue или TObjectQueue, эта очередь предназначена для вставки и изъятия элементов из разных потоков. Для этой очереди задаётся ограничение на максимальное количество находящихся в ней элементов, и, если очередь максимально заполнена и какой либо поток пытается добавить ещё один элемент, то этот поток ожидает, пока в очереди появится свободное место или пока не истечёт время ожидания.

Очередь TThreadedQueue подходит, например, для реализации какого либо сервера, который принимает сообщения от клиентов в одном потоке (или нескольких потоках) и складывает их в очередь, а затем берёт эти сообщения из очереди и обрабатывает их в другом потоке (или нескольких потоках).

Вот пример использования очереди TThreadedQueue (для создания потоков я использую класс TTask, о котором я уже рассказывал в статье «Параллельное программирование в Delphi XE7»):

А вот результат, который будет выведен на консоль:

Теперь давайте разберёмся, как работает этот пример. Здесь чтение из очереди умышленно делается очень медленно, раз в 2 секунды. А записывающий поток пытается записать всё сразу. У него бы и получилось записать сразу все 9 сообщений, но у нас установлено ограничение на максимальный размер очереди – всего 5 элементов. Поэтому он записывает первые пять сообщений сразу, а при попытке записать шестое сообщение зависает в ожидании, пока в очереди не освободится место. Но мы опять же специально ограничили время ожидания всего одной секундой, поэтому через секунду он перестаёт ждать и выдаёт ошибку. То же самое происходит и со следующим седьмым сообщением. А вот к моменту отправки восьмого сообщения в очереди появляется свободное место и сообщение успешно записывается. С девятым опять случается неудача, потому, что только что на свободное место было записано сообщение 8 и очередь опять заполнена, а чтение происходит ну оооочень медленно.

Если будете плотно использовать этот класс, то вам может пригодиться ещё функция DoShutDown, которая объявляет, что очередь остановлена (после вызова этой функции новые элементы в очередь не добавляются, т.е. при вызове метода PushItem ничего не происходит), и свойство ShutDown, с помощью которого вы можете проверить, остановлена очередь или нет. Здесь нужно заметить, что после остановки очереди вы всё равно сможете считать попавшие туда элементы.

И в заключении об использовании стандартных дженериков Delphi.

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

Как в Delphi определить текущую операционную систему?

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

Версия Delphi XE2 привнесла новые возможности разработки приложений под Mac OS и iOS. Наряду с этим, разработчики в Delphi также получили возможность без использования сторонних компонентов и библиотек определять под какой операционной системой запущено приложение.

Основная информация по компоненту
Исходник официального примера и документация DocWiki

Демонстрационный пример разработан с использованием платформы FMX и показывает как определить версию операционной системы, архитектуру, какие сервис-паки установлены в операционной системе, использую стандартные возможности Delphi, входящие в состав Delphi XE2.

Внешний вид демонстрационного приложения представлен на рисунке ниже:

Программирование на языке Delphi. Глава 2. Основы языка Delphi. Часть 3

Оглавление


Программные модули


Структура модуля

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

После слова unit записывается имя модуля . Оно должно совпадать с именем файла, в котором находится исходный текст модуля. Например, если файл называется MathLib.pas, то модуль должен иметь имя MathLib. Заголовок модуля формируется автоматически при сохранении файла на диске, поэтому его не следует изменять вручную. Чтобы дать модулю другой заголовок, просто сохраните его на диске под другим именем.

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

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

Блок initialization является необязательным. Он состоит из операторов и выполняется автоматически непосредственно перед запуском основной программы. Блоки инициализации подключенных к программе модулей выполняются в том порядке, в котором они упоминаются в секции uses .

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

Если модуль не нуждается в инициализации и завершении, блоки initialization и finalization можно опустить.

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


    Выберите в главном меню команду File / New. , в появившемся диалоговом окне активизируйте значок с подписью Unit и щелкните на кнопке OK (рисунок 6).

Рисунок 6. Окно среды Delphi для создания нового модуля

Вы увидите, что среда Delphi создаст в редакторе кода новую страницу с текстом нового модуля Unit1 (рисунок 7):

Рисунок 7. Текст нового модуля в редакторе кода

  • Сохраните модуль под именем MathLib, выбрав в меню команду File / Save (рисунок 8):
  • Рисунок 8. Окно сохранения модуля

    Заметьте, что основная программа Console изменилась: в списке подключаемых модулей появилось имя модуля MathLib (рисунок 9). После слова in среда Delphi автоматически помещает имя файла, в котором находится модуль. Для стандартных модулей, таких как SysUtils, это не нужно, поскольку их местонахождение хорошо известно.

    Рисунок 9. Текст программы Console в окне редактора

    Теперь перейдем к содержимому модуля. Давайте объявим в нем константу Pi и две функции: Power — вычисление степени числа, и Average — вычисление среднего арифметического двух чисел:

    Вот как могла бы выглядеть программа, использующая модуль Math:

    После компиляции и запуска программы вы увидите на экране три числа (рисунок 10):

    Рисунок 10. Результат работы программы Console

    Стандартные модули языка Delphi

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

    К системным модулям относятся System, SysUtils, ShareMem, Math. В них содержатся наиболее часто используемые в программах типы данных, константы, переменные, процедуры и функции. Модуль System — это сердце среды Delphi; содержащиеся в нем подпрограммы обеспечивают работу всех остальных модулей системы. Модуль System подсоединяется автоматически к каждой программе и его не надо указывать в операторе uses .

    Модули визуальных компонентов (VCL — Visual Component Library) используются для визуальной разработки полнофункциональных GUI-приложений — приложений с графическим пользовательским интерфейсом (Graphical User Interface). Эти модули в совокупности представляют собой высокоуровневую объектно-ориентированную библиотеку со всевозможными элементами пользовательского интерфейса: кнопками, надписями, меню, панелями и т.д. Кроме того, модули этой библиотеки содержат простые и эффективные средства доступа к базам данных. Данные модули подключаются автоматически при помещении компонентов на форму, поэтому вам об этом заботиться не надо. Их список слишком велик, поэтому мы его не приводим.

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

    Исходные тексты стандартных модулей среды Delphi находятся в каталоге Delphi/Source.

    Область действия идентификаторов

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

    • каждый идентификатор должен быть описан перед тем, как он будет использован;
    • областью действия идентификатора является блок, в котором он описан;
    • все идентификаторы в блоке должны быть уникальными, т.е. не повторяться;
    • один и тот же идентификатор может быть по-разному определен в каждом отдельном блоке, при этом блоки могут быть вложенными;
    • если один и тот же идентификатор определен в нескольких вложенных блоках, то в пределах вложенного блока действует вложенное описание;
    • все глобальные описания подключенного модуля видны программе (подключающему модулю), как если бы они были сделаны в точке подключения;
    • если подключаются несколько модулей, в которых по-разному определен один и тот же идентификатор, то определение, сделанное в последнем подключенном модуле перекрывает все остальные;
    • если один и тот же идентификатор определен и в подключенном модуле, и в программе (подключающем модуле), то первый игнорируется, а используется идентификатор, определенный в программе (подключающем модуле). Доступ к идентификатору подключенного модуля возможен с помощью уточненного имени. Уточненное имя формируется из имени модуля и записанного через точку идентификатора. Например, чтобы в предыдущем примере получить доступ к стандартному значению числа ?, нужно записать System.Pi.

    Строки


    Строковые значения

    Строка — это последовательность символов. При программировании строковые значения заключаются в апострофы, например:

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

    Для записи отсутствующих на клавиатуре символов используется символ # , за которым следует десятичный номер символа в кодовой таблице ASCII, например:

    Строка, которая не содержит символов, называется пустой:

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

    Строковые переменные

    Строковая переменная объявляется с помощью зарезервированного слова string или с помощью идентификатора типа данных AnsiString, например:

    Строку можно считать бесконечной, хотя на самом деле ее длина ограничена 2 ГБ. В зависимости от присваиваемого значения строка увеличивается и сокращается динамически. Это удобство обеспечивается тем, что физически строковая переменная хранит не сами символы, а адрес символов строки в области динамически распределяемой памяти (о динамически распределяемой памяти мы расскажем ниже). При создании строки всегда инициализируются пустым значением (»). Управление динамической памятью при операциях со строками выполняется автоматически с помощью стандартных библиотек языка Delphi.

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

    Символы строки индексируются от 1 до N+1, где N — реальная длина строки. Символ с индексом N+1 всегда равен нулю (#0). Для получения длины следует использовать функцию Length , а для изменения длины — процедуру SetLength (см. ниже).

    Для того чтобы в программе обратиться к отдельному символу строки, нужно сразу за идентификатором строковой переменной или константы в квадратных скобках записать его номер. Например, FriendName[1] возвращает значение ‘A’, а FriendName[4] — ‘x’. Символы, получаемые в результате индексирования строки, принадлежат типу Char.

    Достоинство строки языка Delphi состоит в том, что она объединяет в себе свойства строки самого языка Delphi и строки языка C. Оперируя строкой, вы оперируете значением строки, а не адресом в оперативной памяти. В то же время строка не ограничена по длине и может передаваться вместо C-строки (как адрес первого символа строки) в параметрах процедур и функций. Чтобы компилятор позволил это сделать, нужно, записывая строку в качестве параметра, преобразовать ее к типу PChar (тип данных, используемый в языке Delphi для описания нуль-терминированных строк языка C). Такое приведение типа допустимо по той причине, что строка всегда завершается нулевым символом (#0), который хоть и не является ее частью, тем не менее всегда дописывается сразу за последним символом строки. В результате формат строки удовлетворяет формату C-строки. О работе с нуль-терминированными строками мы поговорим чуть позже.

    Строки в формате Unicode

    Для поддержки работы со строками формата Unicode в язык Delphi имеется строковый тип данных WideString. Работа со строками типа WideString почти не отличается от работы со строками типа AnsiString; существуют лишь два отличия.

    Первое отличие состоит в представлении символов. В строках типа WideString каждый символ кодируется не одним байтом, а двумя. Соответственно элементы строки WideString — это символы типа WideChar, тогда как элементы строки AnsiString — это символы типа AnsiChar.

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

    Короткие строки

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

    Короткая строка может иметь длину от 1 до 255 символов. Предопределенный тип данных ShortString эквивалентен объявлению string [255].

    Реальная длина строки может быть меньше или равна той, что указана при ее объявлении. Например, максимальная длина строки Friend в примере выше составляет 30 символов, а ее реальная длина — 9 символов. Реальную длину строки можно узнать с помощью встроенной функции Length . Например, значение Length(Friend) будет равно 9 (количество букв в слове Alexander).

    Все символы в строке типа ShortString пронумерованы от 0 до N, где N — максимальная длина, указанная при объявлении. Символ с номером 0 — это служебный байт, в нем содержится реальная длина короткой строки. Значащие символы нумеруются от 1. Очевидно, что в памяти строка занимает на 1 байт больше, чем ее максимальная длина. Поэтому значение SizeOf(Friend) будет равно 31.

    Обратиться к отдельному символу можно так же, как и к символу обычной строки. Например, выражения FriendName[1] и FriendName[9] возвращают соответственно символы ‘A’ и ‘r’. Значения FriendName[10] .. FriendName[30] будут случайными, так как при объявлении типизированной константы FriendName символы с номерами от 10 до 30 не были инициализированы. Символы, получаемые в результате индексирования короткой строки, принадлежат типу Char.

    Поскольку существует два типа строк: обычные (длинные) строки и короткие строки, возникает закономерный вопрос, можно ли их совмещать. Да, можно! Короткие и длинные строки могут одновременно использоваться в одном выражении, поскольку компилятор языка Delphi автоматически генерирует код, преобразующий их тип. Более того, можно выполнять явные преобразования строк с помощью конструкций вида ShortString(S) и AnsiString(S).

    Операции над строками

    Выражения, в которых операндами служат строковые данные, называются строковыми . Они состоят из строковых констант, переменных, имен функций и строковых операций. Над строковыми данными допустимы операции сцепления и отношения.

    Операция сцепления (+) применяется для сцепления нескольких строк в одну строку.

    Выражение


    Результат


    ‘Object’ + ‘ Pascal’ ‘Object Pascal’

    Операции отношения (=, <>, >, =, ‘ABCDE’

    True
    ‘Office’ = ‘Office’ True
    ‘USIS’ > ‘US’ True

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

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


    Выражение


    Значение строки


    Name: string[6]; Name := ‘Mark Twain’; ‘Mark T’

    Допускается смешение в одном выражении операндов строкового и символьного типа, например при сцеплении строки и символа.

    Строковые ресурсы

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

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

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

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

    Форматы кодирования символов

    Существуют различные форматы кодирования символов. Отдельный символ строки может быть представлен в памяти одним байтом (стандарт Ansi), двумя байтам (стандарт Unicode) и даже четырьмя байтами (стандарт UCS-4 — Unicode). Строка «Wirth» (фамилия автора языка Pascal — прародителя языка Delphi) будет представлена в указанных форматах следующим образом (рисунок 11):

    Рисунок 11. Форматы кодирования символов

    Существует также формат кодирования MBCS (Multibyte Character Set), согласно которому символы одной строки кодируются разным количеством байт (одним или двумя байтами в зависимости от алфавита). Например, буквы латинского алфавита кодируются одним байтом, а иероглифы японского алфавита — двумя. При этом латинские буквы и японские иероглифы могут встречаться в одной и той же строке.

    Стандартные процедуры и функции для работы со строками

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

    • Concat (S1, S2, . , Sn): string — возвращает строку, полученную в результате сцепления строк S1, S2, . Sn. По своей работе функция Concat аналогична операции сцепления (+).
    • Copy (S: string, Index, Count: Integer): string — выделяет из строки S подстроку длиной Count символов, начиная с позиции Index.
    • Delete (var S: string, Index, Count: Integer) — удаляет Count символов из строки S, начиная с позиции Index.
    • Insert (Source: string; var S: string, Index: Integer) — вставляет строку Source в строку S, начиная с позиции Index.
    • Length (S: string): Integer — возвращает реальную длину строки S в символах.
    • SetLength (var S: string; NewLength: Integer) — устанавливает для строки S новую длину NewLength.

    Выражение


    Значение S


    S := Concat(‘Object ‘, ‘Pascal’);‘Object Pascal’
    S:= Copy(‘Debugger’, 3, 3);‘bug’
    S := ‘Compile’; Delete(S, 1, 3);‘pile’
    S := ‘Faction’; Insert(‘r’, S, 2)‘Fraction’

    • Pos (Substr, S: string): Byte — обнаруживает первое появление подстроки Substr в строке S. Возвращает номер той позиции, где находится первый символ подстроки Substr. Если в S подстроки Substr не найдено, результат равен 0.

    Выражение


    Результат


    Pos(‘rat’, ‘grated’)2
    Pos(‘sh’, ‘champagne’)

    • Str (X [: Width [: Decimals] ], var S: string) — преобразует числовое значение величины X в строку S. Необязательные параметры Width и Decimals являются целочисленными выражениями. Значение Width задает ширину поля результирующей строки. Значение Decimals используется с вещественными числами и задает количество символов в дробной части.

    Выражение


    Значение S


    Str(-200, S);‘-200’Str(200 : 4, S);‘ 200’Str(1.5E+02 : 4, S);‘ 150’

    • Val (S: string, var V; var Code: Integer) — преобразует строку S в величину целого или вещественного типа и помещает результат в переменную V. Если во время операции преобразования ошибки не обнаружено, значение переменной Code равно нулю; если ошибка обнаружена (строка содержит недопустимые символы), Code содержит номер позиции первого ошибочного символа, а значение V не определено.

    Выражение


    Значение V


    Значение Code


    Val(‘100’, V, Code); 100 Val(‘2.5E+01’, V, Code); 25.0 Val(‘2.5A+01’, V, Code); 4

    Описанные процедуры и функции являются базовыми для всех остальных подпрограмм обработки строк из модуля SysUtils.

    • AdjustLineBreaks (const S: string): string — возвращает копию строки S, в которой все мягкие переносы строк (одиночные символы #13 или #10) заменены жесткими переносами строк (последовательность символов #13#10).
    • AnsiCompareStr (const S1, S2: string): Integer — сравнивает две строки, делая различие между заглавными и строчными буквами; учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • AnsiCompareText (const S1, S2: string): Integer — сравнивает две строки, не делая различий между заглавными и строчными буквами; учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • AnsiDequotedStr (const S: string; Quote: Char): string — удаляет специальный символ, заданный параметром Quote, из начала и конца строки и заменяет парные спецсимволы на одиночные; если специальный символ отсутствует в начале или конце строки, то функция возвращает исходную строку без изменений.
    • AnsiExtractQuotedStr (var Src: PChar; Quote: Char): string — делает то же, что и функция AnsiDequotedStr, но результат возвращается вместо исходной строки, которая имеет тип PChar.
    • AnsiLowerCase (const S: string): string — преобразует заглавные буквы строки S к строчным буквам с учетом местного языка.
    • AnsiPos (const Substr, S: string): Integer — выполняет те же действия, что и функция Pos, но в отличие от нее поддерживает работу с многобайтовой MBCS-кодировкой.
    • AnsiQuotedStr (const S: string; Quote: Char): string — преобразует строку, заменяя все вхождения специального символа, заданного параметром Quote, на парные спецсимволы, а также помещает специальный символ в начало и конец строки. Поддерживает работу с MBCS-кодировкой.
    • AnsiSameCaption (const Text1, Text2: string): Boolean — сравнивает две строки, не делая различие между заглавными и строчными буквами, а также не учитывая символ ‘&’; учитывает местный язык.
    • AnsiSameStr (const S1, S2: string): Boolean — сравнивает строки, делая различие между строчными и заглавными буквами; учитывает местный язык.
    • AnsiSameText (const S1, S2: string): Boolean — сравнивает строки, не делая различие между строчными и заглавными буквами; учитывает местный язык.
    • AnsiUpperCase (const S: string): string — преобразует все строчные буквы в заглавные; учитывает местный язык.
    • CompareStr (const S1, S2: string): Integer — выполняет сравнение двух строк, делая различие между строчными и заглавными буквами; не учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • CompareText (const S1, S2: string): Integer — выполняет сравнение двух строк, не делая различий между строчными и заглавными буквами; не учитывает местный язык. Возвращаемое значение меньше нуля, если S1 S2.
    • DateTimeToStr (const DateTime: TDateTime): string — преобразует значение даты и времени в строку.
    • DateTimeToString (var Result: string; const Format: string; DateTime: TDateTime) — преобразует значение даты и времени в строку, выполняя при этом форматирование в соответствии со значением строки Format. Управляющие символы строки Format подробно описаны в справочнике по среде Delphi.
    • DateToStr (const DateTime: TDateTime): string — преобразует числовое значение даты в строку.
    • Format (const Format: string; const Args: array of const): string — форматирует строку в соответствии с шаблоном Format, заменяя управляющие символы шаблона на значения элементов открытого массива Args. Управляющие символы подробно описаны в справочнике по среде Delphi.
    • FormatDateTime (const Format: string; DateTime: TDateTime): string — преобразует значение даты и времени в строку, выполняя при этом форматирование в соответствии со значением строки Format. Управляющие символы строки Format подробно описаны в справочнике по среде Delphi.
    • BoolToStr (B: Boolean; UseBoolStrs: Boolean = False): string — преобразует булевское значение в строку. Если параметр UseBoolStrs имеет значение False, то результатом работы функции является одно из значений ‘0’ или ‘-1’. Если же параметр UseBoolStrs имеет значение True, то результатом работы является одно из значений ‘FALSE’ или ‘TRUE’ (программист может задать другие значения; о том, как это сделать, читайте в справочнике по системе Delphi).
    • IntToHex (Value: Integer; Digits: Integer): string — возвращает шестнадцатиричное представление целого числа Value. Параметр Digits задает количество цифр результирующей строки.
    • IntToStr (Value: Integer): string — преобразует целое число Value в строку.
    • IsDelimiter (const Delimiters, S: string; Index: Integer): Boolean — проверяет, является ли символ S[Index] одним из символов строки Delimiters. Функция поддерживает работу с многобайтовой MBCS-кодировкой.
    • IsValidIdent (const Ident: string): Boolean — возвращает True, если строка Ident является правильным идентификатором языка Delphi.
    • LastDelimiter (const Delimiters, S: string): Integer — возвращает индекс последнего вхождения одного из символов строки Delimiters в строку S.
    • LowerCase (const S: string): string — преобразует все заглавные буквы строки S к строчным; не учитывает местный язык (в преобразовании участвуют лишь символы в диапазоне от ‘A’ до ‘Z’).
    • QuotedStr (const S: string): string — преобразует исходную строку в строку, взятую в одиночные кавычки; внутри строки символы кавычки дублируются.
    • SameText (const S1, S2: string): Boolean — сравнивает строки, не делая различие между строчными и заглавными буквами; учитывает местный язык.
    • SetString (var S: string; Buffer: PChar; Len: Integer) — копирует строку с типом PChar в строку с типом string. Длина копируемой строки задается параметром Len.
    • StringOfChar (Ch: Char; Count: Integer): string — возвращает строку, в которой повторяется один и тот же символ. Количество повторений задается параметром Count.
    • StringToGUID (const S: string): TGUID — преобразует строковое представление глобального уникального идентификатора в стандартный тип TGUID.
    • StrToBool (const S: string): Boolean — преобразует строку в булевское значение.
    • StrToBoolDef (const S: string; const Default: Boolean): Boolean — преобразует строку в булевское значение. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
    • StrToDate (const S: string): TDateTime — преобразует строку со значением даты в числовой формат даты и времени.
    • StrToDateDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку со значением даты в числовой формат даты и времени. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
    • StrToDateTime (const S: string): TDateTime — преобразует строку в числовое значение даты и времени.
    • StrToDateTimeDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку в числовое значение даты и времени. В случае невозможности преобразования, функция возвращает значение, переданное через параметр Default.
    • StrToInt (const S: string): Integer — преобразует строку в целое число. Если строка не может быть преобразована в целое число, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
    • StrToIntDef (const S: string; Default: Integer): Integer — преобразует строку в целое число. Если строка не может быть преобразована в целое число, функция возвращает значение, заданное параметром Default.
    • StrToInt64 (const S: string): Int64 — 64-битный аналог функции StrToInt — преобразует строку в 64-битное целое число. Если строка не может быть преобразована в 64-битное число, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
    • StrToInt64Def (const S: string; const Default: Int64): Int64 — 64-битный аналог функции StrToIntDef — преобразует строку в 64-битное целое число. Если строка не может быть преобразована в 64-битное число, функция возвращает значение, заданное параметром Default.
    • StrToTime (const S: string): TDateTime — преобразует строку в числовой формат времени. Если строка не может быть преобразована в числовой формат времени, функция генерирует исключительную ситуацию класса EConvertError (обработка исключительных ситуаций рассматривается в главе 4).
    • StrToTimeDef (const S: string; const Default: TDateTime): TDateTime — преобразует строку в числовой формат времени. В случае ошибки преобразования, функция возвращает значение, заданное параметром Default.
    • TimeToStr (Time: TDateTime): string — преобразует числовое значение времени в строку.
    • Trim (const S: string): string — возвращает часть строки S без лидирующих и завершающих пробелов и управляющих символов.
    • Trim (const S: WideString): WideString — Unicode-аналог функции Trim — возвращает часть строки S без лидирующих и завершающих пробелов и управляющих символов.
    • TrimLeft (const S: string): string — возвращает часть строки S без лидирующих пробелов и управляющих символов.
    • TrimLeft const S: WideString): WideString — Unicode-аналог функции TrimLeft — возвращает часть строки S без лидирующих пробелов и управляющих символов.
    • TrimRight (const S: string): string — возвращает часть строки S без завершающих пробелов и управляющих символов.
    • TrimRight (const S: WideString): WideString — Unicode-аналог функции TrimRight — возвращает часть строки S без завершающих пробелов и управляющих символов.
    • UpperCase (const S: string): string — преобразует все строчные буквы строки S в заглавные; не учитывает местный язык (в преобразовании участвуют лишь символы в диапазоне от ‘a’ до ‘z’).
    • WideFormat (const Format: WideString; const Args: array of const): WideString — Unicode-аналог функции Format, учитывающий символы местного языка, — форматирует строку в соответствии с шаблоном Format, заменяя управляющие символы в шаблоне на значения элементов открытого массива Args. Управляющие символы подробно описаны в справочнике по системе Delphi.
    • WideFmtStr (var Result: WideString; const Format: WideString; const Args: array of const) — аналог функции WideFormat. Отличие в том, что WideFmtStr возвращает результат через параметр Result, а не как значение функции.
    • WideLowerCase const S: WideString): WideString — Unicode-аналог функции LowerCase (учитывает местный язык) — преобразует все заглавные буквы строки S к строчным буквам.
    • WideSameCaption (const Text1, Text2: WideString): Boolean — Unicode-аналог функции AnsiSameCaption — сравнивает две строки, не делая различие между строчными и заглавными буквами, а также не учитывая символ ‘&’; учитывает местный язык.
    • WideSameStr (const S1, S2: WideString): Boolean — Unicode-аналог стандартной операции сравнения строк — сравнивает две строки, делая различие между строчными и заглавными буквами.
    • WideSameText (const S1, S2: WideString): Boolean — Unicode-аналог функции SameText (учитывает местный язык) — сравнивает строки, не делая различие между строчными и заглавными буквами.
    • WideUpperCase (const S: WideString): WideString — Unicode-аналог функции UpperCase (учитывает местный язык) — преобразует все строчные буквы строки S в заглавные.
    • WrapText (const Line: string; MaxCol: Integer = 45): string — разбивает текст Line на строки, вставляя символы переноса строки. Максимальная длина отдельной строки задается параметром MaxCol.
    • WrapText (const Line, BreakStr: string; const BreakChars: TSysCharSet; MaxCol: Integer): string — более мощный аналог предыдущей функции — разбивает текст Line на строки, вставляя символы переноса строки.
    • AnsiToUtf8 (const S: string): UTF8String — перекодирует строку в формат UTF8.
    • PUCS4Chars (const S: UCS4String): PUCS4Char — возвращает указатель на первый символ строки формата UCS-4 для работы со строкой, как с последовательностью символов, заканчивающейся символом с кодом нуль.
    • StringToWideChar (const Source: string; Dest: PWideChar; DestSize: Integer): PWideChar — преобразует стандартную строку к последовательности Unicode-символов, завершающейся символом с кодом нуль.
    • UCS4StringToWideString (const S: UCS4String): WideString — преобразует строку формата UCS-4 к строке формата Unicode.
    • Utf8Decode (const S: UTF8String): WideString — преобразует строку формата UTF-8 к строке формата Unicode.
    • Utf8Encode (const WS: WideString): UTF8String — преобразует строку формата Unicode к строке формата UTF-8.
    • Utf8ToAnsi (const S: UTF8String): string — преобразует строку формата UTF-8 к стандратной строке.
    • WideCharLenToString (Source: PWideChar; SourceLen: Integer): string — преобразует строку формата Unicode к стандартной строке. Длина исходной строки задается параметром SourceLen.
    • WideCharLenToStrVar (Source: PWideChar; SourceLen: Integer; var Dest: string) — аналог предыдущей функции — преобразует строку формата Unicode к стандартной строке. Длина исходной строки задается параметром SourceLen, а результат возвращается через параметр Dest.
    • WideCharToString (Source: PWideChar): string — преобразует последовательность Unicode-символов, завершающуюся символом с кодом нуль, к стандартной строке.
    • WideCharToStrVar (Source: PWideChar; var Dest: string) — аналог предыдущей функции — преобразует последовательность Unicode-символов, завершающуюся символом с кодом нуль, к стандартной строке. Результат возвращается через параметр Dest.
    • WideStringToUCS4String (const S: WideString): UCS4String — преобразует строку формата Unicode к строке формата UCS-4.

    Массивы


    Объявление массива

    Массив — это составной тип данных, состоящий из фиксированного числа элементов одного и того же типа. Для описания массива предназначено словосочетание array of . После слова array в квадратных скобках записываются границы массива, а после слова of — тип элементов массива, например:

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

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

    Массив может быть определен и без описания типа:

    Чтобы получить доступ к отдельному элементу массива, нужно в квадратных скобках указать его индекс, например

    Объявленные выше массивы являются одномерными, так как имеют только один индекс. Одномерные массивы обычно используются для представления линейной последовательности элементов. Если при описании массива задано два индекса, массив называется двумерным , если n индексов — n-мерным . Двумерные массивы используются для представления таблицы, а n-мерные — для представления пространств. Вот пример объявления таблицы, состоящей из 5 колонок и 20 строк:

    То же самое можно записать в более компактном виде:

    Чтобы получить доступ к отдельному элементу многомерного массива, нужно указать значение каждого индекса, например

    или в более компактной записи

    Эти два способа индексации эквивалентны.

    Работа с массивами

    Массивы в целом участвуют только в операциях присваивания. При этом все элементы одного массива копируются в другой. Например, если объявлены два массива A и B,

    то допустим следующий оператор:

    Оба массива-операнда в левой и правой части оператора присваивания должны быть не просто идентичны по структуре, а описаны с одним и тем же типом, иначе компилятор сообщит об ошибке. Именно поэтому все массивы рекомендуется описывать в секции type .

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

    Для массивов определены две встроенные функции — Low и High. Они получают в качестве своего аргумента имя массива. Функция Low возвращает нижнюю, а High — верхнюю границу этого массива. Например, Low(A) вернет значение 1, а High(A) — 5. Функции Low и High чаще всего используются для указания начального и конечного значений в операторе цикла for . Поэтому вычисление суммы элементов массива A лучше переписать так:

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

    требуются два вложенных цикла for и две целые переменные Col и Row для параметров этих циклов:

    Массивы в параметрах процедур и функций

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

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

    Внутри подпрограммы Average нижняя граница открытого массива A равна нулю (Low(A) = 0), а вот значение верхней границы (High(A)) неизвестно и выясняется только на этапе выполнения программы.

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

    Вот пример использования функции Average:

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

    И еще одно важное замечание по поводу открытых массивов. Некоторые библиотечные подпрограммы языка Delphi принимают параметры типа array of const — открытые массивы констант . Массив, передаваемый в качестве такого параметра, обязательно конструируется в момент вызова подпрограммы и может состоять из элементов различных типов (!). Физически он состоит из записей типа TVarRec , кодирующих тип и значение элементов массива (записи рассматриваются ниже). Открытый массив констант позволяет эмулировать подпрограммы с переменным количеством разнотипных параметров и используется, например, в функции Format для форматирования строки (см. выше).

    Уплотнение структурных данных в памяти

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

    Ключевое слово packed указывает компилятору, что элементы структурного типа должны храниться плотно прижатыми друг к другу, даже если это замедляет к ним доступ. Если структурный тип данных описан без ключевого слова packed , компилятор выравнивает его элементы на 2- и 4-байтовых границах, чтобы ускорить к ним доступ.

    Заметим, что ключевое слово packed применимо к любому структурному типу данных, т.е. массиву, множеству, записи, файлу, классу, ссылке на класс.

    Множества


    Объявление множества

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

    Для описания множественного типа используется словосочетание set of , после которого записывается базовый тип множества:

    Теперь можно объявить переменную множественного типа:

    Можно объявить множество и без предварительного описания типа:

    В выражениях значения элементов множества указываются в квадратных скобках: [2, 3, 5, 7], [1..9], [‘A’, ‘B’, ‘C’]. Если множество не имеет элементов, оно называется пустым и обозначается как [ ]. Пример инициализации множеств:

    Количество элементов множества называется мощностью . Мощность множества в языке Delphi не может превышать 256.

    Операции над множествами

    При работе с множествами допускается использование операций отношения (=, <>, >=, in .

    Операции сравнения (=, <>). Два множества считаются равными, если они состоят из одних и тех же элементов. Порядок следования элементов в сравниваемых множествах значения не имеет. Два множества A и B считаются не равными, если они отличаются по мощности или по значению хотя бы одного элемента.

    Выражение


    Результат


    [1, 2] <> [1, 2, 3] True [1, 2] = [1, 2, 2] True [1, 2, 3] = [3, 2, 1] True [1, 2, 3] = [1..3] True

    Операции принадлежности (>=, = B равно True, если все элементы множества B содержатся в множестве A. Выражение A = [1, 2] True [1, 2] in . Используется для проверки принадлежности элемента указанному множеству. Обычно применяется в условных операторах.

    Выражение


    Результат


    5 in [1..9] True 5 in [1..4, 6..9] False

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

    можно заменить более коротким:

    Операцию in иногда пытаются записать с отрицанием: X not in S. Такая запись является ошибочной, так как две операции следуют подряд. Правильная запись имеет вид: not (X in S).

    Объединение множеств (+) . Объединением двух множеств является третье множество, содержащее элементы обоих множеств.

    Выражение


    Результат


    [ ] + [1, 2] [1, 2] [1, 2] + [2, 3, 4] [1, 2, 3, 4]

    Пересечение множеств (*) . Пересечение двух множеств — это третье множество, которое содержит элементы, входящие одновременно в оба множества.

    Выражение


    Результат


    [ ] * [1, 2] [ ] [1, 2] * [2, 3, 4] [2]

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

    Выражение


    Результат


    [1, 2, 3] — [2, 3] [1] [1, 2, 3] — [ ] [1, 2, 3]

    В язык Delphi введены две стандартные процедуры Include и Exclude, которые предназначены для работы с множествами.

    Процедура Include (S, I) включает в множество S элемент I. Она дублирует операцию + (плюс) с той лишь разницей, что при каждом обращении включает только один элемент и делает это более эффективно.

    Процедура Exclude (S, I) исключает из множества S элемент I. Она дублирует операцию — (минус) с той лишь разницей, что при каждом обращении исключает только один элемент и делает это более эффективно.

    Выражение


    Результат


    S := [1, 3]; [1, 3] Include(S, 2); [1, 2, 3] Exclude(S, 3) [1, 2]

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

    Записи


    Объявление записи

    Запись — это составной тип данных, состоящий из фиксированного числа элементов одного или нескольких типов. Описание типа записи начинается словом record и заканчивается словом end . Между ними заключен список элементов, называемых полями , с указанием идентификаторов полей и типа каждого поля:

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

    Чтобы получить в программе реальную запись, нужно создать переменную соответствующего типа:

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

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

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

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

    Допускается применение оператора присваивания и к записям в целом, если они имеют один и тот же тип. Например,

    После выполнения этого оператора значения полей записи Friend станут равными значениям соответствующих полей записи BestFriend.

    Записи с вариантами

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

    Вариантная часть напоминает условный оператор case . Между словами case и of записывается особое поле записи — поле признака . Оно определяет, какой из вариантов в данный момент будет активизирован. Поле признака должно быть равно одному из расположенных следом значений. Каждому значению сопоставляется вариант записи. Он заключается в круглые скобки и отделяется от своего значения двоеточием. Пример описания записи с вариантами:

    Обратите внимание, что у вариантной части нет отдельного end , как этого можно было бы ожидать по аналогии с оператором case . Одно слово end завершает и вариантную часть, и всю запись.

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

    Характеристика системы программирования Delphi. Историчекские сведения о создании Delphi.

    Порядок создания приложения в Delphi.

    Delphi— язык программирования, который используется в одноимённой среде разработки. Название используется начиная с 7 версии среды разработки, ранее это был Object Pascal, разработанный фирмой Borland и изначально реализованный в её пакете Borland Delphi, от которого и получил в 2003 году своё нынешнее название. Object Pascal по сути является наследником языка Pascal с объектно-ориентированными расширениями.

    Изначально среда разработки была предназначена исключительно для разработки приложений Microsoft Windows, затем был реализован также для платформ GNU/Linux (как Kylix), однако после выпуска в 2002 году Kylix 3 его разработка была прекращена, и, вскоре после этого, было объявлено о поддержке Microsoft .NET. При этом высказывались предположения, что эти два факта взаимосвязаны.

    Реализация среды разработки проектом Lazarus (Free Pascal, компиляция в режиме совместимости с Delphi) позволяет использовать его для создания приложений на Delphi для таких платформ, как GNU/Linux, Mac OS X и Windows CE.

    Delphi — результат развития языка Турбо Паскаль, который, в свою очередь, развился из языка Паскаль. Паскаль был полностью процедурным языком, Турбо Паскаль, начиная с версии 5.5, добавил в Паскаль объектно-ориентированные свойства, а Delphi — объектно-ориентированный язык программирования с возможностью доступа к метаданным классов (то есть к описанию классов и их членов) в компилируемом коде, также называемом интроспекцией. Так как все классы наследуют функции базового класса TObject, то любой указатель на объект можно преобразовать к нему, после чего воспользоваться методом ClassType и функцией TypeInfo, которые и обеспечат интроспекцию. Также отличительным свойством Дельфи от С++ является то, что объекты по умолчанию располагаются в динамической памяти. Однако можно переопределить виртуальные методы NewInstance и FreeInstance класса TObject. Таким образом, абсолютно любой класс может осуществить «желание» «где хочу — там и буду лежать». Соответственно организуется и «многокучность».

    Де-факто Object Pascal, а затем и язык Delphi являются функциональными наращиваниями Turbo Pascal. Об этом говорят обозначения версий компилятора. Так, в Delphi 7 компилятор имеет номер версии 15.0 (Последняя версия Borland Pascal / Turbo Pascal обозначалась 7.0, в Delphi 1 компилятор имеет версию 8.0, в Delphi 2 — 9.0, и т. д. Номер версии 11.0 носит компилятор Pascal, входивший в состав среды C++ Builder).

    Delphi оказал огромное влияние на создание концепции языка C# для платформы .NET. Многие его элементы и концептуальные решения вошли в состав С#. Одной из причин называют переход Андерса Хейлсберга, одного из ведущих разработчиков Дельфи, из компании Borland Ltd. в Microsoft Corp.

    Версия 1 была предназначена для разработки под 16-разрядную платформу Win16;

    Версии со второй компилируют программы под 32-разрядную платформу Win32;

    Вместе с 6-й версией Delphi вышла совместимая с ним по языку и библиотекам среда Kylix, предназначенная для компиляции программ под операционную систему GNU/Linux;

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

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

    Delphi for .NET — среда разработки Delphi, а также язык Delphi (Object Pascal), ориентированные на разработку приложений для .NET.

    Первая версия полноценной среды разработки Delphi для .NET — Delphi 8. Она позволяла писать приложения только для .NET.

    В настоящее время, в Delphi 2006, можно писать приложения для .NET, используя стандартную библиотеку классов .NET, VCL для .NET. Среда также позволяет создавать .NET-приложения на C# и Win32-приложения на C++. Delphi 2006 содержит функции для написания обычных приложений с использованием библиотек VCL и CLX.

    Delphi 2006 поддерживает технологию MDA с помощью ECO (Enterprise Core Objects) версии 3.0.

    В марте 2006 года компания Borland приняла решение о прекращении дальнейшего совершенствования интегрированных сред разработки JBuilder, Delphi и C++ Builder по причине убыточности этого направления. Планировалась продажа IDE-сектора компании. Группа сторонников свободного программного обеспечения организовала сбор средств для покупки у Borland прав на среду разработки и компилятор.[6]

    Однако в ноябре того же года было принято решение отказаться от продажи IDE бизнеса. Тем не менее, разработкой IDE продуктов теперь будет заниматься новая компания — CodeGear, которая будет финансово полностью подконтрольна Borland.

    В августе 2006 года Borland выпустил облегченные версию RAD Studio под именем Turbo: Turbo Delphi, Turbo Delphi for .NET, Turbo C#, Turbo C++.

    procedure TMainForm.FormCreate(Sender: TObject);

    TStringList — это потомок TStrings, реализующий абстрактные методы.>

    Что значат операторы @ и ^ в Делфи?

    Что значат операторы @ и ^ ?? Так же хочется узнать, что будет в Pkts если Psh[4, 1] = ?

    1 ответ 1

    Оператор @ обозначает взятие адреса переменной (получение указателя на переменную)

    Оператор ^ в коде обозначает разыменование указателя и получение значения переменной.

    Оператор ^ при объявлении типа обозначает использование указателя. Например PPoint = ^TPoint обозначает, что объект типа PPoint будет указателем на объект типа TPoint .

    Эти операторы дополняют друг друга.

    что будет в Pkts если Psh[4, 1] = 230 ?

    В Pkts[4,1] будет элемент со смещением 9 от Psh[4,1] .

    Давайте разберем по шагам, мы получаем адрес переменной Psh[4,1], тут же его разыменовываем обратно в переменную, и от этой переменной берем [9] элемент (то есть похоже что это строка или другой массив).

    Обратите внимание, что подобный код не скомпилируется в Делфи, т.к. не указан разыменуемый тип @Psh[4, 1] .

    Блог GunSmoker-а

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

    25 декабря 2011 г.

    Разработка системы плагинов в Delphi

    Эта статья — продолжение старой серии.

    Напомню, что те статьи, несмотря на некоторый практический выхлоп в конце, всё же не очень-то освещали тему плагинов, а представляли собой копание во внутренностях работы DLL и пакетов Delphi. Было решено эту серию закончить — именно как серию о плагинах.

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

    В общем, все эти факторы в итоге перевесили мою лень и я решил написать нормальную статью про плагины.

    Обновление: я вынес много материала с объяснениями в отдельную статью Разработка API (контракта) для своей DLL.

    Примечание: я был бы очень благодарен, если кто-нибудь со знанием Visual Studio (C++) просмотрел бы раздел 8 (и особенно — касательно генерации заголовочников для Visual Studio C++) на предмет моих ошибок.

    Оглавление

    Что у нас есть

    Что хотим получить

    Мы хотим разработать систему плагинов — т.е. набор правил, по которым сторонние разработчики (т.е. не авторы программы) могли бы писать функциональность, встраивающуюся в основную программу и как-бы являющуюся её частью. При этом хочется, чтобы плагины можно было бы писать на любом (нативном) языке программирования, а не только Delphi и C++ Builder.

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

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

    При этом предполагается, что плагины будут «тяжеловесные». Т.е. предполагающие существенный объём кода для реализации их функциональности. Ну, скажем, вроде как плагины звукового вывода в WinAmp, архиваторные, Lister-ые или FS-плагины в Total Commander или протокольные плагины в QIP.

    Некоторые задачи более удачно ложатся на «легковесные плагины» — скрипты. К примеру, как макросы в MS Word. Вам нужно выбирать систему на скриптах, если ваши плагины должны много обращаться к интерфейсу (и, в частном случае, — внутренним объектам) программы, но при этом сами они относительно просты. Так, что их можно выразить на скриптовом языке. И в этом случае эта статья — не для вас (в этом случае могу ткнуть в направлении статьи про TMS Scripter Studio Pro в 6-м номере журнала Blaise Pascal Magazine).

    В целом, в одном приложении вполне может быть реализовано две системы плагинов — для разных потребностей. К примеру, тот же MS Word (помимо макросов и VBA) поддерживает плагины в виде COM-объектов.

    Как пойдём

    Сразу замечу, что в этой статье я не буду говорить «почему». Я просто озвучу вариант, который выбрал я. Ответы на вопросы «почему» можно почерпнуть в старой серии статей.

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

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

    Если вы не начинающий (а зачем вы это читаете?) — вы можете сделать такой выбор сами. Если же вы начинающий и озабочены тем, что не можете сделать выбор, вот мой совет — просто следуйте этой статье. Когда у вас накопится опыт, вы уже более чётко будете представлять себе, что вы хотите получить, какие есть варианты достижения этого, какие у них плюсы и минусы. Так что вы сможете сделать свой выбор. Потом, не сейчас.

    Основные понятия

    Плаги́н (от англ. plug-in) — независимо компилируемый программный модуль, динамически подключаемый к основной программе, предназначенный для расширения и/или использования её возможностей. Плагин — это маленькая программка, которая встраивается в основную (большую) программу и расширяет её возможности.

    Основная программа при этом называется «ядро» (core) или (иногда) «сервер».

    Чаще всего основной программой является .exe файл, а плагины — это .dll файлы.

    Ядро предоставляет сервисы, которые плагин может использовать. К ним относится предоставляемая плагину возможность зарегистрировать себя в ядре, а также протокол обмена данными с другими плагинами. Плагины являются зависимыми от сервисов, предоставляемых ядром и отдельно (сами по себе) не используются.

    Протокол (также называемый API — Application Programming Interface) — набор правил, контракт, которому соглашаются следовать ядро и плагины, чтобы понять друг друга и успешно взаимодействовать. Все сервисы ядра могут предоставляться только в рамках этого соглашения.

    Заголовочники, заголовочные файлы (headers) — набор исходных файлов, которые содержат объявления структур, использующихся в протоколе плагинов. Как правило, не содержат реализации. Заголовочные файлы предоставляются на нескольких языках — как правило, это язык, на котором написана программа (в нашем случае — Delphi), C++ (как стандарт) и некоторыми дополнительными (Basic и т.п.). Все эти файлы эквивалентны и просто представляют собой перевод из одного языка программирования на другой. Чем больше языков будет в комплекте — тем лучше. Если вы не предоставите заголовочные файлы для какого-то языка, то программисты на этом языке не смогут писать плагины для вашей программы, пока они сами не переведут файлы с предоставляемого языка (Delphi или C++) на их язык. Т.е. отсутствие заголовочников на каком-то языке — это не красный «стоп», но достаточное препятствие. В этом смысле очень удачно выглядит COM (где описание хранится в универсальном формате библиотеки типов — TLB). Вам не нужно ничего делать, кроме как распространять .tlb файл (который также может быть встроен в .dll). Если язык умеет работать с COM — он может импортировать информацию из TLB и создать заголовочник самостоятельно. TLB файл — это двоичный файл. Его создают и редактируют в каком-нибудь редакторе, либо он генерируется средой разработки. Его также можно «скомпилировать» из текстового описания — IDL файла (.idl или .ridl).

    Документация — представляет собой словесное описание протокола плагинов. Она пишется разработчиком программы для разработчиков плагинов (т.е. она односторонняя). Конечно, вы можете вести документацию и для себя лично, но сейчас речь не про неё. Итак, в этой документации как минимум должно быть формальное описание API — перечисление всех функций, методов, интерфейсов и типов данных с объяснениями «как» и «зачем» (т.н. Reference). Дополнительно, документация может содержать неформальное описание процесса разработки плагинов (guide, how-to и т.п.). В простейших случаях документация пишется прямо в заголовочниках (комментариях), но чаще всего это файл (или файлы) в формате chm, html или pdf.

    SDK (Software Development Kit) — набор из заголовочников и документации. SDK — это то, что необходимо стороннему разработчику для написания плагинов к вашей программе. SDK — это то, что вы должны создать и публично распространять для всех желающих писать плагины к вашей программе.

    Базовый набор правил и соглашений

    Итак, для системы плагинов я выбрал схему DLL + интерфейсы. Замечу, что мы не можем использовать пакеты по соображениям межязыковой совместимости, а COM мне не нравится отложенной выгрузкой :) Также замечу, что предлагаемая схема вполне будет способна работать с плагинами в виде пакетов, если вы захотите это сделать, но при этом не будет никаких плюшек пакетов (управление памятью, разделение классов), окромя бонуса с DllMain (как описано в оригинальной серии статей).

    Правило номер два — в системе плагинов обязательно должны быть явные функции инициализации и финализации. Это значит, что каждая DLL должна экспортировать минимум 2 функции, которые будут вызываться непосредственно (первой) после загрузки плагина ядром и перед самой выгрузкой (последней). Зачем нужны отдельные функции.

    Правило номер три — модель вызова любых функций и методов в системе плагинов должна быть safecall . Напомню, что safecall — это, на самом деле, stdcall с неявным HRESULT. Например, вот три примера эквивалентных объявлений (т.е. они написаны по-разному, но представляют собой одно и то же):

    Правило номер четыре — обработка ошибок в стиле COM (safecall/HRESULT). Т.е. в Delphi это прозрачно будут исключения.

    Правило номер пять — все строки должны иметь тип WideString .

    Правило номер шесть — вы не должны использовать типы данных Delphi, потому что они не имеют аналога в других языках. Например, string , array of , TObject , TForm (и вообще любые объекты и уж тем более компоненты) и т.п. Что можно использовать — целочисленные типы ( Integer , Cardinal , Int64 , UInt64 , NativeInt , NativeUInt , Byte , Word и т.п.; я бы не рекомендовал использовать Currency , если только он вам действительно нужен), вещественные ( Single и Double ; я бы рекомендовал избегать типов Extended и Comp , если только они действительно вам нужны и иначе никак), перечислимые и subrange-типы (с некоторыми оговорками), символьные типы ( AnsiChar и WideChar , но не Char ), строки (только в виде WideString ), логический тип ( BOOL , но не Boolean ), интерфейсы (interface), в методах которых используются допустимые типы, записи (record) из вышеуказанных типов, а также указатели на них (в том числе указатели на массивы из вышеуказанных типов, но не динамические массивы).

    Как узнать, какой тип можно использовать, а какой — нет? Относительно простое правило — если вы не видите тип в этом списке, и типа нет в модуле Windows (модуле Winapi.Windows , начиная с Delphi XE2), то этот тип использовать нельзя. Если же тип перечислен мною выше или находится в модуле Windows / Winapi.Windows — используйте его. Это достаточно грубое правило, но для начала — сойдёт.

    Правило номер семь — как только вы опубликовали какой-то тип (интерфейс), вы не должны его изменять. Если вам нужно его расширить или изменить — вы вводите новый интерфейс (новую версию интерфейса), но не меняете старый.

    Правило номер восемь — вы не используете разделяемый менеджер памяти (не подключаете модуля вроде ShareMem , SimpleShareMem и т.п.).

    Правило номер девять — все интерфейсы API должны иметь GUID. Все интерфейсы вне API (используемые только ядром) могут не иметь GUID.

    Кроме этих правил есть ещё множество негласных общих правил программирования, которые не специфичны для системы плагинов, а должны выполняться всегда. Вот некоторые из них (составлены Реймондом Ченом):

    • Всё, что не определено — неопределённо. Это может звучать как тавтология, но, на самом деле, это очень полезная тавтология. Многие правила ниже являются просто частным случаем этого правила.
    • Все параметры должны быть допустимы. Контракт функции может применяться только если вызывающий выполнил все свои обязательства, и одно из них — все параметры должны быть тем, чем они заявлены. Это частный случай правила «Всё, что не определено — неопределённо»:
      • Указатели должны быть не равны nil , если только противное не указано явно.
      • Указатели указывают на то, на что они должны указывать. Если функция принимает указатель на CRITICAL_SECTION , то вам нужно передавать указатель на корректную CRITICAL_SECTION .
      • Указатели корректно выровнены. Выравнивание указателей — это фундаментальное требование архитектуры, хотя многие люди и пренебрегают им из-за всепрощающей архитектуры x86.
      • Вызывающий имеет право использовать память, на которую ему указывают. Это означает отсутствие вещей вроде указателей на освобождённую память или на память, над которой у вызывающего нет контроля.
      • Все буфера должны быть доступны и корректны вплоть до задекларированного или подразумеваемого размера. Если вы передаёте указатель на буфер и говорите, что он 10 байтов в длину, то буфер действительно должен иметь размер в 10 байт.
      • Описатели должны ссылаться на корректные объекты, которые не были уничтожены. Если функция хочет описатель окна, то вам нужно дать ей описатель настоящего окна.
    • Все параметры стабильны:
      • Вы не можете менять параметр, пока работает функция, в которую он передан.
      • Если вы передали указатель, то память, на которую он указывает, не должна модифицироваться другим потоком в течение вызова функции.
      • Вы также не можете освобождать эту память во время вызова функции.
    • Передаётся правильное число параметров и в правильном соглашении вызова. Это ещё один особый случай правила «Всё, что не определено — неопределённо».
      • Слава богу, современные компиляторы отказываются передавать неверное число параметров, хотя вы будете удивлены, узнав, скольким людям удаётся обмануть компилятор, чтобы передать неверные параметры, обычно используя изощрённые приведения типов.
      • Когда вы вызываете метод объекта, то Self должен быть объектом. Опять-таки, это — то, что автоматически отслеживает современный компилятор, хотя людям, использующим COM из C (и, да, такие бывают), приходится передавать его вручную, так что иногда они лажают.
    • Время жизни параметров функций:
      • Вызываемая функция может использовать параметры во время своего выполнения.
      • Вызываемая функция не может использовать параметры после возврата управления. Конечно же, если вызывающий и вызываемый устанавливают отдельное соглашение о продлении времени жизни параметров, то применимы и такие правила:
        • Время жизни параметра, который является интерфейсом (указателем на COM объект), может быть увеличено вызовом метода IUnknown.AddRef .
        • Многие параметры передаются в функции с подразумеваемым контекстом, что они будут использованы после выхода из функции. Если это так, то гарантия продления времени жизни параметра на срок, который нужен вызываемой функции, является ответственностью вызывающего. К примеру, если вы регистрируете функцию обратного вызова (callback), то она должна быть допустима, пока вы не отмените её регистрацию.
    • Входные буфера:
        Функции разрешено читать буфер от начала до указанной или подразумеваемой границы буфера, даже если для определения результата выполнения функции требуется только часть информации из буфера.
    • Выходные буфера:
      • Выходной буфер не может пересекаться с входным буфером или любым другим выходным буфером.
      • Функции разрешено писать в выходной буфер в границах, указанными вызывающим, даже если для записи результата нужен не весь буфер.
      • Если функции нужна только часть буфера для хранения результата вызова, то содержимое неиспользованной части буфера не определено.
      • Если функция завершается неудачей и документация не указывает состояние выходных буферов при неудаче, то содержимое выходных буферов не определено. Это частный случай правила «Всё, что не определено — неопределённо».
      • Заметьте, что COM устанавливает дополнительные правила для выходных буферов. COM требует, чтобы все выходные буферы находились бы в маршаллируемом состоянии даже при завершении функции с ошибкой. Для объектов, которые требуют нетривиального маршаллинга (наиболее яркие примеры: интерфейсы и BSTR (WideString)), это означает, что выходные буфера должны быть равны nil при ошибке.

    (Запомните, каждый пункт здесь является базовым правилом, а не непреложной истиной. Считайте, что перед каждым пунктом написано «Если явно не указано обратное, то. «. Если вызывающий и вызываемый соглашаются на исключении из правила, то это исключение будет работать.)

    Написать этот список было непростым делом: всё равно что выписать все недопустимые шахматные ходы. Эти правила настолько автоматически выполняются, что они скорее не правила, а вещи, которые просто существуют, и действовать иначе является сумасшествием. В результате, я уверен, будет множество других «правил, настолько очевидных, что о них не говорят», которые я упустил (к примеру: «вы не можете завершить поток, который выполняет чью-то ещё функцию»).

    Удобное правило для оценки того, что вы можете и не можете делать: спросите себя, «Как бы мне понравилось, если бы кто-то сделал это со мной?» (это особый случай теста «представьте, если бы это было возможным»).

    Структура папок и файлов

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

    1. Общие файлы — используются и ядром и плагинами
    2. Файлы ядра (то, к чему не имеют доступа плагины)
    3. Файлы плагинов (то, к чему не имеет доступа ядро)
    4. Файлы вне системы плагинов (функциональность ядра и плагинов)

    Поэтому, для начала, чтобы отделить п4 от пп1-3, давайте создадим новую папку (в любом месте), скажем — PluginAPI. В эту папку мы будем помещать всё то, что имеет отношение к системе плагинов, вне этой папки будем хранить всё остальное.

    Далее, в этой папке создадим три подпапки: Headers (для общих файлов), Core (для файлов ядра) и Plugins (для файлов плагинов). Headers — это то, что вы должны распространять публично для всех желающих писать плагины для вашей программы (часть SDK, заголовочники). Core будет использоваться только вами (как разработчиком программы), а Plugins часто может оказаться пустой, но если это не так, то там лежат файлы, которые вы должны будете прикладывать к Headers, когда вы распространяете SDK. Это — дополнительная функциональность, обёртка вокруг заголовочников.

    Далее, вы создаёте основную программу. Поскольку у меня программы нет, я возьму уже готовую — это демка RichEdit из комплекта поставки Delphi. Программа-пример представляет собой простой текстовый редактор. Файлы этой демки (remain.pas, remain.dfm и т.п.) относятся к пункту 4: файлы вне системы плагинов. Поэтому вы сохраняете основную программу в любом месте, а затем добавляете нужные файлы через Project/Add to project (либо прописываете пути поиска в проекте или IDE, но я не буду это показывать).

    Какие файлы? Вроде у нас ещё ничего нет? Вот-вот. Так что давайте (наконец-то!) что-то попишем.

    Менеджер плагинов

    Откройте свою программу (в примере — скопированную демку RichEdit) и сделайте File/New unit. Сохраните этот модуль в папке PluginAPI\Core под именем PluginManager.pas и введите в него такую заготовку:
    Этот код объявляет и создаёт менеджер плагинов. Менеджер плагинов — это вспомогательный код в вашей программе, который служит для управления плагинами. Он выполняет всю черновую работу с плагинами, так что вам не нужно засорять код своей основной программы. Менеджер создаётся автоматически при старте программы и автоматически удаляется при выходе из программы.

    Вы видите тут три части. Секция interface перечисляет то, с чем будет работать основная программа. Пока это глобальная функция Plugins для доступа к менеджеру и сам менеджер — интерфейс IPluginManager : пока пустой.

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

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

    Итак, что будет делать менеджер плагинов? Ну, наверное для начала плагины надо бы найти и загрузить. Как это обычно делают? Есть два способа:

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

      Минусы:

      • Пользователю нужно настраивать плагины вручную.

    2. Программа загружает как плагины все файлы из предопределённой папки.
      Плюсы:
      • Пользователю ничего не надо делать.

      Минусы:

      • Чтобы удалить или добавить плагин, его нужно скопировать в папку или удалить из неё.
      • Плагин нельзя «отключить», не удалив его.

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

    Итак, что должен уметь делать менеджер плагинов, чтобы реализовать какую-то из схем выше?

    Загрузка одного плагина

    Какую-бы схему мы ни реализовали бы — нам обязательно в любом случае потребуется функция загрузки одного плагина. Вот давайте с неё и начнём:
    Ух, что-то тут много всего появилось. Давайте по порядку. Начнём с изменений в IPluginManager .

    Собственно функция загрузки плагина — это LoadPlugin :
    Как видите, она принимает имя файла плагина для загрузки. Очевидно, что она должна возвращать загруженный плагин. Нам надо его как-то представить — вот тут появляется новая сущность: «плагин». Он у нас представлен новым интерфейсом IPlugin .

    Лирическое отступление. Сразу заметим такую вещь: сейчас мы говорим про внутреннюю кухню ядра, мы ещё не начали формировать API (напомню: исходный файл менеджера плагинов лежит в PluginAPI\Core, а не в PluginAPI\Headers). Именно поэтому тут совершенно нормально использовать string , соглашение вызова register и другие вещи, специфичные для Delphi: потому что их будет использовать только наша программа и никто иной.

    Окей, возвращаясь к коду. Функция LoadPlugin устроена просто (если вы посмотрите на её реализацию в классе TPluginManager ):
    Она создаёт наш плагин (в виде объекта TPlugin , реализующего интерфейс IPlugin ) и регистрирует его в своём «списке всех плагинов» — FItems . Как вы видите, функция LoadPlugin на самом деле не выполняет непосредственно загрузку плагина, а делегирует (передаёт) эту работу классу-оболочке для плагина TPlugin . Это — правильно. Загрузка плагина — забота плагина и его класса. Управление плагинами — забота менеджера плагинов. Правильное разделение обязанностей, короче говоря.

    Раз уж у нас появился «список плагинов», то неплохо бы его выставить наружу — так в IPluginManager появляются свойства Items и Count :
    Они дают ядру доступ к списку плагинов, позволяя их перебрать.

    Ну и если плагин загружен, то надо ведь и обратное действие уметь выполнять: выгрузку плагинов. Вот у нас появляется ещё один метод: UnloadPlugin .
    Метод принимает номер плагина для его выгрузки. Из его реализации (в классе TPluginManager ):
    видно, что он просто удаляет плагин из списка, уничтожая его.

    Пока мы ещё говорим про класс менеджера плагинов, заметим такую вещь: мы работаем с интерфейсами и для FItems у нас используется динамический массив. Это значит, что все типы данных являются управляемыми и нам не нужно их освобождать руками. Т.е. если ядро вызовет три раза LoadPlugin для загрузки трёх плагинов, а потом просто выйдет, то у нас не будет никакой утечки памяти. Менеджер плагинов начнёт удаляться в finalization (при условии, что ссылку на него никто больше не держит), при этом автоматически очистится FItems (как авто-управляемый динамический массив), а все его элементы будут автоматически освобождены (как автоуправляемые интерфейсы) — опять же при условии, что на них больше нет ссылок. В этот момент и произойдёт выгрузка каждого плагина.

    Итак, с IPluginManager и TPluginManager мы закончили, давайте посмотрим теперь на IPlugin / TPlugin .

    Тут пока совсем всё просто: наружу интерфейс выставляет несколько свойств, которые могут быть интересны ядру: индекс плагина, имя файла и описатель загруженного файла.
    Реализация методов в классе TPlugin тривиальна. Единственный момент — обратите внимание на реализацию свойства Index .
    В данном случае оно реализовано через поиск (см. TPluginManager.IndexOf ).
    Можно было бы сделать иначе: хранить индекс плагина в поле класса TPlugin . Оба решения имеют как плюсы, так и минусы, но в целом разницы нет никакой (у вас будет загружено более 1000 плагинов? Навряд ли).

    Более интересно выглядят (скрытые) конструктор и деструктор плагина. Помимо тривиальностей по инициализации и заполнению свойств плагина, они выполняют собственно работу, ради которой всё и затевалось: загрузку и выгрузку DLL.

    Уже в этот момент этот код можно потестировать. К примеру, если вы бросите на форму основной программы TButton , TEdit и TListbox , то вы можете написать такой тестовый код:
    Запустите программу, вы можете ввести в Edit имя файла любой DLL и нажать на кнопку для загрузки её как плагина. При этом она тут же появится в списке загруженных плагинов.

    Конечно, сейчас это может показаться несколько странным: как это так получается, что любая DLL может быть загружена как плагин? И что вообще делает этот плагин? Собственно, ничего странного нет: ведь мы пока не написали ни одной строчки API, у нас нет никакого контракта для плагинов, пока мы писали лишь код поддержки. Именно поэтому для плагина (пока!) подходит любая DLL и она просто ничего не делает.

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

    Загрузка папки с плагинами

    Следующая задача — загрузить целую папку с плагинами. Раз у нас уже есть функция загрузки одного плагина, то теперь мы можем вызвать её в цикле по всем плагинам в папке (пусть даже пока функция загрузки просто грузит DLL, а сами плагины пока что ничего не делают — не беда).

    Я не буду приводить весь код целиком, а приведу лишь добавленный и изменённый код. Код в многоточиях оставлен без изменений.
    Опять у нас получилось больше кода, чем изначально планировалось. И вот почему.

    Как видите, основной новый метод тут — LoadPlugins . Он принимает имя папки и необязательное расширение файлов. Реализация метода ищет в указанной папке все файлы и загружает те из них, которые подходят под указанное расширение. Если расширение не указывать, то будут загружены вообще все файлы.

    К примеру:
    или:
    В реализации метода LoadPlugins нет ничего сложного, за исключением одного момента: обработка ошибок. Предположим, один из плагинов не смог загрузится. Что тогда делать? Есть два варианта: первый — остановиться и сообщить об ошибке. Это самый простой вариант и он мне не нравится. Зато можно было бы ничего больше не писать. Вариант два — продолжить загрузку плагинов. Именно этот вариант я и показал. Тут возникает вопрос, что делать с ошибкой загрузки плагина и как о ней сообщить. Ведь если мы грузим целую папку с плагинами, то отказать в загрузке может не один плагин, а много.

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

    Тут возникает два момента: во-первых, раз уж мы возбуждаем свою ошибку, то нам надобен для этого класс. Использовать Exception я вам категорически запрещаю. Поэтому мы вводим свои классы ошибок:
    Кроме того, для последней ситуации кроме сообщения нам требуется хранить дополнительную информацию: список плагинов, которые отказались грузится. Зачем это нужно? Ну, скажем, ядро может отключить сбойнувшие плагины, чтобы не грузить их во второй раз в будущем при следующей загрузке. Плагин сбойнул? Отключили. Если пользователь исправит ошибку, препятствующую загрузке плагина — он включит плагин обратно.

    Итак, для этого к исключению мы пристыковываем список файловых имён:
    А метод LoadPlugins теперь может собирать информацию о сбойнувших плагинах:
    и возбуждать единую ошибку в конце работы:
    Ну и раз уж мы определили свои типы ошибок, неплохо бы их соблюдать. Поэтому изменяем метод LoadPlugin , чтобы при исключении он возбуждал бы именно наш тип ошибки:
    Ну, вроде и всё с этим. Как обычно, вы можете проверить код в своём приложении — пусть оно грузит содержимое подпапки Plugins. Пусть даже сейчас это ничего не будет делать.

    Отключение плагинов

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

    К этой задаче можно подступиться по разному. Я предлагаю такое решение: завести «чёрный список» плагинов. Любой плагин можно добавить в этот список. Любой плагин можно удалить из списка. Функция LoadPlugin (а следовательно и функция LoadPlugins ) не станет грузить плагин, если он находится в чёрном списке. Соотвественно, чтобы отключить плагин — его надо занести в чёрный список (рассматривайте его как список отключенных плагинов), а чтобы включить — удалить из этого списка.

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

    Итого получаем (и снова, я привожу лишь изменения, а не весь код):
    Тут тоже ничего сложного: чёрный список плагинов хранится в FBanned , куда плагин можно добавить или из которого удалить через функции Ban и Unban соответственно. Функция CanLoad используется для определения статуса плагина: нужно его грузить или нет. Помимо собственно чёрного списка, функция дополнительно проверяет уже загруженные плагины, блокируя их повторную загрузку.

    Вспомогательные функции Save- и LoadSettings используются для сохранения и загрузки списка заблокированных плагинов в реестр, следуя правилам хорошего тона. Таким образом, тестовый код может выглядеть так:
    Результат работы функции SaveSettings в реестре выглядит так:

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

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

    Скачать исходный код к этому моменту можно тут.

    Теперь, когда у нас готова основа архитектуры плагинов со стороны ядра, можно начать прорабатывать контракт плагинов: API. Начнём с их инициализации. Как уже говорилось, у DLL должно быть минимум две функции: инициализации и финализации, которые будут вызываться первыми и последними. Вот давайте их сейчас и напишем. Откройте вашу программу и сделайте File/New unit. Сохраните новый модуль в папке PluginAPI\Headers под именем PluginAPI.pas:
    Здесь мы определяем прототипы двух функций (Init и Done). Обе они имеют соглашение вызова safecall, а функция инициализации к тому же принимает параметры от ядра и возвращает плагин (т.е. самого себя). Пока у нас нет функциональности, поэтому я вписал самый базовый интерфейс, который только может быть. Заметьте, что мы сейчас уже говорим про API, поэтому должны придерживаться упомянутых в начале статье правил — в частности, касаемо соглашения вызова и типов данных.

    Также в модуле определены имена, которые должны иметь функции инициализации и финализации. Имена выбраны как случайные — это сделано специально, чтобы в произвольной DLL таких имён точно бы не оказалось. Таким образом, если загружать произвольную DLL как плагин, то эта операция будет неудачной, потому что в произвольно взятой DLL (а не специально разработанном плагине) уж точно нет функции с именем ’87D4EDFB420343F2976EB3CF4DB7C224′. Для получения такого имени я нажал в редакторе кода Delphi комбинацию Ctrl + Shift + G и получил:
    После чего я удалил [], <> и -, оставив только
    И таким образом я получил имя, которое гарантировано уникально.

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

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

    Т.е. мы говорим, что плагин для нашей программы представляет собой переименованную в .MyAppPlugin-файл обыкновенную DLL, которая экспортирует функции 87D4EDFB420343F2976EB3CF4DB7C224 и 87D4EDFB420343F2976EB3CF4DB7C224_done с прототипами, указанными выше, и расположенная в подпапке Plugins основной программы.

    В любом случае, давайте во-первых научим работать с этим наш менеджер плагинов, а потом напишем пустой плагин (ибо после внесения изменений в программу уже нельзя будет загрузить произвольную DLL):
    Мне кажется, тут всё очевидно. Хочу только обратить внимание, что Init-функцию мы подразумеваем обязательной, а Done функцию — опциональной (т.е. она может отсутствовать, если она не нужна плагину).

    Теперь давайте создадим плагин. Для этого сделайте File/New/Delphi Projects/Dynamic-Link Library и сохраните куда-нибудь проект (вне папки PluginAPI). Затем сделайте File/Add to project и укажите файл PluginAPI.pas. После чего напишите минимальный код:
    Мы просто вставили пустые функции инициализации и финализации и экспортировали их.

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

    Управление заголовочниками

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

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

    Тут сразу же я хотел бы рассмотреть вот какой момент. Предположим, сейчас мы напишем (слово «предположим» означает что это писать не надо):
    Мы можем использовать эти объявления и включить заголовочный файл PluginAPI.pas в SDK (вместе с его описанием в документации). Хорошо, но что будут делать программисты, работающие на других языках? Один из вариантов — перевести все .pas файлы на C++. К примеру, наш файл PluginAPI.pas, будучи переведённым на C++, станет выглядеть как-то так:
    Примечание: чёрт, я мало что понимаю в C++, так что с переводом я мог наврать. Буду благодарен, если мне ткнут носом в правильный перевод.

    Как видите, текст дословно дублирует паскалевский код, но на другом языке (C++). Т.е. это просто копия.

    Суть такого телодвижения в том, что C++ — это де-факто стандарт. Если кто-то не программирует на Delphi, то он программирует либо на C++ (и тогда он может воспользоваться указанным заголовочником), либо программирует на другом языке. В последнем случае ему придётся переводить заголовочники (с Delphi или C++ на свой язык) самостоятельно.

    В любом случае, если вы в основном работаете на Delphi и слабо знакомы с другими языками, либо же вы хотите как-то автоматизировать работу (перевод заголовочников — не самая приятная работа, к тому же тут легко ошибиться: забыть перевести внесённые изменения), то вам захочется подыскать другой способ. Хотя, повторюсь, вы можете просто писать .pas файлы и переводить их на другие языки — это отлично будет работать.

    Но сейчас я предлагаю воспользоваться ещё одним заимствованием из COM: библиотекой типов.

    Библиотеки типов

    Я кратко уже описал смысл выше: библиотека типов — это универсальное хранилище информации о сборке. Она хранится в двоичном формате (TLB) либо как отдельный файл (.tlb), либо как ресурс внутри .dll. С библиотеками типов умеют работать почти все современные компиляторы под Windows. Если кто-то умеет работать с COM, то он умеет работать и с библиотекой типов.

    Самое важное для нас тут то, что библиотека типов может быть импортирована сторонней средой разработки, и при этом среда разработки сама (и автоматически) сгенерирует все необходимые исходные файлы. К примеру, если импортировать TLB в Delphi — Delphi сама создаст .pas заголовочник. Если импортировать TLB в Visual Studio C++ — она сама создаст .hpp файлы. Аналогичное справедливо и для других сред разработки (импортировать TLB можно даже в PHP!).

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

    Давайте создадим описание наших интерфейсов в виде библиотеки типов. Я буду описывать процесс, используя Delphi XE2, но это же должно быть верно и для более старых версий Delphi, включая Delphi 7 — правда, с некоторыми оговорками. Итак, выберите File/New/ActiveX/Type Library. Появится редактор библиотеки типов:

    Редактор в Delphi 7

    Редактор в Delphi XE2

    Сохраните этот файл с именем PluginAPI в папке PluginAPI\Headers. В Delphi XE2 это будет файл PluginAPI.ridl, а в Delphi 7 — PluginAPI.tlb. .ridl-файл — это текстовый формат, .tlb — двоичный.

    Тут есть один не совсем очевидный момент: в Delphi XE2 на экране вы видите 2 кнопки сохранения: во-первых, это стандартная кнопка в панели инструментов, а, во-вторых, это кнопка сохранения в редакторе библиотеки типов (она видна на снимке экрана выше — это самая правая кнопка в панели инструментов редактора библиотеки типов). Так вот, чтобы сохранить изменения в библиотеки типов, нужно нажать на стандартную кнопку сохранения, а не на кнопку сохранения в редакторе библиотеки типов. Это ещё более неочевидно, потому что изменения в библиотеки типов не приводят к немедленному включению стандартной кнопки сохранения Save all. Чтобы она включилась, нужно переключить вкладки — уйти с редактора библиотеки типов на, скажем, главный модуль программы или страницу Welcome и вернуться обратно. После этого кнопка сохранения будет активной.

    Если же вы щёлкните на кнопке сохранения в редакторе библиотеки типов, то это будет не сохранение, а экспорт в .tlb-файл (напомню, что Delphi XE2 хранит библиотеку типов в текстовом формате в .ridl файле). В Delphi 7 это немного попроще, потому что кнопка экспорта так и называется — экспорт. И выглядит она по другому. Соответственно, в Delphi 7 эта кнопка экспортирует двоичный файл в текстовый.

    В общем, Delphi 7 и Delphi XE2 ведут себя с точностью до наоборот. Нам это, впрочем никак не мешает, поскольку одно в другое преобразовать не проблема.

    В любом случае, при создании библиотеки типов ей автоматически был присвоен уникальный GUID и установлена версия 1.0. А после сохранения её в файл она стала иметь имя «PluginAPI». На первой вкладке вам ничего больше менять не нужно. Теперь переключитесь на вкладку «Uses» и сбросьте галочку с «Borland standard VCL type library», оставив только «OLE Automation». Это отвяжет библиотеку типов от Delphi (при этом среда покажет предупреждение, что теперь с этой библиотекой типов не будут работать некоторые Delphi-вые фишки — соглашайтесь).

    Теперь создадим в библиотеке типов наши интерфейсы IPluginInfo и ICoreInfo . Для этого щёлкните по первой (красной) кнопке в панели инструментов редактора библиотеки типов, либо же щёлкните по ней правой кнопкой мыши и выберите New\Interface. Это создаст новый интерфейс с автоматически сгенерированным GUID. Установите ему имя в «IPluginInfo», версию — в 1.0, а предка — в IUnknown :

    На вкладке «Flags» сбросьте опции «Dual» и «OLE Automation». Это отключит дополнительные возможности COM, объявив интерфейс в чистом виде.

    Далее щёлкните правой кнопкой по интерфейсу в дереве и выберите New\Property — это создаст два метода-акцесора Get и Set. Поскольку свойства у нас только для чтения, то удалите метод Set, оставив только Get (Get — первый, Set — второй; ещё их можно опознать по свойству Invoke kind: Put — это Set, ну а Get — это Get). Установите имя в ID, а тип данных — в GUID. Больше ничего менять не нужно.

    Аналогично создайте свойства Name и Version , имеющие тип BSTR , а также интерфейс ICoreInfo со свойством Version, имеющим тип long :

    Тут надо сделать замечание по поводу типов данных. Типы данных указываются в «стиле C++», а не Delphi. К примеру, GUID вместо TGUID , BSTR вместо WideString , int вместо Integer , long вместо LongInt , * вместо ^ (указатель) и так далее. Список соответствия можно посмотреть в этой статье. Кроме того, поскольку интерфейсы в C++ являются просто абстрактными классами, а экземпляры классов в C++ не являются по умолчанию указателями (как в Delphi), то с именами интерфейсов необходимо явно указывать указатель, например: Когда вы закончите формировать библиотеку типов, сохраните её. Также сделайте экспорт в альтернативный формат (в Delphi 7 — в текстовый, в Delphi XE2 — в двоичный). В текстовом виде наша библиотека сейчас выглядит примерно так: Файлы PluginAPI.tlb и PluginAPI.ridl (или .idl) — это то, что необходимо распространять в составе SDK. Имея на руках эти файлы любой сможет их импортировать в свой любимый язык программирования и получить заголовочные файлы. Вам не нужно ничего переводить самому.

    Генерация заголовочников для Delphi и C++ Builder

    К примеру, если говорить про Delphi и C++ Builder, то получение нужных файлов (да, это немного бессмысленно, т.к. эти файлы у нас в любом случае есть, но чисто ради примера) удобно делать через утилиту GenTLB.exe в папке bin — вы можете указать ей .ridl файл и на выходе получить комплект из .pas, .hpp и .cpp — все необходимые заголовочники для Delphi и C++. Есть утилита tlibimp.exe, которая работает по .tlb файлу. К примеру, если у вас на руках есть .tlb файл, то получить комплект файлов вы можете такой командой: Здесь Source.tlb — это исходный файл, а D:\Output\ — папка, куда нужно поместить результаты. Опции -C, -P и -I отвечают за генерацию .hpp/.cpp, .pas и .ridl файлов соответственно, а опция -Pt+ включает красивое «схлопывание» методов-акцессоров в свойства для Delphi.

    Кроме того, если вы создадите библиотеку типов в рамках проекта (т.е. вы выберите File\New\ActiveX\Type Library в то время, когда у вас открыт проект основной программы), то необходимые .pas файлы будут сгенерированы (и будут обновляться в дальнейшем) автоматически.

    Автогенерируемые файлы получают суффикс «_TLB». Например, библиотека «PluginAPI.tlb» создаст файл «PluginAPI_TLB.pas». Сам файл при этом выглядит примерно так: Тут довольно много воды (к примеру, можно подчистить списки uses и удалить к чертям комментарии), но сам код практически дословно повторяет наш исходный вариант. Что и требовалось получить.

    Генерация заголовочников для Visual Studio C++


    Итак, это был вариант для Delphi и C++ Builder. Чуть более интересно — Visual Studio C++. Во-первых, вам понадобится сама студия. У Microsoft есть обрезанный бесплатный вариант Visual Studio Express — его будет достаточно. Взять его можно тут. Только убедитесь, что берёте именно C++, а не Phone, Basic или C#. Обратите внимание, что по умолчанию при загрузке сайт предлагает установить Trial версии Professional — убедитесь, что вы щёлкните по второму варианту (установка версии Express). Скачается небольшой web-установщик — запускайте его и ставьте. Далее ничего необычного нет.

    Я думаю, что импортировать TLB можно и в самой студии, но т.к. я в ней не силён, то мне было проще работать через утилиты командной строки. Если вы знаете другой способ генерации заголовочников по TBL в Visual Studio — ради бога, используйте его. Я же установил Platform SDK (сейчас он называется Windows SDK), который взял тут. Это тоже веб-установщик и устанавливается он как обычно. После этого в SDK нас интересуют утилиты. Во-первых, там есть утилитка OleView.exe (C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\OleView.Exe). Она может открыть .tlb-файл и показать, что там внутри — т.е. это просмотрщик (примечание: если вдруг он почему-то не работает, то скорее всего надо зарегистрировать библиотеку IViewers.Dll в той же папке). Плюс, он умеет экспортировать библиотеку типов в .idl, .h и .c. Вот этим и нужно воспользоваться. Открываете свою библиотеку типов и экспортируете её в заголовочники.

    По правде сказать, в .idl он у меня сконвертировал, а вот в .h и .c — нет. Мелькнувшее окошко консоли ругнулось на то, что он не может найти cl.exe — это препроцессор C, который идёт в комплекте с Visual Studio. Я не уверен, почему это происходит у меня, заставить его работать я так и не смог (ладно, особо упорно я и не старался). Поэтому я пошёл другим путём: я создал .bat файл с таким содержанием: (где IDL файл — это текстовое представление библиотеки типов; я не пробовал с файлами, генерируемыми Delphi; этот IDL файл — тот, что мне выдал OLEView).

    MIDL компилятор по IDL файлу сгенерировал мне PluginAPI.hpp и PluginAPI_i.c — это и есть нужные нам файлы.

    Выводы по ведению заголовочных файлов

    Итак, подводя черту, у вас есть три способа ведения заголовочников:

    1. Просто писать .pas файл и при необходимости вручную перевести его на другие языки.
    2. Создать библиотеку типов и редактировать её в редакторе Delphi (вообще говоря, для этого можно использовать любой редактор TLB, а не только Delphi). В конце автоматически получить .tlb и комплект файлов для Delphi, C++ Builder, Visual Studio C++, а также текстовое описание (idl/ridl).
    3. Создать idl или ridl файл. Писать текст — это быстрее и удобнее, чем использовать редактор, но нужно знать язык описания интерфейсов (IDL). В конце скомпилировать файл в .tlb и получить комплект сопроводительных файлов.

    Как делать — на ваш выбор. В принципе, начать можно с п1, а затем перейти к п2 или п3, но надо понимать, что при этом вы будете дублировать свою работу (сначала вы пишете .pas, а потом ровно это же воспроизводите в библиотеке типов). Так что изначально начав работу с библиотекой типов вы избежите повторения. Тем не менее, именно по этой причине (поскольку одно тождественно второму), здесь и далее я буду говорить только про Паскаль и приводить код только Delphi модулей — подразумевая, что для других языков вы либо сделаете перевод, либо составите .tlb-файл. Но я больше не буду заострять на этом внимание. Итак, всюду ниже вы делаете как написано, но в итоге собираете файлы для других языков. Надеюсь, это понятно.

    Давайте я ещё просуммирую действия, которые вам необходимо делать при работе с TLB:

    • Помещайте .tlb файлы в PluginsAPI\Headers. .tlb файл — это главный и основной файл. Его нужно распространять обязательно. Все остальные пункты ниже — необязательные. Их можно не делать. Но если вы их сделаете, то это будет дополнительное удобство.
    • В папку PluginsAPI\Headers положите файлы .idl или .ridl.
    • Получите комплект заголовочников для Delphi, C++ Builder и Visual Studio C++, как указано выше (tlibimp для Delphi и C++ Builder и oleview/midl для Visual Studio).
    • В папке PluginAPI\Headers создайте подпапки Delphi, Builder и VC.
    • В папку Delphi положите XYZ_TLB.pas
    • В папку Builder положите XYZ_TLB.h и XYZ_TLB.cpp
    • В папку VC положите XYZ.hpp и XYZ_i.c

    Папка PluginAPI\Headers целиком (с подпапками) — это то, что вам нужно распространять среди разработчиков ваших плагинов.

    Далее мы к этому возвращаться не будем.

    Что касается файла PluginAPI.pas с двумя типами данных и двумя константами — вы можете описать его словами в документации, плюс приложить вот это определение на C: Этого будет достаточно, чтобы разработчики плагинов поняли бы, о чём идёт речь, и что нужно делать. Конечно, разработчикам на других языках (не C++ и не Delphi) придётся перевести эти строки на свой язык самостоятельно, но перевод пары строк — это полный пустяк. Основную массу заголовочников транслировать не нужно — для этого есть импорт библиотеки типов.

    Далее, в документации нужно особо уточнить, что ваши плагины и ядро — это не COM-объекты. Просто самое типичное использование библиотеки типов — это COM. Т.е. если кто-то видит библиотеку типов, он может автоматически потянуться её импортировать и вызывать CoCreateInstance для создания объектов. Только это не будет работать в вашем случае. Потому что у вас нет никаких COM-объектов, и уж тем более их никто глобально не регистрировал, чтобы их можно было получить через CoCreateInstance . Поэтому нужно чётко указать: это не COM, библиотека типов служит лишь для авто-генерации заголовочных файлов для вашего любимого языка программирования, а чтобы сделать плагин — нужно создать DLL с двумя вот такими (см. C код выше) функциями. Первая из которых работает с интерфейсами. Какими интерфейсами? А вот они как раз описаны в библиотеке типов.

    Начала реализации функциональности

    Итак, вернёмся же к нашим баранам. Я напомню, что мы решили дать возможность плагину и ядру узнать информацию друг о друге и для этого мы ввели такие интерфейсы: Я написал их от балды — вы можете использовать любую другую структуру, даже один интерфейс вместо двух (скажем, IVersionInfo ). Тут ничего жёстко не определено, работает ваша фантазия — как захотите, так и сделаете. Я бы только рекомендовал предусмотреть идентификацию плагинов по уникальному ID. В качестве такого хорошо подходит GUID. А вот имя файла — не достаточно (плагин можно переименовать).

    В любом случае, теперь их надо использовать в коде. Начнём с плагина. Возьмём пустой плагин из предыдущего примера и дополним его новым кодом следующим образом: Итак, плагин реализует интерфейс IPluginInfo — для этого нам понадобился объект. Реализация методов в данном случае тривиальна — мы просто возвращаем константы. Конструктор объекта получает из переданного нам интерфейса интерфейс ICoreInfo и сохраняет его в поле FCore . Это не нужно в этом примере (нигде ниже FCore не используется), но может быть полезно в общем случае. После получения интерфейса, мы проверяем, что версия ядра — 1 или выше. Конечно, это всегда будет так. Глупая проверка :)

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

    Теперь, что касается ядра. Изменения в основной программе (показываем имя плагина и его версию вместо имени файла): Теперь пояснения по коду. Здесь я практически продублировал интерфейс IPluginInfo из заголовочника в IPlugin . Плюсы: вы посмотрите, как красиво выглядит код в основной программе! Можно подумать мы с компонентами работаем, а не с плагинами. Минусы: нам пришлось писать кучу кода-обёртки, который мне сейчас придётся объяснять.

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

    Сам код для информации о плагине состоит из трёх однотипных методов ( GetID , GetName , GetVersion ). По сути, здесь показан пример получения информации по запросу: мы не вызываем интерфейс IPluginInfo до тех пор, пока кто-то не обратится к одному из свойств плагина ( ID , Name , Version ). Это можно было бы делать и по другому: запросить информацию в конструкторе TPlugin . Кроме того, этот код показывает пример кэширования информации: когда мы получили информацию, мы сохранили её в полях объекта (кэше). Если она потребуется нам повторно — нам не придётся дёргать плагин снова, чтобы её получить. Этот подход отлично подходит для статической информации (которая не меняется), но мало пригоден для динамической.

    Что касается интерфейса ICoreInfo , то его реализует менеджер плагинов. Тут есть такой вопрос: вообще-то менеджер плагинов не в курсе, что за версия у ядра. Это знает ядро, а не менеджер плагинов. Поэтому мы по умолчанию ставим версию 1. А ядру даёт метод SetVersion , с помощью которого оно сможет установить (при необходимости) номер версии 2, 3 и так далее. Обратите внимание, что хотя методы SetVersion и Get_Version и выглядят парой (и, по сути, это методы-акцессоры Get и Set для свойства «Версия»), они имеют существенное отличие: метод Get предназначен для плагинов, а метод Set — для ядра (это видно по соглашению вызова: safecall и register ). Метод SetVersion плагин вызвать не может!

    В связи с тем, что у нас появился интерфейс ядра, который мы передаём плагину, у нас возникла небольшая проблема: как вы помните из сказанного выше, плагины не выгружаются программой явно, они выгружаются при удалении менеджера плагинов. А наш менеджер плагинов удаляется тогда, когда все ссылки на него будут уничтожены. Последняя из которых (ссылка) обнуляется в finalization модуля менеджера. А проблема тут вот в чём: мы ведь передали интерфейс менеджера плагинам. Это означает, что обнуление ссылки в секции finalization не будет последним: ведь ссылку на менеджер держат плагины, которые. не выгружаются потому, что менеджер ещё жив. Вот и получается круговая ссылка: менеджер ссылается на плагины, а плагины — на менеджер. И никто из них выгружаться не хочет, потому что есть активные ссылки. Вот поэтому нам нужно внести изменение: в секции finalization нужно явно скомандовать менеджеру плагинов выгрузить все плагины. Сделать это можно просто финализировав массив FItems — всё остальное за нас сделает механизм учёта ссылок. Финализация массива уничтожит каждый его элемент, каждый элемент — это плагин. Т.е. каждый плагин будет выгружен. Выгрузка плагина отпустит ссылку на менеджер плагинов, после чего обнуление ссылки на менеджер плагинов в секции finalization уничтожит его — и все будут довольны. Код я тут выписывать не буду — там всего несколько строк.

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

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

    Описание среды Delphi

    Ориентация среды разработки программ Delphi на работу в операционных системах семейства Windows. Типы окон, используемых Windows. Интерфейс программы, компоненты, используемые в программе. Использование кривых Безье и координатного указателя курсора.

    Рубрика Программирование, компьютеры и кибернетика
    Вид реферат
    Язык русский
    Дата добавления 25.12.2014
    Размер файла 253,5 K

    Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

    Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

    Размещено на http://www.allbest.ru/

    3.2 Описание среды программирования

    3.2.1 Среда разработчика Delphi

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

    Среди пользователей персональных компьютеров в настоящее время наиболее популярно семейство операционных систем Windows и, естественно, что тот, кто собирается программировать, стремится писать программы, которые будут работать в этих системах. Бурное развитие вычислительной техники, потребность в эффективных средствах разработки программного обеспечения привели к появлению систем программирования, ориентированных на так называемую «быструю разработку», среди которых можно выделить Borland Delphi и Microsoft Visual Basic. В основе систем быстрой разработки лежит технология визуального проектирования и событийного программирования, суть которой заключается в том, что среда разработки берет на себя большую часть генерации кода программы, оставляя программисту работу по конструированию диалоговых окон и функций обработки событий.

    Delphi- это среда разработки программ, ориентированных на работу в операционных системах семейства Windows. Программы в Delphi создаются на основе современной технологии визуального проектирования, которая, в свою очередь, базируется на идеях объектно-ориентированного программирования. Программы в Delphi пишутся на языке Object Pascal, который является преемником и развитием языка Turbo Pascal. Язык программирования Turbo Pascal, а также одноименная интегрированная среда разработки, в которой он использовался, в недавнем прошлом завоевал широкую популярность как средство разработки программных продуктов и особенно как средство обучения программированию. Эта популярность была обусловлена простотой языка, высококачественным компилятором и удобной средой разработки.

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

    3.2.2 Интегрированная среда разработки Delphi

    Интегрированная Среда Разработки (Integrated Development Environment — IDE, в дальнейшем мы будем использовать для нее аббревиатуру ИСР) — это среда, в которой есть все необходимое для проектирования, запуска и тестирования приложений и где все нацелено на облегчение процесса создания программ. ИСР интегрирует в себе редактор кодов, отладчик, инструментальные панели, редактор изображений, инструментарий баз данных — все с чем приходится работать.

    Запустите Delphi с помощью меню Windows Пуск | Программы. Когда вы щелкните на пиктограмме Delphi, перед вами откроется основное окно Интегрированной Среды Разработки (см. рис. 1).В основных чертах окна ИСР всех версий Delphi одинаковы.

    Рис 1 Основное окно Интегрированной Среды Разработки в Delphi6

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

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

    File(Файл) — содержит набор команд для работы с файлами, позволяет добавлять их в проект, создавать новые файлы с помощью шаблонов, удалять, переименовывать и распечатывать. Кроме того, в этом разделе находятся команды для создания новых форм и приложений и команда выхода. В Delphi 6 сюда включена команда для создания нового элемента Frame (фрейм).

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

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

    View(Вид) — под этим названием объединены команды для вызова наиболее часто используемых инструментов управления проектом, таких как Project Manager (Менеджер проекта), Translation Manager (Менеджер языка DLL), Object Inspector (Инспектор объектов), ToDoList (Список задач), Alignment Palette (Окно выравнивания компонентов), Browser (Исследователь), Code Explorer (Проводник по программе), Component List (Список компонентов), Window List (Список окно), Type Library (Библиотека типов), Debug Windows (Окно отладчика). В число последних входят Breakpoints (Список точек остановки), Call Stack (Окно стека), Watches (Окно контроля переменных), Local Variables (Окно лакальных переменных), Threads (Окна статуса нитей), Modules (Окно исполняемых модулей), CPU (Окно контроля переменных), FPU (Окно операций над числами с плавающей запятой), Event Log (Окно событий). Кроме того, здесь же находятся раздел меню Toggle Form/Unit (Переключатель формы / модуля) и опции Forms (Окно формы), Units (Окно модулей), New Edit Window (Новое окно редактирования), которые, как ясно уже из названия, позволяют выводить на экран окна форм, модулей и создавать новое окно редактирования, а также производить настройку панели инструментов. В дополнение к ним в Delphi5 появилась возможность сохранять текущие настройки среды для последующего использования с помощью группы команд из раздела Desktops.

    Project(Проект) — предназначен для того, чтобы добавлять проект в Repository (Архив объектов), загружать окно редактирования текста проекта, добавлять проекты в группу, компилировать как отдельный проект, так и группу в целом, проверять корректность кода и в том, и в другом случае, получать информацию об итогах компиляции проекта, задавать свойства web-приложений и экспортировать их на Web-сервер, а также вызывать элемент Options (Окно свойств проекта). В дополнение к этим функциям в Delphi5 появилась возможность с помощью группы команд из раздела Languages создавать специальную DLL с файлом ресурсов, содержащим поддержку национального языка.

    Run(Выполнить)-позволяет запускать разработанное приложение, передавать ему строку параметров, производить отладку, задавать точки остановки, осуществляет пошаговое выполнение, просматривать значения переменных и изменять их. При разработке компонентов ActiveX с помощью команд меню можно зарегистрировать ActiveX-сервер или удалить запись о нем из системного реестра. Данное меню содержит также опцию, которая необходима, чтобы установить MTS Object на Microsoft Transaction Server (MTS) для его дальнейшего использования под управлением этого сервера. В Delphi 5 появился раздел Attach to Process, который позволяет производить отладку процессов, запущенных не только на локальном, но и на удаленном компьютере.

    Component(Компоненты) — здесь сосредоточены команды, предназначенные для установки компонентов, импорта ActiveX-компонентов, создания новых компонентов и шаблонов компонентов, редактирования пакетов, а также настройки палитры компонентов.

    Database(Базы Данных) — содержат команды вызова утилит для работы с базами данных, таких как SQL Explorer (Исследователь баз данных), SQL Monitor (SQL монитор) и Form Wizard (Мастер создания форм).

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

    Help(Помощь) — объединяет команды вызова справочной системы Delphi и ее настройки, а также позволяет обратиться к Web-серверу для получения дополнительной информации.

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

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

    Для того чтобы настроить панель инструментов, выберите раздел View => Toolbars главного меню или воспользуйтесь всплывающим меню панели инструментов. После этого укажите, какие из групп «быстрых» кнопок следует отображать на панелью Доступны Standard (Стандартная), View (Просмотр), Debug (Отладчик), Custom (Пользовательская), Component Palette (Палитра компонентов). Вы можете выбрать те из них, которые необходимы для работы, и кроме того, определить, какие «быстрые» кнопки будут входить в каждую из групп.

    Настройка панели инструментов производится следующим образом. Сначала выберите раздел всплывающего меню Customize (Пользовательские настройки) и в появившемся окне (см. рис. 2) откройте страницу Commands (Команды). Далее с помощью мыши выберите в правом окне пиктограмму нужной вам опции и перетащите ее на панель инструментов. Для удаления ненужных кнопок достаточно перетащить их с панели инструментов в окно Commands.

    В дополнение к этому с помощью страницы Options (Опции) данного окна можно установить, показывать или нет подсказки (строка Show tooltips (Показывать названия инструментов)) при перемещении курсора над кнопками панели инструментов и включать или нет в подсказку комбинацию » быстрых» клавиш (строка Show shortcut keys on tooltips (Показывать » быстрые» клавиши инструментов)) для вызова команды, запускаемой щелчком по кнопке.

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

    Component palette (Палитра компонентов) — это один из наиболее часто используемых инструментов Delphi. Оно состоит из большего числа страниц, на которых располагаются компоненты (см.рис.3). В процессе работы пользователь может создавать новые страницы и удалять существующие, добавлять и убирать компоненты на любой из страниц, изменять их порядок.

    рис. 3 Палитра компонентов

    Standard — стандартные компоненты управления Delphi. расширяющие возможности предыдущего набора;

    Additional -дополнительные компоненты управления Delphi, расширяющие возможности предыдущего набора;

    Win32 — компоненты, инкапсулирующие в себе набор 32-разрядных интерфейсов windows (9x/NT);

    System — специфические системные не визуальные компоненты Delphi;

    Data Access — компоненты для доступа к базам данных;

    Data Controls — компоненты для отображения информации из баз данных;

    ADO — компоненты, позволяющие подключатся к базам данных с использованием ActiveX Data Objects (ADO);

    InterBase — компоненты, предназначенные для подключения к базам данных InterBase без использования Borland Database Engine (BDE) или ActiveX Date Objects (ADO);

    MIDAS — компоненты для многозвенной технологии доступа к базам данных;

    InternetExpress — компоненты, позволяющие создавать приложения, которые могут работать как с Web Server application (Internet-приложениями), так и с multi-tiered database (многозвенными приложениями);

    Internet — компоненты для работы в Internet;

    FastNet components — компоненты, благодаря которым приложения могут использовать различные Internet — протоколы;

    Decision Cube — компоненты для многомерного анализа информации баз данных (только в поставке Delphi Client/Server);

    QReport — компоненты для визуального проектирования печатных отчетов;

    Dialogs — компоненты, инкапсулирующие в себе стандартные диалоговые окна Windows;

    Win 3.1 — компоненты управления Windows 3.1 (для обратной совместимости приложений);

    Samples — компоненты, которые используется как примеры в документации (их тексты находится в папке /DELPHI/SOURCE/SAMPLES/);

    ActiveX — компоненты ActiveX, разработанные сторонними фирмами;

    Servers page components — компоненты, которые представляют собой «обертку» VCL для популярных COM-серверов.

    Палитра компонентов может отображаться или не отображаться на панели инструментов Delphi. Управление этой опцией осуществляется с помощью раздела меню View => Component Palette. Как и в предыдущих версиях, конфигурирование палитры осуществляется без перекомпиляции VCL.

    В основном поле окна вы можете видеть слева окно Инспектор Объектов (Object Inspector), с помощью которого вы в дальнейшем будете задавать свойства компонентов и обработчики событий. Эта страница состоит из 2-х колонок: левая колонка содержит название свойства, а правая — конкретное значение свойства (см.рис.4)

    рис. 4 Инспектор Объектов

    Окно Инспектора Объектов отображает информацию для того компонента, который выделен щелчком мыши. Строки страницы этого окна выбираются щелчком мыши и могут отображать простые или сложные свойства. К простым относятся свойства, которые определяются одним значением — числом, строкой символов, значением False или True и т.д. Сложные свойства определяются совокупностью значений. Слева от имени таких свойств указывается символ «+». Двойной щелчок по имени такого свойства приводит к раскрытию списка значений сложного свойства. Закрывается раскрытый список также двойным щелчком мыши по имени сложного свойства. Интересным новшеством в Delphi 6 является добавление в Инспекторе Объектов так называемых расширенных встроенных компонентных ссылок (expanded inline component references) или, короче, встроенных компонентов. Под этим термином подразумеваются некоторые свойства компонентов, значениями которых являются имена других компонентов (т.е. ссылки на другие компоненты).

    Например, у многих компонентов имеется свойство Popup Menu, содержащее имя компонента, являющегося контекстным меню. Свойства, которые содержат ссылку на встроенный компонент, отображаются в Инспекторе Объектов по умолчанию красным цветом. Когда такому свойству присваивают значение, около него появляется символ «+». Если выполнить двойной щелчок по свойству, содержащему имя встроенного компонента либо просто щелкнуть по символу «+», то раскроется список свойств встроенного компонента.

    Свойства встроенного компонента по умолчанию отображаются зеленым цветом.

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

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

    3.2.3 Структура проекта Delphi

    Программа Delphi — это несколько связанных между собой файлов. Так, любая программа всегда состоит из уже знакомого нам файла проекта (такой файл имеет расширение.dpr) и одного или нескольких модулей (файлы с расширением.pas). Файл проекта не предназначен для редактирования пользователем и создается автоматически самой системой программирования Delphi. Для того чтобы увидеть содержимое файла проекта, необходимо выполнить команду Project | View Source. Содержимое файла проекта может быть, например, следующим:

    Файл проекта (главный модуль) начинается словом program, за которым следует имя программы, совпадающее с именем проекта. Имя проекта задается программистом в момент сохранения файла проекта, и оно определяет имя создаваемого средой Delphi исполняемого файла (файла с расширением.ехе). Далее за словом uses следуют имена используемых модулей: стандартного модуля Forms и модуля формы Unitl. Похожая на комментарий директива <$R *.res>указывает компилятору, что нужно использовать файл ресурсов, который содержит описание* ресурсов приложения, например, пиктограммы. Звездочка указывает, что имя файла ресурсов такое же, как и у файла проекта, но с расширением.res.

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

    Помимо главного модуля каждая программа включает как минимум один модуль формы, который содержит описание стартовой формы приложения и поддерживающих ее работу процедур. В Delphi каждой форме соответствует свой модуль. Для переключения между формой и окном редактора кода, содержащего соответствующий модуль, следует выполнить команду главного меню View | Toggle Form/Unit, либо нажать функциональную клавишу F12.

    Модули — это программные единицы, служащие для размещения фрагментов программ. При помощи содержащихся в них текстов программ (программных кодов) и реализуется решаемая пользователем задача. Модули имеют стандартную конструкцию (последовательность и перечень разделов), предусмотренную языком программирования Object Pascal. Приведем структуру модуля в общем виде:

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

    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;

    Начинается модуль словом unit, за которым следует имя модуля. Именно это имя упоминается в списке используемых модулей в операторе uses главного модуля приложения.

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

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

    Раздел реализации начинается словом implementation и содержит объявления локальных переменных, процедур и функций, поддерживающих работу формы. В начале раздела реализации располагается директива <$R *.dfin>, указывающая компилятору, что в-раздел реализации надо вставить команды установки значений свойств формы, которые находятся в файле с расширением ‘.dfm, имя которого совпадает с именем модуля. Файл в формате dfm генерируется Delphi на основе внешнего вида формы.

    За директивой <$R *.dfm>располагаются описания процедур обработки событий формы. Сюда же программист может поместить описание своих процедур и функций, которые могут вызываться из процедур обработки событий.

    Инициирующая и завершающая части являются необязательными. Инициирующая часть начинается словом initialization либо заключается в оператор-1ые скобки begin. end. Операторы из этой части выполняются до передачи управления основной программе и обычно используются для подготовки ее работы.

    Завершающая часть начинается словом finalization и содержит операторы, выполняющиеся в момент окончания программы.

    В приведенном выше примере модуля инициирующая и завершающая части отсутствуют.

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

    При компиляции программы Delphi создает файлы с расширениями.dcu для каждого модуля.

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

    3.2.4 Библиотека визуальных компонентов

    Классы созданные разработчиками Delphi, образуют сложную иерархическую структуру, называемую Библиотекой визуальных компонентов (Visual Component Library -VCL). Количество входящих в VCL классов составляет несколько сотен. На рисунке 5 изображены базовые классы, являющиеся родоначальниками всех остальных классов.

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

    Характерным примером класса, определенного в VCL, но не являющегося компонентом, является класс TFont. Мы не можем непосредственно поместить на форму объект класса TFont. С другой стороны, при работе, например, с такими компонентами, как Label или Edit мы будем использовать свойство Font классового типа TFont.

    Размещено на http://www.allbest.ru/

    Заметим также, что не все компоненты-потомки класса TComponent являются визуальными. Например, компонент Timer, предназначенный для отсчета интервалов реального времени, является невизуальным.

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

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

    Описание среды функционирования. Плюсы и минусы Windows 98

    Как и любая программа, Windows 98 имеет свои сильные и слабые стороны; начнем с сильных.

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

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

    В-третьих, поддержка самых разнообразных устройств и механизм Plug and Play.Большинство новых устройств Windows 98 находит сама, автоматически устанавливая для них нужные драйверы, причем этот механизм улучшен по сравнению с предыдущей версией.

    В-четвертых, практически полная совместимость сверху вниз. Все программы (за редким исключением), написанные для версий Windows и MS-DOS, прекрасно работают и под управлением Windows 98.

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

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

    В-седьмых, встроенные средства работы и интеграции с Internet.

    В-восьмых, более эффективная работа на компьютерах с процессорами Pentium и старше.

    В-девятых, долгожданные средства цветовой калибровки на уровне операционной системы и поддержка до 7 мониторов одновременно.

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

    Как известно, за все надо платить, и за все удобства Windows 98 приходится платить очень большой нагрузкой на аппаратную часть компьютера. Windows 98 (как и ее предшественница Windows 95) весьма требовательно к быстродействию процессора, объему оперативной памяти и свободному месту на диске (причем по сравнению с предыдущей версией эти требования несколько возросли). Таким образом, самый главный недостаток- высокие требования к быстродействию всех без исключения частей компьютера. Если использовать Windows 98 на медленных компьютерах, то из-за постоянных простоев и ожидания работа из приятной превращается в настоящее мучение.

    Еще одним минусом Windows 98 является ее неполная многозадачность, особенно для старых программ. И если с распределением ресурсов Windows 98 справляется достаточно успешно, то с защитой дело обстоит все еще не очень хорошо, — если некорректная работа одной из программ приведет к разрушению общих системных ресурсов, то Windows 98, скорее всего, перестанет нормально работать.

    Самый главный недостаток Windows 98, связанный с критичностью по времени, не позволяет использовать эту оболочку для обработки поступающих извне сигналов в реальном масштабе времени. В данном случае Windows 98 просто «захлебывается».

    Хотя Windows 98 и ориентируется на постоянных пользователей сети Internet, степень ее защиты от несанкционированного доступа (взлома) все же недостаточна.

    Наконец, как это ни печально, Windows 98 не лишена ошибок, как, впрочем, и другие программы.

    Элементы интерфейса Windows 98

    После запуска Windows 98 вы попадаете на рабочий стол (Desktop). Здесь обычно располагаются некоторые элементы интерфейса Windows 98:

    Мой компьютер(My Computer) — содержит дерево, условно представляющее все папки компьютера;

    Корзина (Recycle Bin) позволяет удалять файлы и папки. Если вы перетащите какой-либо файл (папку) на пиктограмму Корзины (Recycle Bin) (пиктограмма при этом окрасится в синий цвет), то вы удалите его с диска;

    Сетевое окружение (Network Neighborhood) — позволяет просмотреть сетевые ресурсы, если вы подключены к сети;

    Портфель(My Briefcase) — позволяет синхронизировать файлы, обрабатывающиеся сразу на нескольких компьютерах. Пользоваться им или нет — дело ваше;

    Internet Explorer — позволяет путешествовать по Internet. Если вы не клиент Internet, данную пиктограмму также можно удалить;

    Outlook Express это своего рода почтовый ящик, куда будет поступать информация, приходящая по электронной почте. Если вы таковой не имеете, то смело помещайте Outlook Express в Корзину (Recycle Bin);

    Мой документы (My Document) — папка для личных документов, в ней вам предлагается сохранять результаты своей работы (если ее расположение на дисках по умолчанию вас не устраивает, можете переместить ее в любое место).

    Панель задач (Taskbar) обычно расположена в самом низу экрана. На ней располагаются кнопки всех запущенных в данный момент программ и открытых папок. Чтобы перейти к любой из запущенных задач, достаточно щелкнуть мышью по соответствующей кнопке на панели задач. Если вам неясно, какая задача соответствует кнопке, — задержите на ней на пару секунд мышь.

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

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

    Кнопка Пуск и Главное меню

    Слева на панели задач располагается кнопка Пуск (Start). Щелчок по этой кнопке вызывает главное меню, которое позволяет запустить любую программу, вызвать справку, найти файл и т.п.

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

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

    окно Windows — программы;

    окно DOS — программы, запущенной в окне;

    вторичное окно Windows — программы (создается непосредственно самой программой).

    Окно с сообщением или запросом (создается одной из работающих в данный момент Windows- программ).

    Окно раскрытой папки.

    Окно Windows — программы. Как правило, содержит все элементы, описанные выше. Заголовок содержит имя программы.

    Окно DOS — программы. Из — под Windows можно запустить и программы, предназначенные для работы под управлением DOS. Такая программа может быть запущена в двух режимах — полноэкранном и в окне. Первый режим практически ничем не отличается от выполнения программы под управлением непосредственно DOS. Режим же выполнения в окне позволяет более гибко управлять работой программы. У окна DOS — программы отсутствует строка меню. Все остальные элементы присутствуют. В заголовке располагается имя DOS — программы.

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

    Окно сообщений и запросов также генерируется Windows — программами. В них выводится различные предупреждения, сообщения о возникших ситуациях, запросы на различные действия. Как правило, эти окна выводится поверх всех других окон и содержат только заголовок и кнопку Закрыть (Close). Отличительной особенностью этих окон является их неизменный размер (т.е. изменить границы окна невозможно).

    Активный и неактивные окна

    На экране может быть только одно активное окно, все остальные окна, находящиеся в этот момент на экране, неактивны. Активное окно всегда выводится поверх других и все действия, совершаемые в данный момент, относятся именно к нему. Заголовок активного окна выделяется ярким цветом, у неактивных окон заголовки блеклого цвета. Если окно данной программы неактивно, то говорят, что программа выполняется в фоновом режиме (если данный режим для нее имеет смысл).

    Управление окнами

    Чтобы изменить размер окна, маркер мыши необходимо установить на границу окна. Если данное окно допускает изменение размера, то маркер примет вид, представленный на рис 3.16. Стрелки будут показывать возможное направление. Если вы действительно хотите изменить границу окна, то необходимо просто переместить ее на новое место (нажать на левую кнопку мыши и, не отпуская ее, передвинуть маркер на нужное место). Если в окне отсутствовали линейки прокрутки, а вы не уменьшили его размер, то могут появиться одна или обе эти линейки.

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

    Начнем со случая, когда окно занимает часть экрана. Слева расположена кнопка Свернуть (Minimize): она позволяет свернуть окно (если это окно программы или папки, то после свертывания от окна останется кнопка на панели задач; если же это вторичное окно, то от него останется строка заголовка и оно будет помещено в левый нижний угол окна программы). В середине находится кнопка Развернуть (Maximize), щелчок по ней позволяет развернуть окно на полный экран (если это вторичное окно, то оно сольется с окном программы). Справа расположена кнопка Закрыть (Close), она позволяет закрыть окно.

    Если окно развернуть на весь экран. то посередине вместо кнопки Развернуть (Maximize) находится кнопка Восстановить (Restore); она позволяет восстановить размер окна.

    Если вторичное окно свернуто, то слева располагается кнопка Восстановить (Restore), а в центре кнопка Развернуть (Maximize).

    Если вторичное окно слито с окном программы, то вверху расположены кнопки изменения размера окна программы, а сразу под ними кнопки изменения размера вторичного окна.

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

    Прокрутка содержимого окна

    Если содержимое окна не « влезает» в текущие размеры окна, то слева и внизу появляются линейки прокрутки (или одна из них). Рассмотрим, как ими пользоваться. Самый простой вариант — это перетащить на новое место бегунок. При этом, соответственно, изменится и содержимое окна.

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

    Если же необходимо переместить окно на один экран вверх или вниз (вправо или влево), то вы можете поступить следующим образом. Установите маркер мыши на линейку прокрутки выше или ниже бегунка и щелкните левой кнопкой мыши. При этом произойдет скроллинг на один экран.

    Чтобы закрыть окно (если это возможно), нужно щелкнуть по кнопке Закрыть (Close). Если данная операция допустима (окна программ, папок, вторичные окна, часть окон запроса), то после щелчка окно исчезнет с экрана.

    — если это было окно программы, то данная программа будет завершена;

    — если это было окно папки, то оно исчезнет с экрана, а папка будет закрыта;

    — если это было вторичное окно программы, то обработка данного документа будет прекращена;

    — если это было сообщение программы, то данное окно исчезнет с экрана;

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

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

    При щелчке по кнопке Закрыть (Close) DOS — программы, запущенной в окне, делается попытка завершить данную программу. Если сделать это корректно не удается, выводится соответствующее предупреждение. В этом случае рекомендуется отменить закрытие окна с потерей данных (щелчок по кнопке Нет (No)), а затем завершить DOS — программу стандартным для нее способом.

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

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

    Основное с точки зрения внешнего вида свойство кнопки — Caption.

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

    Например, если в вашем приложении имеется кнопка выполнения какой-то операции, вы можете задать ее свойство Caption равным «&Выполнить». На кнопке эта надпись будет иметь вид «Выполнить». И если пользователь нажмет клавиши Alt-B, то это будет эквивалентно щелчку на кнопке.

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

    Свойство Cancel, если его установить в true, определяет, что нажатие пользователем клавиши Esc будет эквивалентно нажатию на данную кнопку. Это свойство целесообразно задавать равным true для кнопок «Отменить» в различных диалоговых окнах, чтобы можно было выйти из диалога, нажав на эту кнопку или нажав клавишу Esc.

    Свойство Default, если его установить в true, определяет, что нажатие пользователем клавиши ввода Enter будет эквивалентно нажатию на данную кнопку, даже если данная кнопка в этот момент не находится в фокусе. Правда, если в момент нажатия Enter в фокусе находится другая кнопка, то все-таки сработает именно кнопка в фокусе. Если у нескольких кнопок на форме свойство Default задано равным true, то при нажатии Enter сработает та из них, которая находится раньше в последовательности табуляции.

    Еще одно свойство — ModalResult используется в модальных формах. В обычных приложениях значение этого свойства должно быть равно mrNone.

    Из методов, присущих кнопкам, имеет смысл отметить один — Click. Выполнение этого метода эквивалентно щелчку на кнопке, т.е. вызывает событие кнопки OnClick. Этим можно воспользоваться, чтобы продублировать какими-то другими действиями пользователя щелчок на кнопке. Пусть, например, вы хотите, чтобы при нажатии пользователем клавиши с символом «С» или «с» в любой момент работы с приложением выполнялись операции, предусмотренные в обработчике события OnClick кнопки Buttonl. Поскольку неизвестно, какой компонент будет находиться в фокусе в момент этого события, надо перехватить его на уровне формы. Такой перехват осуществляется, если установить свойство формы KeyPreview в true. Тогда в обработчике события формы OnKeyPresss можно написать оператор

    if (key=’Y’ or key=’Z’) then Buttonl.Click;

    Если пользователь ввел символ «С» или «с», то в результате будет выполнен обработчик щелчка кнопки Buttonl.

    Все сказанное выше в равной степени относится и к Button, и к BitBtn. Рассмотрим теперь особенности кнопки с пиктограммой BitBtn. Изображение на этой кнопке задается свойством Glyph. При нажатии кнопки с многоточием в строке свойства Glyph в Инспекторе Объектов вызывается окно. Нажав в нем кнопку Load вы перейдете в обычное окно открытия файла рисунка и можете выбрать файл битовой матрицы.bmр, содержащий желаемое изображение. В частности, с Delphi поставляется большое количество изображений для кнопок. Они расположены в каталоге \lmages\Buttons, а сам каталог Images в Delphi 5 и 4 расположен в каталоге \program files\common files\borland shared, а в других версиях Delphi — в каталоге \program files\borland\delphi.

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

    Файл изображения для кнопки может содержать до четырех изображений пиктограмм размера 16×16. Самое левое соответствует отжатой кнопке. Второе слева соответствует недоступной кнопке, когда ее свойство Enabled равно false. Третье слева изображение используется при нажатии пользователя на кнопку при ее включении. Четвертое слева изображение используется в кнопках с фиксацией SpeedButton, о которых будет сказано позднее, для изображения кнопки в нажатом состоянии. Большинство изображений для кнопок использует две пиктограммы. Число пиктограмм вы можете узнать из свойства кнопки NumGlyphs, которое после загрузки изображения покажет вам число пиктограмм в нем.

    Расположение изображения и надписи на кнопке определяется свойствами Margin, Layout и Spacing. Если свойство Margin равно -1 (значение по умолчанию), то изображение и надпись размещаются в центре кнопки. При этом положение изображения по отношению к надписи определяется свойством Layout, которое может принимать значения: blGlyphLeft (слева, это значение принято по умолчанию), blGlyphRight (справа), blGlyphTop (вверху), blGlyphBottom (внизу). Если же Margin > 0, то в зависимости от значения Layout изображение и надпись смещаются к той или иной кромке кнопки, отступая от нее на число пикселей, заданное значением Margin.


    Свойство Spacing задает число пикселей, разделяющих изображение и надпись на поверхности кнопки. По умолчанию Spacing = 4. Если задать Spacing = О, изображение и надпись будут размещены вплотную друг к другу. Если задать Spacing = -1, то текст появится посередине между изображением и краем кнопки.

    Еще одно свойство BitBtn — свойство Kind определяет тип кнопки. По умолчанию значение этого свойства равно bkCustom — заказная. Но можно установить и множество других предопределенных типов: bkOK, bkCancel, bkHelp, bkYes, bkNo, bkClose, bkAbort, bkRetry, bklgnore, bkAll. В этих типах уже сделаны соответствующие надписи, введены пиктограммы, заданы еще некоторые свойства. Обычно все-таки лучше ими не пользоваться. Во-первых, надписи все равно надо переводить на русский язык. Во-вторых, предопределенные рисунки обычно выбиваются из общего стиля конкретного приложения. И главное — предопределение некоторых свойств, не учтенных вами, может иногда приводить к странным результатам работы. Уж лучше использовать заказные кнопки и самому устанавливать в них все необходимые свойства.

    Компонент Image и некоторые его свойства

    Нередко возникает потребность украсить свое приложение каким — то изображениями. Это может быть графическая заставка, являющаяся логотипом вашего приложения. Или это могут быть фотографии сотрудников некоего учреждения при разработке приложения, работающего с базой данных этого учреждения. В первом случае вам потребуется компонент Image, расположенный на странице Additional библиотеки компонентов, во втором — его аналог DBImage, связанный с данными и расположенный на странице Data Controls.

    Начнем знакомство с этими компонентами. Откройте новое приложение и перенесите на форму компонент Image. Его свойство, которое может содержать изображение — Picture. Нажмите на кнопку с многоточием около этого свойства или просто сделайте двойной щелчок на Image, и перед вами откроется окно Picture Editor, позволяющее загрузить в свойство Picture какой — нибудь графический файл (кнопка Load), а также сохранить открытый файл под новым именем или новом каталоге. Щелкните на Load, чтобы загрузить графический файл. Перед вами откроется окно Load Picture. По мере перемещения курсора в списке по графическим файлам в правом окне отображаются содержащиеся в них картинки, а над ними — цифры, характеризующие размер картинки. Вы можете найти графические файлы в каталоге Images. В Delphi 5 он обычно расположен в каталоге…\program files\ Common Files\ Borland\ Borland Shared, в Delphi 4 — в …\ program files\ Common Files\ Borland Shared, в Delphi 3 — в …\ program files\ Borland\ Delphi 3, а в Delphi 1 — в каталоге Delphi 1. К сожалению, в Delphi 1 окно загрузки изображения значительно просматривать файлы до их загрузки.

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

    Вернемся к рассмотрению свойств компонента Image.

    Если установить свойство AutoSize в true, то размер компонента Image будет автоматически подгоняться под размер помещенной в него картинки. Если же свойство AutoSize установлено в false, то изображение может не поместиться в компонент или, наоборот, площадь компонента может оказаться много больше площади изображения.

    На практике часто встречается задача: по заданным на плоскости значениям (хi,yi), i= 0,1,…, n построить функцию, либо точно проходящую через эти точки, либо проходящую как можно ближе к этим точкам (рис 6.). Ниже рассмотрены три способа решения этой задачи: интерполяционный многочлен Лагранжа, метод наименьших квадратов и интерполяция кубическими сплайнами.

    Рис. 6 Задача интерполяции

    Интерполяционный многочлен Лагранжа

    График функции, определенной интерполяционным многочленом Лагранжа, проходит через все точки (хii,уi.):

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

    Для вычисления значений многочлена Лагранжа по уравнению (1.1) можно воспользоваться функций Lagr.

    Листинг 1. Функция Лагранжа

    В проекте предусмотрена возможность перетаскивания любой точки мышью. Поэтому наряду с традиционными функциями масштабирования используются функции обратного масштабирования и созданы обработчики трех событий onMouseDown, onMouseMove, onMouseUp, В процедуре Image IMouseDown определяется номер Num точки, ближайшей к (X,Y), и поднимается флаг, разрешающий перемещения, — Drawing:= True.

    Подобные документы

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

    курсовая работа [1,5 M], добавлен 12.01.2011

    Delphi как среда разработки программ, ориентированных на работу в Windows. Назначение и преимущество использования электронных учебников. Описание возможностей среды Delphi 5 для разработки электронного учебника. Варианты применения служб Internet.

    дипломная работа [3,6 M], добавлен 13.07.2011

    Основные понятия об операционных системах. Виды современных операционных систем. История развития операционных систем семейства Windows. Характеристики операционных систем семейства Windows. Новые функциональные возможности операционной системы Windows 7.

    курсовая работа [60,1 K], добавлен 18.02.2012

    Эволюция графических пользовательских интерфейсов. Устройство системы X Window и менеджеры окон. Описание рабочего стола и приложения KDE и GNOME. Обзор основных принципов организации интерфейса в системе Windows, описание пакета ее прикладных программ.

    реферат [1,8 M], добавлен 15.02.2012

    Изучение учебника и справочной подсистемы Windows 95/NT, получение навыков работы с «мышью», манипулирование окнами и значками программ и документов, запуска программ в системе Windows 95/NT. Разработка простого приложения для Windows при помощи Delphi.

    контрольная работа [1,1 M], добавлен 15.01.2009

    Разработка визуального интерфейса пользователя, на основе экранных форм среды Delphi и визуальных компонент. Основные типы данных, используемые в программе MD 5 Calc. Однонаправленные хэш-функции. Процесс хэширования MD5, возможности его применения.

    курсовая работа [433,1 K], добавлен 28.08.2012

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

    курсовая работа [38,5 K], добавлен 08.05.2011

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

    курсовая работа [54,3 K], добавлен 10.01.2012

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

    курсовая работа [1019,0 K], добавлен 16.05.2020

    Использование языка программирования Delphi для записи программ, представляющих собой последовательность инструкций. Классы и директивы в объектно-ориентированном программировании. Разработка демонстрационной программы. Процесс настройки Windows XP.

    дипломная работа [917,4 K], добавлен 15.01.2014

    Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
    PPT, PPTX и PDF-файлы представлены только в архивах.
    Рекомендуем скачать работу.

    Примеры вызовов API Windows в Delphi

    Содержание

    Разработчики библиотеки визуальных компонент (VCL) Delphi очень серьезно поработали над ее проектированием и воплощением в реальность. Как показывает практика, стандартного набора объектов обычно достаточно для создания реальных приложений. И, что более существенно, им (разработчикам) удалось решить очень сложную задачу, стоящую перед создателями любой среды визуального программирования — скрыть от программиста сложность и трудоемкость программирования в Windows и, в то же время, не лишать его возможности доступа к тем богатым возможностям системы, которые предоставляет Windows API.

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

    Компоненты, расположенные на странице “Standard”, представляют из себя объектную оболочку для стандартных управляющих элементов Windows. Поэтому для них существуют ограничения, накладываемые самой системой. Например, 32Кб — максимальный размер текста в TMemo.

    Добавление картинки (BitMap) в меню.

    Для добавления в меню картинки можно использовать функцию API Windows SetMenuItemBitmaps(), например, следующим образом:

    BMP1, BMP2 : TBitMap;

    procedure TForm1.FormCreate(Sender: TObject);

    SetMenuItemBitmaps(File1.Handle, 1, MF_BYPOSITION, BMP1.Handle, BMP2.Handle);

    procedure TForm1.FormDestroy(Sender: TObject);

    File1 это объект класса TMenuItem — пункт меню “File”. Значения параметров при вызове функции можно посмотреть в справочнике по Windows API.

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

    Вторая картинка BMP2 отображается рядом с пунктом меню, когда он выбран (Checked=True ).

    Компонент класса TMemo может содержать до 32К текста (для Windows 3.x) вследствие ограничения Windows. В Delphi 2.0 предел увеличен до 64К (в декабрьской бета-версии).

    I. Определение позиции каретки в TMemo.

    Можено использовать сообщения Windows API EM_LINEFROMCHAR и EM_LINEINDEX для определения текущей позиции каретки.

    LineNum:= Memo1.Perform(EM_LINEFROMCHAR, Memo1.SelStart,0);

    CharNum:= Memo1.Perform(EM_LINEINDEX, LineNum, 0);

    Label1.Caption := ‘Line : ‘+ IntToStr(LineNum+1);

    Label2.Caption := ‘Position :’ + IntToStr((Memo1.SelStart —

    Метод Perform, определенный в классе TControl, посылает сообщение своему же объекту, то есть его использование имеет тот же эффект, что и вызов функции API SendMessage():

    II. Операция UnDo в TMemo.

    Отмена последнего редактирования (операция UnDo) в объекте класса TMemo выполняется так же с помощью сообщений, посылаемых в данный объект:

    procedure TForm1.UnDoClick(Sender: TObject);

    if Memo1.Perform(EM_CANUNDO, 0, 0)<>0 then

    Memo1.Perform(EM_UNDO, 0, 0);

    В справочнике по Windows API описаны сообщения, которые можно послать в объект TMemo для управления его поведением. Кроме вышеназванных, имеется еще несколько полезных:

    EM_EMPTYUNDOBUFFER Сбрасывает флажок UnDo

    EM_GETHANDLE Получает указатель на буфер с текстом

    EM_LINESCROLL Прокрутка текста в окне TMemo

    EM_SETHANDLE Установка указателя на буфер с текстом

    EM_SETTABSTOPS Устанавливает табуляцию в окне с текстом

    Windows накладывает ограничение на количество элементов в списке этих управляющих элементов. В случае Windows 3.x это количество равно 5440, в Windows’95 — 32767.

    I. Как получить горизонтальную прокрутку (scrollbar) в ListBox?

    Так же как в случае с TMemo, здесь можно использовать сообщения. Например, сообщение может быть отослано в момент создания формы:

    procedure TForm1.FormCreate(Sender: TObject);

    ListBox1.Perform(LB_SETHORIZONTALEXTENT, 1000, Longint(0));

    Второй параметр в вызове — ширина прокрутки в точках.

    II. Вставка графики в ListBox.

    У класса TListBox ( и TComboBox тоже) есть свойство Style, определяющее порядок рисования объекта. По-умолчанию оно установлено в lbStandard и за внешний вид объекта отвечает Windows. Если установить это значение в lbOwnerDrawFixed или lbOwnerDrawVariable, то можно несколько разнообразить внешний вид объекта. Давайте построим для примера ListBox, отображающий названия файлов формата .BMP из какой-либо директории вместе с их картинками.

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

    procedure TForm1.Button1Click(Sender: TObject);

    ListBox1.Perform(LB_DIR, DDL_READWRITE, Longint(@s[1]));

    Здесь мы указали ListBox’у, какие файлы требуется отображать.

    Далее, как уже было сказано, свойство Style нужно установить в lbOwnerDrawFixed и создать обработчик события OnDrawItem:

    procedure TForm1.ListBox1DrawItem(Control: TWinControl;

    Index: Integer; Rect: TRect; State: TOwnerDrawState);

    with (Control as TListBox).Canvas do

    if Bitmap <> nil then begin

    <вычисляем квадрат для показа картинки>

    BMPRect:=Bounds(Rect.Left + 2, Rect.Top + 2,

    Offset := Rect.Bottom-Rect.Top + 6;

    <не забыть освободить!>

    Чтобы картинки получились побольше, значение свойства ItemHeight можно увеличить.

    Есть около двух десятков сообщений, которые можно послать в объекты класса TListBox и TComboBox. Подробнее о них можно узнать в справочнике по Windows API (on-line Help).

    Компоненты, размещенные на этой странице представляют из себя объектную оболочку для управляющих элементов, появившихся в более поздних версиях Windows.

    I. Эмуляция потери фокуса.

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

    procedure TForm1.SpeedButton1Click(Sender: TObject);

    if ActiveControl is TEdit then

    (ActiveControl as TEdit).OnExit(ActiveControl);

    Обработчик события должен быть определен, иначе возникнет GPF.

    II. Обработка двойного щелчка мышью.

    Если значение свойства GroupIndex равно 0, то двойное нажатие будет воспринято объектом как два одиночных. Если требуется, чтобы в результате двойного щелчка кнопка не фиксировалась в нажатом состоянии, то ее свойство AllowAllUp устанавливается в True, и в обработчике события кнопка возвращается в прежнее состояние:

    procedure TForm1.SpeedButton1DblClick(Sender: TObject);

    SpeedButton1.Down:= Not SpeedButton1.Down;

    Ограничения по количеству страниц для этих объектов — 16364 (еще одно “магическое число” из класса TList). Но, на самом деле, не имеет смысла создавать более сотни страниц.

    I. Переход на страницу по ее имени.

    Если объект типа TTabSet содержит большое количество страниц, то пролистывать их в поисках нужной — дело утомительное. Проще найти ее по имени. Предположим, что имя страницы вводится в Edit1 :

    procedure TMultPageDlg.Edit1Change(Sender: TObject);

    if s1 = » then Exit;

    for i:=TabSet.Tabs.Count-1 downto 0 do begin

    s2:=Copy(TabSet.Tabs[i], 1, Ord(s1[0]));

    if CompareText(s1, s2)

    I. Добавление новых объектов во время выполнения программы.

    После создания нового объекта, нужно в его свойстве Parent указать требуемую страницу TabbedNotebook:

    TOLEContainer — компонент, который нужно использовать во время дизайна с осторожностью, поскольку вся информация о нем сохраняется в .DFM файле. И если попытаться использовать встроенный (embedded) OLE-объект большого размера, то при сохранении формы может возникнуть ошибка “Out of resource”.

    I. Хранение OLE-объектов в базе данных

    Использование технологии OLE для хранения информации в базе данных оправдано, если эта информация неоднородна. Иногда возникает необходимость одновременно сохранять в таблице и использовать в дальнейшем графические изображения, документы в формате Microsoft Word, звук и тому подобное. К сожалению, в стандартном наборе компонент отсутствует компонент вроде TDbOleContainer. Однако, обойтись без него можно достаточно просто.

    Для хранения OLE-объекта подойдет поле типа BLOB, а для работы с этим объектом — существующий компонент TOLEContainer . Если предполагается использовать таблицу на разных компьютерах, то OLE-объект нужно делать встроенным (embedded) и следить за тем, чтобы присутствовали соответствующие OLE-серверы.

    Итак, сперва нужно заполнить таблицу:

    procedure TForm1.OLEInitClick(Sender: TObject);

    if InsertOLEObjectDlg(Self, 0, Info) then begin

    procedure TForm1.OLEFreeClick(Sender: TObject);

    procedure TForm1.InsertClick(Sender: TObject);

    OLE:=FindComponent(‘Temp_OLE’) as TOLEContainer;

    if Assigned(OLE) then

    with OLE do begin

    SaveToStream(TBS as TStream);

    procedure TForm1.PostClick(Sender: TObject);

    if Table1.State <> dsBrowse then Table1.Post;

    Вторая часть проблемы — чтение OLE-объекта из таблицы и обновление информации при его модификации. Здесь можно предложить динамически создавать OLE-контейнер и считывать OLE-объект из потока, связанного с BLOB полем. И делать это каждый раз при переходе на новую запись (перехватывать для DataSource событие OnDataChange). Либо вытаскивать информацию по запросу пользователя, а уничтожать OLE-контейнер при переходе на новую запись.

    procedure TForm1.ViewClick(Sender: TObject);

    OLE.LoadFromStream(TBS as TStream);

    procedure TForm1.SaveClick(Sender: TObject);

    OLE:=FindComponent(‘Temp_OLE’) as TOLEContainer;

    if Assigned(OLE) then

    if Modified then begin

    TBS:= TBlobStream.Create(Table1.FieldByName(‘OLE’) as

    SaveToStream(TBS as TStream);

    procedure TForm1.DataSource1DataChange(Sender: TObject;

    if Table1.State = dsBrowse then

    В примерах выше предполагается наличие таблицы с BLOB полем, которое называется “OLE”.

    Компоненты, расположенные на данной странице представляют из себя объектную оболочку для BDE (Borland Database Engine) — библиотеки доступа к данным. Компоненты TTable, TQuery, TStoredProc, TDataBase и TBatchMove (а так же TSession, который включен в Палитру в версии Delphi 2.0) имеют свои низкоуровневые аналоги. Хотя эти объекты достаточно полно реализуют возможности BDE, тем не менее, существуют некоторые задачи, решить которые можно только с помощью прямых вызовов функций BDE. Например, сменить пароль в таблице Paradox или упаковать таблицу dBase. Но об этом позже, а сейчас — о компонентах на странице “Data Access”.

    I. Определение момента перехода на другую запись.

    Если в момент вызова обработчика события OnDataChange для TDataSource

    состояние его DataSet есть dsBrowse, то произошел переход на другую запись.

    procedure TForm1.DataSource1DataChange(Sender: TObject;

    if DataSource1.DataSet.State = dsBrowse then

    I. Проблемы при создании таблиц.

    Как известно, класс TTable имеет метод для создания таблиц CreateTable, который использует при этом структуры FieldDefs и IndexDefs. Однако, некоторые возможности не реализованы (видимо из соображений общности — TTable должен работать одинаково с таблицами разных форматов). Два примера на эту тему: используя TTable нельзя создать автоинкрементное поле для таблицы формата Paradox и нельзя указать разрядность и число знаков после запятой для числового поля в таблице dBase. В подобных случаях лучше использовать Local SQL (и компонент TQuery). Выражение SQL, решающее первую проблему: CREATE TABLE “PDX_TBL.DB”

    PRIMARY KEY (KOD)

    Выражение SQL, решающее вторую проблему: CREATE TABLE “DB_TBL.DBF”

    FIELD1 DECIMAL (10,3)

    II. Добавление записи в DataSet, не имеющий первичного ключа.

    Не вдаваясь в теорию реляционных СУБД, скажу, что BDE не очень хорошо работает с наборами данных, у которых нет первичного ключа и нельзя однозначно идентифицировать запись. Для программиста, использующего компонент TTable для доступа к таблице без первичного ключа, проблемы возникают при добавлении в такую таблицу новой записи. Для того, чтобы состояние TTable соответствовало состоянию физической таблицы, ее требуется переоткрыть (Close , а потом Open). Метод Refresh не поможет — он проходит только при наличии первичного ключа. После пероткрытия возникает проблема, как позиционироваться на запись, которая была текущей до переоткрытия. Найти ее можно только перебором записей, поскольку закладки (BookMark) для такой таблицы нестабильны.

    В случае с объектом класса TQuery, работающим с живым набором данных (live dataset), ситуация аналогичная. Не имеет значения, что запрос выполняется для таблицы, имеющей первичный ключ — результатом такого запроса все равно является набор данных, у которого нет ни первичного ключа, ни индексов.

    III. Определение номера текущей записи.

    Понятие номера (физического) текущей записи имеет некоторый смысл при работе с таблицами в формате dBase. Этот номер меняется достаточно редко, только при упаковке удаленных записей в таблице. В случае таблиц Paradox можно говорить только о логическом номере записи: место записи меняется при добавлении/удалении новой записи или при наложении индекса. Для данных на SQL сервере понятия “номер записи” не имеет смысла вообще (этого понятия нет в теории реляционных баз данных).

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

    Так как же быть программисту, если он работает с данными в формате dBase и желает, все-таки, что бы номер записи присутствовал на форме? Ему придется использовать прямой вызов функции BDE (подключив сперва к программе модули DBITYPES и DBIPROCS):

    function TForm1.GetRecNo : Longint;

    dbiGetRecord(Table1.Handle, dbiNoLock, NIL, @Pr);

    Функция GetRecNo возвращает номер записи, ее можно использовать, например, для определения вычисляемого поля (обработчик OnCalcFields) и отражать это поле в DBGrid первым.

    Более подробно о вызовах BDE будет рассказано позже.

    Это наиболее применяемый в программах компонент для отображения данных. Понятно, что задачи бывают очень разными и требования к TDBGrid также очень разнообразны. Но в первой версии Delphi этот объект достаточно прост, он не умеет отображать графические и мемо-поля, в него нельзя поместить CheckBox или ComboBox. Есть достаточно много претензий к данному компоненту, но есть уверенность, что в Delphi 2.0 многие проблемы, связанные с этим компонентом, решены. Однако, многое можно сделать и с той версией DBGrid, что поставляется с Delphi 1.0.

    I. Унаследованные свойства.

    Многие проблемы можно решить простым переносом некоторых свойств и методов из раздела protected в декларации класса TDBGrid в разделы public и published. Лучше всего создать новый класс — наследника от TDBGrid:

    <прямоугольник для указанной ячейки>

    function CellRect(ARow, AСol : Integer) : TRect;

    Система программирования Delphi

    Общие сведения о языке программирования Delphi

    Язык программирования Delphi — результат развития языка Турбо Паскаль, который, в свою очередь, развился из языка Паскаль. Паскаль был полностью процедурным языком, Турбо Паскаль, начиная с версии 5. 5, добавил в Паскаль объектно-ориентированные свойства, а Delphi — объектно-ориентированный язык программирования с возможностью доступа к метаданным классов (то есть к описанию классов и их членов) в компилируемом коде, также называемом интроспекцией.

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

    Среда разработки Delphi 7.0

    Внешний вид среды программирования Delphi отличается от многих других из тех, что можно увидеть в Windows. К примеру, Borland Pascal for Windows 7.0, Borland C++ 4.0, Word for Windows, Program Manager — это все MDI приложения и выглядят по-другому, чем Delphi. MDI (Multiple Document Interface) — определяет особый способ управления нескольких дочерних окон внутри одного большого окна.

    Среда Delphi же следует другой спецификации, называемой Single Document Interface (SDI), и состоит из нескольких отдельно расположенных окон.

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

    Ниже перечислены основные составные части Delphi:

    — Дизайнер Форм (Form Designer)

    — Окно Редактора Исходного Текста (Editor Window)

    — Палитра Компонентов (Component Palette)

    — Инспектор Объектов (Object Inspector)

    — Справочник (On-line help)

    Есть, конечно, и другие важные составляющие Delphi, вроде линейки инструментов, системного меню и многие другие, нужные Вам для точной настройки программы и среды программирования.

    Дизайнер Форм и Окно Редактора Исходного текста, наверное, самые используемые и самые важные элементы среды. В первом вы настраиваете внешний вид программы, а во втором редактируете и пишете код программы. Элементы представлены на рисунке 1 и на рисунке 2.

    Рисунок 1 — Дизайнер Форм (Form Designer)

    Рисунок 2 — Окно Редактора Исходного Текста (Editor Window)

    Палитра Компонент позволяет Вам выбрать нужные объекты для размещения их на Дизайнере Форм. Палитра Компонентов использует постраничную группировку объектов. Внизу Палитры находится набор закладок — Standard, Additional, Dialogs и т.д. Если Вы щелкнете мышью на одну из закладок, то Вы можете перейти на следующую страницу Палитры Компонентов. Принцип разбиения на страницы широко используется в среде программирования Delphi и его легко можно использовать в своей программе. На рисунке 3 представлена Палитра Компонентов.

    Рисунок 3 — Палитра Компонетов (Component Palette)

    Еще одним важным компонентом среды являеться Инспектор Объектов. Информация в Инспекторе Объектов меняется в зависимости от объекта, выбранного на форме.

    Инспектор Объектов состоит из двух страниц, каждую из которых можно использовать для определения поведения данного компонента. Первая страница — это список свойств, вторая — список событий. Если нужно изменить что-нибудь, связанное с определенным компонентом, то Вы обычно делаете это в Инспекторе Объектов. К примеру, Вы можете изменить имя и размер компонента TLabel изменяя свойства Caption, Left, Top, Height, и Width. Инспектор объектов представлен на рисунке 4.

    Рисунок 4 — Инспектор Объектов (Object Inspector)

    Последняя важная часть среды Delphi — Справочник (on-line help). Для доступа к этому инструменту нужно просто выбрать в системном меню пункт Help и затем Contents. На экране появится Справочник

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