Нeвизуальные классы в Delphi

В этом обзоре мне хотелось бы осветить несколько классов Delphi, которым обычно в книгах и других обучающих материалах уделяется (если уделяется) весьма скромное внимание. Это так называемые невизуальные классы, не порожденные непосредственно от TComponent и не имеющие владельца. Следовательно, начинающий разработчик в палитре компонентов их не видит, в книжках о них не пишут, а help’ы ему читать неохота или он просто не знает английского… Так уж получается что RAD не способствуют обучению работы с невизуальными классами, что плохо отражается как на квалификации такого программиста, так и на предлагаемых им программных решениях. Отсюда и множество вопросов из разряда FAQ задаваемых по этой теме в конференциях.

Итак, наиболее подробно я хочу остановиться на трех классах инкапсулирующих список (TList), поток данных (TStream) и поток приложения (TThread).

Начнем с класса TList. Это удачное решение программистов Borland, которое реализует большинство возможностей для работы со связными списками нетипизированных указателей. Список – это наиболее гибкая структура данных, которая позволяет хранить в индексированном виде объекты разных типов. Массив нетипизированных указателей размещается в куче.

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

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

·        property Count: Integer; - определяет количество элементов списка

·        property Items[Index: Integer]: Pointer; - индексированный массив указателей на объекты.

Эти два свойства служат для доступа к объектам списка. Обратите внимание, что многие из объектов, построенных с использованием списков, для доступа к своим элементам используют аналогичные свойства (TStrings, TStringGrid, TListView и другие).

Стоит также отметить свойство Capacity. Если вы заранее знаете количество элементов в списке, то стоит присвоить это значение свойству Capacity.

Методов у класса TList заметно больше чем свойств. Рассмотрим наиболее важные из них:

·        function Add(Item: Pointer): Integer; - добавляет объект в список. Указатель на объект указывается как параметр функции. Стоит отметить, что большинство объектов в Delphi уже являются указателями. Так, переменная Form1: TForm, является указателем на объект Form. В отличие от Borland Pascal for DOS, в Delphi в этом случае не применяется запись ^TForm.

·        procedure Clear; dynamic; - как следует из названия, очищает список, при этом удаляются все ссылки на объекты и занимаемая ими память. Счетчик Count устанавливается в нуль. Сами объекты должен удалить программист используя, например, метод Dispose.

·        procedure Delete(Index: Integer); - удаляет заданный индексом элемент из списка. Элементы нумеруются начиная с 0! Следующие за удаленной записью элементы, смещаются на один «вверх».

·        procedure Exchange(Index1, Index2: Integer); - вспомогательная функция, используется для обмена местами двух элементов списка.

·        function IndexOf(Item: Pointer): Integer; - определяет, является ли конкретный указанный объект элементом списка, и если это так, возвращает его индекс.

·        procedure Insert(Index: Integer; Item: Pointer); - вставляет объект в список на место, указанное параметром Index.

·        procedure Sort(Compare: TListSortCompare);

type TListSortCompare = function (Item1, Item2: Pointer): Integer; 
Данная процедура весьма удобное средство для сортировки списка.

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

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

для сравнения текста:

       CompareStr        – для регистрозависимого текста

CompareText       - для регистронезависимого текста

для сравнения дат

       CompareFileTime  - даты в системном формате

для сравнения регионов

       EqualRgn

другие

       EqualRect,

       EqualSid,

       IShellFolder::CompareID.

 

Следующая остановка – потоки данных. В Delphi есть несколько видов потоков – файловый поток, поток в памяти, поток связанный с handle объекта, строковый поток и т.д. Все они унаследованы от своего абстрактного предка TStream. Разобравшись с ним вы сможете единообразно работать с любыми другими потоками.

Поток - это некоторая обобщенная модель двоичных данных

У базового класса есть два свойства, унаследованные всеми потомками: Position и Size, которые определяют позицию в потоке и его размер, соответственно.

Рассмотрим основные функции класса. Если вы уже работали с файлами, то обращение к этим методам весьма схоже:

function Read(var Buffer; Count: Longint): Longint; virtual; abstract; - читает данные из потока в буфер;

function Write(const Buffer; Count: Longint): Longint; virtual; abstract; - пишет данные в поток;

function Seek(Offset: Longint; Origin: Word): Longint; virtual; abstract; - устанавливает позицию в потоке.

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

Есть и пара просто полезных функций

function CopyFrom(Source: TStream; Count: Longint): Longint; - эта функция копирует часть или весь поток из другого потока. Этим методом, например, очень удобно копировать файлы. Есть мнение, что работа с потоками, быстрее работы с файлами. К сожалению у меня не было возможности проверить это утверждение…

procedure SaveToFile(const FileName: string); - позволяет сохранить участок памяти в дисковый файл.

 

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

Итак, когда и зачем используются потоки? Что это такое? Потоки – это объекты, которым процессор выделяет время. Обратите внимание, не процессам или программам, а порожденным им потокам. Так что любая программа содержит в себе хотя бы один поток. Он называется главным. С появлением потоков появилась возможность отдавать для решения каждой задачи ровно столько ресурсов, сколько ей нужно. Для этого используется понятие приоритета потока. Каждый процесс имеет свой приоритет (от фонового до реального времени). В рамках процесса существуют потоки, приоритетом которых (потоков) может управлять процесс. Таким образом приоритет может иметь значение от 0 до 31. Следует заметить, что программы написанные в Delphi по умолчанию порождают процесс с нормальным приоритетом (он может быть переднего или заднего плана, в зависимости от активности задачи и меняться от 9 до 7). Процессы реального времени используются крайне редко.

Чтобы сделать свой поток, вам нужно унаследовать свой класс от объекта TThread. Это можно сделать из меню File -> New… Будет создан модуль с заготовкой для класса. Все что вам нужно сделать – перекрыть метод Execute. Для этого его нужно объявить как override и наделить нужной функциональностью.

При написании метода Execute нужно учесть следующее: поток работает параллельно с основным потоком программы и может обращаться к каким-то элементам или компонентам  также параллельно с основным потоком. Чтобы избежать этого, нужно использовать метод Synchronize. В его параметре следует указать действия, производимые с «общими» объектами. Например

Synchronize(MainForm.Label1.Caption:=’Hello!’);

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

Процесс может находиться в двух состояниях – выполняться или быть приостановленным. За это отвечают методы

procedure Suspend;

procedure Resume;

Также процесс может самоуничтожаться по завершении - свойство FreeOnTerminate.

Метод

function WaitFor: LongWord; ожидает завершения потока.

 

Hosted by uCoz