Примеры применения потоковой системы VCLИсточник: "Программист" №2, 2002 Абдулин Марат
Потоковая система VCL - незаменимый помощник Delphi-программиста. В этом можно убедиться, познакомившись с решениями, предложенными вашему вниманию в этой статье. Здесь представлены примеры использования потоковой системы VCL и конкретные решения прикладных задач, использование которых на практике доказало свою состоятельность. Потоковая система предназначена для решения самых разных прикладных задач. К их числу можно отнести:
Основная идеяВ основу рассматриваемых здесь решений положена идея представления данных объекта в виде потока байтов. С таким потоком можно сделать практически все что угодно: записать на диск, сохранить в memo-поле, упаковать в строку или OleVariant, поместить в буфер обмена и проделать многие другие манипуляции. Чтобы представить данные объекта в виде потока байтов, совсем не обязательно писать собственные процедуры - они уже реализованы в потоковой системе. Основное требование - объект должен быть компонентом, пусть невизуальным, пусть не зарегистрированным в палитре компонентов, достаточно того, чтобы он был наследником класса TComponent. Если у вас уже есть такой объект, то сохранение его данных в потоке и восстановление из потока не представляет особой сложности. Сохранить данные объекта в потоке можно при помощи методов TStream.WriteComponent или TStream.WriteComponentRes, а восстановить - при помощи методов TStream.ReadComponent и TStream.ReadComponentRes соответственно. Указанные методы работают с разными форматами потока данных компонента. Если используются методы WriteComponent и ReadComponent, то в потоке хранятся только данные компонента. Если WriteComponentRes и ReadComponentRes, то в потоке присутствует заголовок Windows-ресурса, за которым следуют данные самого компонента, такой поток является Windows-ресурсом. Сохранение результатов редактирования и защита от сбоевСохранение данных объекта в потоке используется прежде всего для сохранения объекта в файле. В VCL в модуле Classes реализованы процедуры WriteComponentResFile и ReadComponentResFile. Первая сохраняет данные объекта в бинарном файле, а вторая восстанавливает данные объекта из бинарного файла. Если же вы хотите сохранять данные в текстовом формате, то потоковая система поможет выполнить преобразование бинарного формата в текстовый (ObjectBinaryToText) и обратно (ObjectTextToBinary).
Главный недостаток сохранения данных компонента в текстовом формате - длительное время выполнения процедур преобразования одного формата в другой, в особенности при вызове процедуры ObjectTextToBinary. Если смириться с этим недостатком, то вы получаете чрезвычайно простую реализацию, универсальность и, конечно же, текстовый формат данных. Файлы в таком формате можно читать и править в любом текстовом редакторе. Очень удобно в таком файле хранить параметры конфигурации программы (без особых усилий вы получаете своего рода ini-файл); данные программы-редактора (вам не нужно будет придумывать собственные форматы хранения данных и писать нетривиальные процедуры преобразования программного представления данных в текстовое и наоборот); и, например, критически важные программные данные, которые программа систематически сохраняет на диске (такие данные легче читать и анализировать). Взаимодействие с буфером обменаПользователи (в том числе и программисты) привыкли к тому, что в любом редакторе есть функции взаимодействия с буфером обмена. Реализация этих функций - не такое уж сложное дело. Данные компонента можно поместить в буфер обмена с помощью метода TClipboard.SetComponent, и извлечь из буфера при помощи метода TClipboard.GetComponent. Эти методы помещают и извлекают данные компонента в формате CF_COMPONENT, который регистрируется библиотекой VCL (модуль Clipbrd) в момент загрузки программы. Формат CF_COMPONENT будет понятен только вашей программе, и это вполне устроит конечных пользователей, но, вполне возможно, не устроит вас - программиста, потому что функции взаимодействия с буфером обмена по непонятным причинам не работает, и вы уже не первый день пытаетесь понять почему. Разобраться в этом помогут процедуры CopyComponentAsText и PasteComponentAsText, которые для взаимодействия с буфером обмена используют текстовый формат CF_TEXT:
Имейте в виду, что преобразование бинарного формата в текстовый и обратно займет некоторое время. Впрочем, конечные пользователи заметят это, только если к процедуре обратиться несколько раз подряд, например в цикле. Клонирование объектовДругой пример применения потоковой системы - создание точной копии объекта (клонирование объекта). Функция клонирования объекта представляет собой реализацию классического шаблона проектирования (design pattern) Prototype (см. [1]), суть которого заключается в конструировании объекта по его прототипу.
Функция CloneComponent вызывает процедуру CopyComponent, в которой данные SrcComp записываются в поток, а затем читаются из потока в DstComp. Хотел бы обратить ваше внимание на то, что при записи компонента SrcComp в поток попадают только значения, отличные от значений по умолчанию. Поэтому не удивляйтесь, если при прямом использовании процедуры CopyComponent (т.е. при использовании этой процедуры в обход CloneComponent) вы обнаружите различия в значениях свойств в SrcComp и DstComp. Это может произойти, потому что компонент SrcComp «умолчит» о тех свойствах, значения которых совпадают со значениями, устанавливаемыми в его конструкторе. Для решения этой проблемы необходимо перед вызовом процедуры CopyComponent установить у свойств компонента DstComp значения по умолчанию. Код установки значений по умолчанию можно вынести в специальный метод компонента, скажем, SetDefaults, который можно будет вызывать как из конструктора, так и напрямую. Замечу, что при вызове процедуры CopyComponent из CloneComponent такой проблемы не будет, поскольку в процедуре CloneComponent новый компонент сначала конструируется (и его свойства приобретают значения по умолчанию), а затем в него копируются данные из исходного SrcComp-компонента. Сохранение данных объекта в базе данныхЕще один пример применения потоковой системы - сохранение данных объекта в memo-поле записи таблицы. Большинство реляционных базы данных не поддерживает прямое сохранение данных объектов в таблице, но эту функциональность можно реализовать самому. Чтобы сохранить данные объекта в memo-поле необходимо преобразовать их в поток байтов, а затем поместить в Memo-поле. Вот процедуры сохранения и восстановления данных объекта в Memo-поле:
Передача данных объекта между процессамиСледующий пример использования потоковой системы - передача данных объекта между процессами. Передачу данных объекта проще всего осуществить, используя механизм Automation. Большие массивы бинарной (несимвольной) информации принято передавать серверу Automation при помощи параметров типа OleVariant. Например, передачу данных объекта серверу Automation можно выполнить так: вызывающая сторона упаковывает данные объекта в OleVariant и передает их серверу Automation, а принимающая сторона, получает данные и извлекает их из OleVariant. Упаковкой данных объекта в OleVariant занимается процедура ComponentToVariant, а извлечением данных - процедура VariantToComponent:
Передача данных объекта между процессами находит применение в многокомпонентных приложениях, где отдельные подсистемы можно обозначить или как клиентские или как серверные. Как правило, в таких системах, серверная (вызываемая) сторона отвечает за управляемый доступ к данным, а клиентская (вызывающая) сторона отвечает за модификацию этих данных. Чтение данных формы из файлаИспользование потоковой системы для реализации механизма передачи данных объекта между процессами действительно кажется экзотическим, поскольку основное назначение потоковой системы - чтение-запись данных формы. Следующий пример вряд ли можно причислить к разряду экзотических, в нем потоковая система как раз используется для чтения данных формы из файла. Простейший способ решения проблемы - поставлять пользователю стандартную форму и несколько дополнительных форм. Универсальное решение - поставлять конструктор форм, с помощью которого пользователь самостоятельно добавит нужные поля и удалит ненужные. Данные формы можно напрямую записать в поток методом TStream.WriteComponentRes. Небольшие сложности возникают при чтении данных формы из потока; чтобы корректно прочитать форму из потока придется позаимствовать код TApplication.CreateForm
Данные формы, как и данные любого другого объекта, порожденного от класса TComponent, можно записать в поток и прочитать из потока. Процедуры записи и чтения данных объекта предоставляет сама потоковая система. Поддержка сервисов перманентности в COM-объектахВ отличие от объектов Delphi, COM-объекты должны самостоятельно реализовывать сервисы записи и чтения собственных данных. Клиенты COM-объектов получают доступ к этим сервисам (еще их называют сервисами перманентности [4]) через один из интерфейсов семейства IPersist* (IPersistFile, IPersistMemory, IPersistStream, IPersistStorage, IPersistMoniker). Если ваш COM-объект должен поддерживать один из этих интерфейсов, то для их реализации также можно воспользоваться потоковой системой VCL. Рассмотрим реализацию сервисов перманентности на примере интерфейса IPersistStream. Данные COM-объекта мы будем хранить в специальном компоненте, а реализацию методов интерфейса IPersistStream делегируем вспомогательному классу TOlePersist, у которого будет ссылка на компонент, хранящий данные COM-объекта. При таком подходе класс TOlePersist можно будет использовать в разных реализациях COM-объектов. Наибольший интерес в интерфейсе IPersistStream представляют методы IPersistStream.Load и IPersistStream.Save. Их реализация приводится ниже:
Реализацию других методов интерфейса IPersistStream вы можете найти в демонстрационной программе, которую можно скачать с сайта www.programme.ru. Там же вы сможете найти и код других примеров, описанных в статье. ЗаключениеВ использовании потоковой системы нет ничего сверхсложного или необычного, пожалуй, одно из доказательств этого - небольшой объем примеров. Возможно, сами примеры разные, но все они в своей основе используют одну идею - представление программных данных в виде потока байтов. Чаще всего программные данные хранятся (инкапсулируются) в объекте, поэтому в качестве входного параметра всех процедур используется объект-компонент. Если вы будете писать собственные процедуры, рекомендую делать именно так, тогда ваши процедуры будут отвечать требованию общности и универсальности. Если вы еще не знакомы с потоковой системой, советую обратить внимание на литературу, приведенную в библиографии, в частности на две книги Рея Лишнера [2] и [3], в них содержится информация в достаточном объеме. Здесь я только вскользь упомянул особенности самой потоковой системы, этой теме нужно посвящать отдельную статью, а, лучше целую серию статей. Надеюсь, моя статья подвигнет других программистов поделиться знаниями. Применяя потоковую систему начиная с первой версии Delphi, я продолжаю открывать для себя ее новые возможности, и недавно обнаружил несколько статей Андрея Чудина, связанных с этой темой, собственно он и стал автором идеи публикации данной статьи в журнале «Программист», за что я выражаю ему свою признательность. Я рекомендую изучить и его статьи и взять на вооружение описываемые там процедуры, без них библиотеку нельзя будет назвать полной. Библиография
|