![]() |
Модераторы: Poseidon, Snowy, bems, MetalFan |
![]() ![]() ![]() |
|
Alexeis |
|
||||||||||||||||||||||
![]() Амеба ![]() Профиль Группа: Админ Сообщений: 11743 Регистрация: 12.10.2005 Где: Зеленоград Репутация: 109 Всего: 459 |
Использование потоков данных (TStream) Для чего эта статья. Статья для тех кто до сих пор игнорирует этот весьма эффективный и быстрый метод обработки данных. Чтобы легче себе представить что такое поток можно представить себе магнитную ленту. Чтобы прочитать что-то с нее мы опускаем считывающую головку и читаем данные определенного объема или пока не дойдем до конца ленты. Тоже самое при записи. Если запись производить несколько раз (блок за блоком), то информация будет ложиться на ленту последовательно блок за блоком. После записи или чтения позиция считывающей головки останется там же где она осталось после предыдущей операции. Вот и все ограничения! Внутри это может быть реализовано совершенно по разному. В перерывах между чтением данных данных хранилище может шириться (например подгружаться из интернета или из диска), можно сделать бесконечную ленту, т.е. при переходе в конец потока подставить ему начало хранилища, можно реализовать принцип конвейера, т.е. хранилище маленькое, но в него постоянно подкладывают в конец (очередь), а забирают всегда поочереди. Это весьма удобный механизм при работе с одиночными объектами, коллекциями, массивами объектов и даже деревьями. Все начинается именно с TStream. В делфи есть много классов его наследников таких как TMemoryStream, TFileStream, TResourceStream, TCompressionStream, TStringStream и другие. Их объединяют механизмы удобной быстрой загрузки, сохранения, добавления и обрезки данных. Но их объединяет не только логика но и реализация, реализация класса TStream. Забегая наперед скажу что сам класс "недореализован", т.е. нельзя создать объект такого класса (так как он абстрактный), но кой чего он умеет. Как это ни странно, но начнем именно с того чего он не умеет. Не умеет он то что касается низкоуровневого доступа к самим данным. Оно и понятно, наследников много всем не угодишь, но при этом он "знает" как это должны делать его наследники. Под знает я имею ввиду что часть его методов виртуальные и даже абстрактные (без реализации). Работает это так. Пусть мы создаем собственный класс. Наш класс занимается обработкой данных. Может получать их из некоторого источника и после обработки сохранять или передавать дальше по цепочке. Однако источников данных может быть много, например из файла, из ресурса, или просто из буфера в памяти. Если решать это задачу в лоб, то нам понадобилось бы создавать 3 разных метода, для получения данных из перечисленных источников. Используя класс TStream, нам досрочного сделать всего один. Часто его объявляют так
Это совсем не значит, что мы ожидаем что нам в функцию передадут объект класса TStream (Это невозможно! Такой класс создать нельзя в принципе). Вместо TStream, нам подойдет любой полноценный класс где реализованы все возможности ввода и вывода (например TFileStream, TResourceStream и т.д.). Это возможно потому, что все эти классы являются наследниками TStream. А мы у себя считая что работаем TStream, на самом деле будем вызывать методы того класса который в действительности был создан (это то что я имел ввиду когда писал, что TStream "знает" как это должны делать его наследники). Это и есть полиморфизм на практике т.е. наследник и предок реагируют похоже на одинаковые события. Чтобы такое было возможно метод должен объявлен как виртуальный или абстрактный. Если метод был объявлен как виртуальный, то производимое методом действие будет заменено действием того объекта, который был реально создан, если же он объявлен как абстрактный, то изначально предок ничего и не делал, но по его названию можно догадаться что бы он хотел сделать. Мы уже говорили, что механизм виртуальных методов позволяет подменять действие, но бывает так что класс предназначается только для подмены своего наследника (именно такой TStream), тогда метод предка никогда не будет вызываться, вот как раз в таком случае виртуальные методы делают абстрактными (т.е. без своей реализации). После прочтения предыдущего абзаца у вас уже достачно знаний, чтобы разобрать структуру класса
Начнем анализ класса с методов Read и Write. Они абстрактные, значит реализованы в наследнике. Служат они для чтения данных из текущей позиции потока наследника TStream и записи в него соответственно. Первый параметр это сам буфер данных (внимание не указатель, а сама переменная буфера!). Второй размер буфера в байтах. Результат число переданных данных. Это базовые методы ввода/вывода. На них построена вся передача данных. Их удобство заключается в том, что при правильном использовании вы никогда не прочитаете данных больше чем вам может предоставит источник и не запишите в никуда в случае если приемник данных не готов в данный момент их принять. Если что-то не так то результат функция всегда вернет нуль или значение отличное от того что вы ей передали в поле Count. На этом удобства не заканчиваются. Если вы уже умеете использовать такой гибкий механизм как исключения, то вы должны оценить методы ReadBuffer, WriteBuffer, они вызывают Read и Write для передачи данных, но в случае неудачи вызывают исключения EReadError и EWriteError соответственно. Это нам дает возможность грамотно выйти из непредвиденной ситуации и объяснить пользователю, почему его действия не увенчались успехом. Пример: Создаем пустой текстовый файл на диске C: с именем "1.txt".
Под отладкой все равно вылетают исключения, но так бывает только под отладкой... Кто повнимательнее заметил заметил что открытие файла тоже в защищенной секции. Что поделаешь, отстуствие файла это тоже ошибка, но тут я ее не стал отдельно отрабатывать, так как для исследуемой функции LoadFromStream не имеет значения к какому из потоков принадлежит переданный ей объект. Уточню, что методы ReadBuffer и WriteBuffer определенны уже в TStream. Т.е. если вы вдруг захотите написать своего наследника TStream, для своего специфического источника данных, то вам не нужно будет их переопределять самому(ой). Говоря об операциях чтения и записи следует уточнить, что не все наследники TStream позволят вам писать/читать данные одновременно. Например, в ресурс нельзя ничего писать, а из файла открытого для записи бесполезно читать. Это значит что если класс имеет метод WriteToStream, то передавать ему объект класса TResourceStream будет неправильно. Метод Seek устанавливает позицию чтения/записи в потоке, хотя он и реализован в TStream, но его ОБЯЗАНЫ перекрывать все наследники. Он существует в 2х вариациях
Перекрыть нужно хотя бы один. Если один из них не будет реализован, то при необходимости всегда будет вызван другой. Если не будет ни одного, то при попытке изменить позицию чтения произойдет исключение. Метод Seek позволяет производить установку позиции от начала потока (параметр Origin = soBeginning), от текущей позиции (Origin = soCurrent), или от конца(Origin = soEnd). Для получения позиции от начала хранилища удобно пользовать свойством Position. Еще один метод, который часто нужно перекрывать в наследнике это SetSize
Он хоть и не абстрактный но пустой. Его сделали пустым на случай если вдруг ваш поток не поддерживает операции записи (это допустимо). В этом случае, было бы нелогичным требовать от наследника TStream реализовывать возможность изменения размера его внутреннего хранилища данных, тогда как он не предусматривает такую операцию. В простых случаях линейных хранилищ Функцию GetSize можно не переопределять. Она самостоятельно использует метод Seek для получения размеров хранилища. При использовании объектов класса следует использовать публичное свойство Size. Теперь о реализованных высокоуровневых возможностях. Копирование больших фрагментов (более 100кб)
Замечательная функция, она нам позволит скопировать большой блок данных из чужого потока, начиная с текущей позиции в потоке Source, при этом начнет писать данные, начиная с текущей позиции в нашем потоке. Для копирования будет выделен внутренний буфер размером в 60 кб, позволяющий сравнительно большими блоками копировать данные, при этом не занимая надолго сам источник данных (источник может оказаться неспособным к работе с несколькими процессами). И еще если в качестве Count указать 0, то функция поймет что нужно скопировать весь поток от начала (Внимание! Не от текущей позиции) до конца. Весьма удобная функция, при перекачке данных из медленного источника в быстрый или из ReadOnly потока в поток доступный для записи. Сериализация объектов. Сериализация это перевод структур данных (в данном случае классов наследников TComponent) в бинарное представление. В таком виде данные объектов можно передать из программы или сохранить на диске. Тут же уточняю сохраняться лишь published свойства. Потому если вы хотите использовать такой механизм в своей программе, то в published секцию следует перенести все свойства определяющие состояние объекта.
Можно записывать несколько объектов подряд, сохранять картинки, тесктовые файлы и много чего другого. Кстати, таким же образом создается любая форма, только источником для нее становиться TResourceStream. Файл DFM сохраняется в ресурс, на лету конвертируется из текстового ресурса в бинарный и становиться доступным для загрузки функцией ReadComponent. Однако перед использованием этих функций нестандартные компоненты следует зарегистрировать при помощи функции RegisterClass. (Подробный пример см. в справке по Delphi "TStream.ReadComponent Method") Дополнительные возможности. Реализация интерфейса IStream. Связь с COM. Наверное некоторые уже заметили созвучность имени класса TStream и COM интерфейса IStream. IStream - это универсальный интерфейс приема-передачи данных в Windows. Если бы нам удалось подружить TStream и IStream, то задачу передачи данных между двумя сторонними объектами можно было бы считать решенной. Например, передать картинку из потока TFileStream в объект класса Bitmap библиотеки GDI+. Аналогичная задача стоит при обмене данными между приложением и своей собственной Dll. Как известно в Dll нельзя передавать объекты классов, а интерфейсы очень даже можно, при этом идеально сохраняется принцип инкапсуляции класса и объектный подход к разарботке приложений. Ближе к делу. Для того чтобы получить интерфейс IStream имея объект наследник TStream необходимо дополнительно воспользоваться классом TStreamAdapter, который и реализует IStream, но не сам, а с помощью класса наследника TStream. Вот его кноструктор.
Видно, что при создании объект должен принять экземпляр класса наследника TStream, но кроме того у него есть еще такой замечательный параметр Ownership, который указывает нужно ли уничтожать сам объект потока (тот который передали ему в конструкторе Stream: TStream). Напомню, что после того как интерфейс освобождается (переменной интерфейса присваивают nil) и больше не остается ссылок на этот объект (ссылока значит то, что у этого объекта получали интерфейс), сам объект, который релизовывал интерфейс уничтожается. Уничтожается, ну и пусть, но ведь после уничтожения у нас останеться неуничтоженным сам объект потока (наследник TStream, который и реализовывал передачу данных). Уничтожить самостоятельно мы его не можем, ведь мы не знаем, когда клиент прекратит с ним работать. Вот для это и нужно в качестве Ownership передвать значение soOwned, т.е. адптер владеет экземпляром потока и удалит его, перед тем как самоуничтожиться. Звучит это все страшно, а используется очень просто. Вот пример.
Все теперь передаем интерфейс strm в Dll ил вообще куда угодно и не думаем о уничтожении ни TStreamAdapter ни TMemorySteam, в нужное время они сами уничтожаться, тогда когда клиент закончит с ними работать. Теперь рассмотрим случай, когда программа на делфи является не сервером, а клиентов, т.е. ей передают интерфейс IStream, объекта потока созданного неизвестно где. Идеально было бы если бы мы получили не IStream, а TStream, ведь все объекты VCL умеют работать именно с TStream, а работать с IStream они не умеют. Решение в лоб состоит в том, чтобы банально скопировать все данные из IStream например в TMemorySteam. Однако, таким образом мы получаем лишнее копирование. Это ничего, когда данных на 20кб, другое дело если их 1Гб, тогда дополнительное копирование выйдет боком. Для таких случаев предлагаю написать свой собственный адаптер, но уже не адаптер сервера, а клиента. Класс назвал TExternStream (от слова External т.е. внешний). Заодно это будет пример написания своего класса наследника TStream. Основная задача этого класса переадресовать все запросы интерфейсу внешнего объекта IStream. Сам интерфейс нужно передать в конструктор. В деструкторе интерфейс освобождается FSource := nil; в классе перекрыты все абстрактные методы Read, Write, виртуальный Seek, GetSize и SetSize.
Используется все это счастье следующим образм. Пусть в клиенсткую часть передали интерфейс strm : IStream, тогда создаем свой экземпляр класса TExternStream на базе этого strm и используем как любой другой объект наследник TStream. Напрмер вызываем Image1.Picture.Bitmap.LoadFromStream(aOut);
Чистого кода! -------------------- Vit вечная память. Обсуждение действий администрации форума производятся только в этом форуме гениальность идеи состоит в том, что ее невозможно придумать |
||||||||||||||||||||||
|
|||||||||||||||||||||||
THandle |
|
|||
![]() Хранитель Клуба ![]() Награды: 1 Профиль Группа: Админ Сообщений: 3639 Регистрация: 31.7.2007 Где: Moscow, Dubai Репутация: 65 Всего: 372 |
Alexeis, хорошая статья
![]() |
|||
|
||||
EvilsInterrupt |
|
|||
Executables research ![]() ![]() ![]() Профиль Группа: Завсегдатай Сообщений: 1019 Регистрация: 14.7.2007 Где: Железнодорожный, МО, Россия Репутация: 3 Всего: 9 |
Спасибо автору! Столько всего открыл для себя, что просто жесть. Но у меня закралось сомнение в необходимости написания TExternalStream, может все-таки разработчики VCL предусмотрели и этот случай ?
|
|||
|
||||
![]() ![]() ![]() |
Правила форума "Delphi: Общие вопросы" | |
|
Запрещается! 1. Публиковать ссылки на вскрытые компоненты 2. Обсуждать взлом компонентов и делиться вскрытыми компонентами
Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader. |
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей) | |
0 Пользователей: | |
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема » |
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |