Библиотека времени выполнения .NET и FCL
Глава из книги “Delphi 2005. Для профессионалов”
Автор: М. Кэнту
Источник: Delphi 2005
Материал предоставил: Издательство »Питер»
Опубликовано: 23.07.2006
Версия текста: 1.0
Несколько последних глав (с 5 по были посвящены библиотеке времени выполнения 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 |
Что касается класса 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
Именно эта часть 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, а остальные методы относятся к сравнению и представлению объектов:
Для демонстрации некоторых методов (и особенно сравнения объектов) я создал пример FclSystemObject. Одна из кнопок примера вызывает разные методы класса System.Object для самой кнопки; результат показан на рис. 9.1. Рис. 9.1. Кнопка Base в приложении FclSystemObject вызывает основные методы System.Object Вторая кнопка демонстрирует различия между методами Equals и ReferenceEquals. В программе определяется пользовательский класс, переопределяющий методы ToString и Equals: Как видно из листинга, класс изменяет выводимую информацию (свое имя) и реализует собственные правила сравнения (в которых задействовано только одно из двух полей). Строковое представление используется при заполнении двух полей со списками при запуске программы: Другой переопределенный метод Equals вызывается при нажатии кнопки Reference Equals после выделения двух объектов в полях со списками: Обратите внимание: Equals является методом объекта и получает сравниваемый объект в параметре, тогда как Reference Equals является статическим методом класса и получает в параметрах оба сравниваемых объекта. Хотя многие классы определяют или переопределяют ToString, во многих классах и базовых типах присутствует обратный метод (статический метод класса) Parse для преобразования строки в значение. Например, если имеется Double и строка, то преобразование между ними может выполняться кодом следующего вида: Класс StringBuilderSystem.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.
ВыражениеРезультат | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Pos(‘rat’, ‘grated’) | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Pos(‘sh’, ‘champagne’) |
- Str (X [: Width [: Decimals] ], var S: string) — преобразует числовое значение величины X в строку S. Необязательные параметры Width и Decimals являются целочисленными выражениями. Значение Width задает ширину поля результирующей строки. Значение Decimals используется с вещественными числами и задает количество символов в дробной части.
Выражение
Значение S
- Val (S: string, var V; var Code: Integer) — преобразует строку S в величину целого или вещественного типа и помещает результат в переменную V. Если во время операции преобразования ошибки не обнаружено, значение переменной Code равно нулю; если ошибка обнаружена (строка содержит недопустимые символы), Code содержит номер позиции первого ошибочного символа, а значение V не определено.
Выражение
Значение V
Значение Code
Описанные процедуры и функции являются базовыми для всех остальных подпрограмм обработки строк из модуля 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 считаются не равными, если они отличаются по мощности или по значению хотя бы одного элемента.
Выражение
Результат
Операции принадлежности (>=, = B равно True, если все элементы множества B содержатся в множестве A. Выражение A = [1, 2]
Выражение
Результат
Операция in позволяет эффективно и наглядно выполнять сложные проверки условий, заменяя иногда десятки других операций. Например, оператор
можно заменить более коротким:
Операцию in иногда пытаются записать с отрицанием: X not in S. Такая запись является ошибочной, так как две операции следуют подряд. Правильная запись имеет вид: not (X in S).
Объединение множеств (+) . Объединением двух множеств является третье множество, содержащее элементы обоих множеств.
Выражение
Результат
Пересечение множеств (*) . Пересечение двух множеств — это третье множество, которое содержит элементы, входящие одновременно в оба множества.
Выражение
Результат
Разность множеств (-) . Разностью двух множеств является третье множество, которое содержит элементы первого множества, не входящие во второе множество.
Выражение
Результат
В язык Delphi введены две стандартные процедуры Include и Exclude, которые предназначены для работы с множествами.
Процедура Include (S, I) включает в множество S элемент I. Она дублирует операцию + (плюс) с той лишь разницей, что при каждом обращении включает только один элемент и делает это более эффективно.
Процедура Exclude (S, I) исключает из множества S элемент I. Она дублирует операцию — (минус) с той лишь разницей, что при каждом обращении исключает только один элемент и делает это более эффективно.
Выражение
Результат
Использование в программе множеств дает ряд преимуществ: значительно упрощаются сложные операторы 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 при ошибке.
(Запомните, каждый пункт здесь является базовым правилом, а не непреложной истиной. Считайте, что перед каждым пунктом написано «Если явно не указано обратное, то. «. Если вызывающий и вызываемый соглашаются на исключении из правила, то это исключение будет работать.)
Написать этот список было непростым делом: всё равно что выписать все недопустимые шахматные ходы. Эти правила настолько автоматически выполняются, что они скорее не правила, а вещи, которые просто существуют, и действовать иначе является сумасшествием. В результате, я уверен, будет множество других «правил, настолько очевидных, что о них не говорят», которые я упустил (к примеру: «вы не можете завершить поток, который выполняет чью-то ещё функцию»).
Удобное правило для оценки того, что вы можете и не можете делать: спросите себя, «Как бы мне понравилось, если бы кто-то сделал это со мной?» (это особый случай теста «представьте, если бы это было возможным»).
Структура папок и файлов
Следующий вопрос — какие файлы нам понадобятся, что нам нужно создать. Для начала заметим, что когда мы говорим о системе плагинов, то мы можем говорить с двух сторон: со стороны ядра и со стороны плагина. И ядро и плагин должны следовать контракту, который мы для них определим, но делать это они будут с разных сторон. Это означает, что нам потребуются:
- Общие файлы — используются и ядром и плагинами
- Файлы ядра (то, к чему не имеют доступа плагины)
- Файлы плагинов (то, к чему не имеет доступа ядро)
- Файлы вне системы плагинов (функциональность ядра и плагинов)
Поэтому, для начала, чтобы отделить п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 . Т.е. она максимально изолирована от внешних воздействий.
Итак, что будет делать менеджер плагинов? Ну, наверное для начала плагины надо бы найти и загрузить. Как это обычно делают? Есть два способа:
- Пользователь указывает в настройках программы, какие плагины нужно включать.
Плюсы:- Пользователь может использовать плагин в любом месте.
- Пользователь может отключать плагин из интерфейса программы, не удаляя его.
Минусы:
- Пользователю нужно настраивать плагины вручную.
- Программа загружает как плагины все файлы из предопределённой папки.
Плюсы:- Пользователю ничего не надо делать.
Минусы:
- Чтобы удалить или добавить плагин, его нужно скопировать в папку или удалить из неё.
- Плагин нельзя «отключить», не удалив его.
В принципе, эти подходы можно комбинировать. Скажем, автозагружать плагины из папки, но при этом не загружать отключенные (исключения) и допускать загрузку по команде из другого места.
Итак, что должен уметь делать менеджер плагинов, чтобы реализовать какую-то из схем выше?
Загрузка одного плагина
Какую-бы схему мы ни реализовали бы — нам обязательно в любом случае потребуется функция загрузки одного плагина. Вот давайте с неё и начнём:
Ух, что-то тут много всего появилось. Давайте по порядку. Начнём с изменений в 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 — это и есть нужные нам файлы.
Выводы по ведению заголовочных файлов
Итак, подводя черту, у вас есть три способа ведения заголовочников:
- Просто писать .pas файл и при необходимости вручную перевести его на другие языки.
- Создать библиотеку типов и редактировать её в редакторе Delphi (вообще говоря, для этого можно использовать любой редактор TLB, а не только Delphi). В конце автоматически получить .tlb и комплект файлов для Delphi, C++ Builder, Visual Studio C++, а также текстовое описание (idl/ridl).
- Создать 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 (Инспектор объектов), To—Do—List (Список задач), 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. На экране появится Справочник