Помощничек
Главная | Обратная связь


Археология
Архитектура
Астрономия
Аудит
Биология
Ботаника
Бухгалтерский учёт
Войное дело
Генетика
География
Геология
Дизайн
Искусство
История
Кино
Кулинария
Культура
Литература
Математика
Медицина
Металлургия
Мифология
Музыка
Психология
Религия
Спорт
Строительство
Техника
Транспорт
Туризм
Усадьба
Физика
Фотография
Химия
Экология
Электричество
Электроника
Энергетика

Общие принципы работыс потоками данных

Потоки данных. Класс TStream

Общие сведения

При разработке приложений часто приходится решать задачи обмена данными между приложениями или приложением и устройством ввода/вывода. При этом большая часть созданного кода обеспечивает работу приложения с файлами.

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

В Delphi имеются два вида потоков: потоки данных и потоки команд (нити). При изучении данной темы под потоком будем подразумевать поток данных.

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

Потоки в Delphi – это обобщенная модель двоичных данных, размещенных на устройствах-накопителях, таких как диски, оперативная память и т.п. и представляющие собой специальные объекты, являющиеся наследниками абстрактного класса TStream .

Поток данных представляет собой структуру (объект) с последовательным доступом. Потоки данных являются стандартом для обмена данными в Delphi, унифицируя операции ввода/вывода для различных носителей. Всюду, где необходимо принимать или отправлять нетипизированный набор данных, предпочтительнее использовать потоки данных. Многие механизмы Delphi по умолчанию умеют работать именно с потоками данных, предоставляя методы вроде LoadFromStreamи SaveToStream(и иногда предоставляя к ним обёртки-переходники вродеLoadFromFileиSaveToFile).

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

В Delphi имеется широкий набор классов, предназначенных для работы с любыми ресурсами – от файла на диске до блока памяти. Каждый класс-наследник реализует базовые методы TStream по-своему. Например, при чтении из потока данных наследник для работы с файлами вызовет функцию чтения данных с диска, а наследник для работы с блоком памяти использует процедуру Move для копирования данных из памяти.

Приведем неполный список классов-наследниковTStream:

· TFileStream (для работы с файлами)

· TResourceStream (для работы с ресурсами программы)

· TStringStream (для работы со строками)

· TMemoryStream (для работы с буфером в памяти)

· TBlobStream (для работы с BLOB полями)

· TWinSocke TStream (для работы с сетевым сокетом).

Пример, демонстрирующий необходимость абстрактного класса и классов-наследников.

Предположим, требуется уметь загружать растровые изображения (bitmap). При этом рисунок может находиться не только в файле, он может быть и в ресурсах программы и в памяти. Без использования потоков данных пришлось бы писать же три разных метода, которые делают одно и то же.Для этого нужен абстрактный класс, который объявляет общий подход, которому обязуются следовать все его наследники.Следовательноможно написать один разтакой код, который грузит рисунок из потока TStream .А вызывающий класс подставит TFileStreamдля загрузки рисунка из файла,TResourceStreamдля загрузки из ресурса, иTMemoryStreamдля загрузки из памяти. В определённом смысле все эти классы-наследники представляют собой простые переходники от общей спецификации, определённойTStream,до конкретного метода доступа: файл, ресурс, память, сеть и так далее; и наоборот. Данный способ взаимодействия классаTStreamи его потомков является полиморфизмом классов в Delphi.

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

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

Общие принципы работыс потоками данных

Позиционирование

Любой поток обладает двумя ключевыми свойствами – размером в байтах (свойство Size) и текущей позицией (Position).

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

Свойство Position указывает текущую позицию в потоке данных, при этом нумерация начинается с 0, т.е 0 соответствует началу, а значение, равное Size– концу потока. Можно читать свойство Position, чтобы узнать текущую позицию, и записывать значение вPosition, чтобы изменить текущую позицию (позиционирование поддерживается не всеми видами потоков). К примеру:

Var

Stream: TStream ;

SavedPos: Int64;

Begin

...

SavedPos :=Stream.Position; // Сохранить текущую позицию потока

Stream.Position := 0; // Перейти в начало потокаданных

//...

Stream.Position := SavedPos; // Возвращение в исходное местоположение

...

Stream.Position := Stream.Size; // Переходвконецпотока

...

end;

Таким образом, свойство Positionпозволяет установить абсолютную позицию в потоке.

Дляотносительного изменения текущей позиции в потоке используется метод Seek. Метод имеет два параметра: позицию и точку отсчёта. Точка отсчета позиции зависит от значения параметра Origin:

· soBeginning (устаревшая константа soFromBeginning) – смещение должно быть положительным и отсчитывается от начала потока;

· soCurrent (устаревшая константа soFromCurrent) – смещение относительно текущей позиции в потоке;

· soEnd (устаревшая константа soFromEnd) – смещение должно быть отрицательным и отсчитывается от конца потока.

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

Пример.

Var

Stream: TStream ;

SavedPos: Int64;

Begin

...

SavedPos := Stream.Seek (0, soBeginning); // Переместиться в начало потока, одновременно сохраняя (старую) текущую позицию потока

//...

Stream.Seek (SavedPos, soBeginning); // Возвращение в исходную позицию потока

...

Stream.Seek (0, soEnd); // Перемещение в конец потока

...

end;

Чтение/запись

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

Для чтения классTStreamпредлагает методы Read и ReadBuffer,

а для записи – методы Write и WriteBuffer.

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

procedure TForm1.Button1Click (Sender: TObject); var Buffer: PChar; Stream: TFileStream ; Size:LongInt; begin Stream := TFileStream.Create ('e:\1.txt', fmOpenRead); try Size := Stream.Size; GetMem (Buffer, Size); try Stream.Read (Buffer[0], Size); Memo1.Lines.Text := Buffer; finally FreeMem (Buffer); end; finally Stream.Free; end; end;   procedure TForm1.Button1Click (Sender: TObject); var Buffer: string; Stream: TFileStream ; begin Stream := TFileStream .Create ('e:\1.txt', fmOpenRead); try SetLength (buffer, Stream.Size); Stream.Read (Buffer[1], Stream.Size); Memo2.Lines.Text := Buffer; finally Stream.Free; end; end;

 

Разница между методами с "Buffer" и без него заключается в том, что методы с суффиксом "Buffer" гарантируют выполнение операции до конца. Если же это невозможно (к примеру, в файле 12 байт, а вы командуете прочитать 24 байта), то будет возбуждено исключение.

А вот методы без суффикса "Buffer" допускают частичное выполнение операции. Они никогда не возбуждают ошибку, а вместо этого возвращают, сколько реально байт было прочитано или записано. Иногда, это может быть и 0 байт. К примеру, если в файле 12 байт, а при вызове метода Read, указывается 24 байта, то метод Read прочитает 12 байт и вернёт число 12 (это метод-функция). Ещё пример: если поток связан с сетевым сокетом и происходит вызов Read в момент, пока никаких данных ещё не пришло: метод завершится тут же, возвращая 0.

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

PIPE (Personal Internet Phone Equipment) – приложение для голосового общения в режиме реального времени при помощи двух компьютеров и IP сети. PIPE звонит не на телефон, а на компьютер (если на нем тоже есть PIPE).

Разумеется, некоторые из наследников TStream могут вводить свои специальные методы для чтения/записи.

Копирование

УTStreamесть ещё один метод CopyFrom, который позволяет скопировать указанное количество данных (в байтах) из указанного массива. Копирование производится с текущей позиции. Метод работает аналогично методу записиWriteBuffer, сдвигая текущую позицию на указанное количество байт и возбуждая исключение при ошибках. Использование CopyFromпозволяет избежать создания буфера, чтения в него данных из исходного потока, запись буфера в выходной поток и удаления буфера – все эти действия выполняются автоматически внутри методаCopyFrom.

Procedure CopyFile (const ASourceFileName, ATargetFileName: String);

Var

Source: TFileStream ;

Dest: TFileStream ;

Begin

// Открытие исходного файла для чтения

Source := TFileStream .Create (ASourceFileName, fmOpenRead or fmShareDenyWrite);

Try

// Создание целевого файла (в режиме записи)

Dest := TFileStream .Create (ATargetFileName, fmCreate or fmShareExclusive);

Try

// Копирование данных из потока в поток

Dest.CopyFrom (Source, Source.Size);

Finally

FreeAndNil (Dest);

end;

Finally

FreeAndNil (Source);

end;

end;

УCopyFromесть специальный случай: если последний параметр (размер) равен 0, тоCopyFromскопирует весь поток целиком – начиная с начала потока (вне зависимости от текущей позиции) и до конца.

Особенностикласса TStream

Рассмотрим некоторые наследники классаTStream.

Примечание: некоторые рассматриваемые возможностимогут отсутствовать в ранних версиях Delphi.

THandleStream

THandleStream предназначен для работы с файлами в смысле операционной системы, т.е. THandleStreamявляется оболочкой к файлам в стиле ОС. Объект этого типа можно связать с THandle, полученным любым способом - скажем, от CreateFile, CreatePipe и так далее: лишь бы на этот описатель можно было вызывать ReadFileи WriteFile.

Использовать THandleStream необходимов двух случаях:

1. Функция ОС вернула описатель THandle, а код, который необходимо вызвать, требуетTStream. В этом случае нужно создать THandleStream,передав в его конструктор описатель от функции ОС, и передать полученный объект коду.

2. Необходимо использовать возможность, которую предоставляет функция ОС, но не объект Delphi.

Пример.

Var

ReadHandle, WriteHandle: THandle;

ReadStream, WriteStream: TStream ;

Begin

Win32Check (CreatePipe (@ReadHandle, @WriteHandle, nil, 0));

Try

ReadStream :=THandleStream.Create (ReadHandle);

WriteStream :=THandleStream.Create (WriteHandle);

Try

...

Bitmap.SaveToStream (WriteStream); // Отправка растрового изображения по pipe

...

Finally

FreeAndNil (WriteStream);

FreeAndNil (ReadStream);

end;

Finally

CloseHandle (WriteHandle);

CloseHandle (ReadHandle);

end;

end;

Сам THandleStreamне имеет ограничений и поддерживает все операции: чтение, запись, позиционирование и изменение размера. Однако нижележащий дескриптор объекта ядра может поддерживать не все операции. Например,описатель от файла на диске может быть открыт только для чтения, а описатель pipe не поддерживает позиционирование.

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

Примечание. Сам THandleStreamникогда не закрывает описатель (ему нельзя передать ответственность за него). Поэтому программист должен закрывать описатель вручную или использоватьTFileStream.

TFileStream

TFileStreamпредназначен для работы с файлами на диске.TFileStreamнаследуется от THandleStream,так что он получает все возможности своего предка.TFileStreamне имеет ограничений на операции и поддерживает конструктор с передачей описателя.

Пример.

Var

FileHandle: THandle;

Stream: TFileStream ;

Begin

FileHandle := CreateFile ('...\Temp.tmp', GENERIC_READ or GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY or FILE_FLAG_DELETE_ON_CLOSE, 0);

Win32Check (FileHandle<> INVALID_FILE_HANDLE);

Stream := TFileStream .Create (FileHandle);

Try

...

Finally

FreeAndNil (Stream);

// <- ненужноделатьCloseHandle (FileHandle);

end;

end;

В отличие от THandleStream,TFileStreamзакрывает открытый описатель файла, вне зависимости от способа его инициализации.

Кроме того,TFileStreamподдерживает перегруженный конструктор, позволяющий открывать файлы. У него есть два параметра: (имя файла) и (режим открытия + режим разделения).

Допустимые режимы открытия:

· fmCreate –создаёт новый файл. Если такой файл уже есть, он удаляется перед созданием. Файл открывается в режиме записи.

· fmOpenRead –открывает файл только для чтения.

· fmOpenWrite –открывает файл только для записи.

· fmOpenReadWrite- открывает файл для чтения-записи.

Допустимые режимы разделения:

· fmShareExclusive –запретить совместное использование.

· fmShareDenyWrite –запретить другим приложениям запись.

· fmShareDenyRead –запретить другим приложениям чтение.

· fmShareDenyNone– не вводить ограничения.

Режимы следует соединять друг с другом операцией or.

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

Флаги разделения используют устаревшую deny-семантику MS-DOS, в отличие от современного API. См. также:взаимодействие флагов режима открытия и разделения.

Типичный пример открытия файла выглядит так:

TFileStream .Create ('MyFile.dat', fmOpenRead or fmShareDenyWrite);

А создания файла – так:

TFileStream .Create ('MyFile.dat', fmCreate or fmShareExclusive);

При работе с файлами типа логов (для которых необходимы совместное использование или мониторинг) могут использоваться такие вызовы:

TFileStream .Create ('MyFile.log', fmOpenRead or fmShareDenyNone);

TFileStream .Create ('MyFile.log', fmCreate or fmShareDenyNone);

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

Если объектTFileStreamсоздавался через конструктор с именем файла, то это имя файла доступно в свойстве FileName, иначе доступно только свойство Handle.

TMemoryStream

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

ИспользуйтеTMemoryStream,если нужен "просто поток" или нужен промежуточный буфер-поток.

TMemoryStreamимеет конструктор без параметров, который создаёт пустой объект.

Для заполнения потока после создания можно использовать обычные методы записи или же специальные методы LoadFromStream (аналог вызоваCopyFrom(Stream, 0)) иLoadFromFile.

Также есть методы SaveToStream иSaveToFile.

TMemoryStreamне имеет ограничений и поддерживает все операции, включая изменение размера.

TMemoryStreamхранит данные в динамической куче процесса, выделяя память по мере необходимости. Он сам автоматически управляет памятью. Реально памяти может быть выделено больше, чем лежит данных в потоке - т.н. "capacity>size". Это стандартная оптимизация для "побайтовых записей".

Дополнительной возможностьюTMemoryStreamявляется предоставление свойства Memory, позволяющего обратиться к данным потока напрямую, через указатель, минуя последовательные методы чтения/записи. Поэтому можно рассматриватьTMemoryStreamкак "переходник" междуTStreamи нетипизированным указателем.

Простейший пример использованияTMemoryStream(в данном случае - для конвертации строки вTStream):

Procedure LoadFromText (constAHandle: THandle; constAText: AnsiString);

Var

Data: TMemoryStream ;

Begin

Data := TMemoryStream .Create;

Try

Data.Write (PAnsiChar (AText)^, Length (AText));

Data.Position := 0;

LoadFromStream (AHandle, Data);

Finally

FreeAndNil (Data);

end;

end;

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

TResourceStream

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

TResourceStreamпохож наTMemoryStream . Он тоже работает с памятью программы (только не с кучей, а с ресурсами), он поддерживает методы SaveToStream иSaveToFile, а также свойство Memory.

Но в отличие отTMemoryStream, TResourceStreamподдерживает только методы чтения, но не записи, а также не поддерживает изменение размера. Иными словами,TResourceStream– это класс read-only.

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

Несколько примеров:

// Пример использования метода SaveToFile

 

// Извлечение своего ресурса в отдельный файл:

ProcedureExtractRes (constResType: PChar; constResName, ResNewFileName: String);

Var

Res: TResourceStream ;

Begin

Res := TResourceStream .Create (HInstance, ResName, ResType);

Try

Res. SaveToFile (ResNewFileName);

Finally

FreeAndNil (Res);

end;

end;

 

...

 

ExtractRes ('BINFILE', 'MYFILE', 'C:\MyFile.bin');

ExtractRes (RT_RCDATA, 'MYDATA', 'C:\MyData.bin');

 

// Примериспользования свойства Memory

 

// Проигрывание MP3 прямо из ресурса, не выгружая его в файл (с использованием BASS)

Var

Res: TResourceStream ;

BkMusic: HSAMPLE;

...

// Создали обёртку TStream

Res := TResourceStream .Create (HInstance, 'BKMUSIC', RT_RCDATA);

// Загружаем напрямую из памяти – нет копирования данных

BkMusic := BASS_SampleLoad (True, Res.Memory, 0, Res.Size, 1, BASS_SAMPLE_LOOP);

// Начинаем!!!

ifBkMusic<> 0

Then

BASS_ChannelPlay (BkMusic, False);

...

ifBkMusic<> 0

Then

Begin

BASS_ChannelStop (BkMusic);

BASS_SampleFree (BkMusic);

BkMusic := 0;

end;

FreeAndNil (Res);

...

TBytesStream

TBytesStream хранит данные потока в массиве байтов.

Используйте TBytesStream как переходник междуTBytes и TStream .

Собственно, TBytesStream аналогичен TMemoryStream, только вместо блока памяти в куче он использует TBytes в качестве хранилища для данных. Он также не имеет ограничений на операции, поддерживая чтение, запись, позиционирование и изменение размера. Он поддерживает методы LoadFromStream, LoadFromFile, SaveToStream и SaveToFile , а также свойство Memory.

В отличие от TMemoryStream, уTBytesStream есть конструктор, который принимает переменную типа TBytes – это будут начальные данные потока. При этом не производится копирование данных (используется счётчик ссылок динамического массива). Все операции чтения-записи будут оперировать с исходными данными в оригинальной переменной типа TBytes. Однако если изменить размер потока (либо явно через Size/SetSize, либо неявно через запись данных в конец потока данных), то поток сделает копию данных и будет работать уже с ней. При этом все будущие изменения в потоке не затронут оригинальной переменной типа TBytes.

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

Дополнительно TBytesStream вводит свойство Bytes. Оно работает аналогично свойству Memory, только имеет тип TBytes, а не Pointer.

Предупреждение: не пытайтесь использовать Length для определения размера данных. Размер хранилища может быть больше актуального размера ("Capacity>Size"). Используйте свойство Size для определения размера данных.

Простой пример использования TBytesStream как переходника (обратите внимание на усечение данных до их актуального размера, указанного в свойстве Size):

function DecodeBase64 (const Input: AnsiString): TBytes;

var

InStr: TPointerStream;

OutStr: TBytesStream;

Len: Integer;

begin

InStr := TPointerStream.Create (PAnsiChar (Input), Length (Input));

try

OutStr := TBytesStream.Create;

try

DecodeStream (InStr, OutStr);

Result := OutStr.Bytes;

Len := OutStr.Size;

finally

FreeAndNil (OutStr);

end;

finally

FreeAndNil (InStr);

end;

SetLength (Result, Len);

end;

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

TStringStream

Класс TStringStream предоставляет последовательный доступ к информации, хранящейся в обычной строке.

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

По сути, TStringStreamявляется обёрткой к TBytesStream, которая просто конвертирует строку в байты и обратно. У TStringStreamесть несколько вариантов конструкторов, которые инициализируют поток по разным типам строк. В Unicode версиях Delphi конструкторы также позволяют вам указывать кодировку для ANSI строк.

Методы чтения-записи TStringStreamне затрагивают исходную строку, а всегда работают с копией данных (внутреннее хранилище в виде TBytes).

Ну и, конечно же, TStringStreamпредоставляет строко-ориентированные свойства и методы. Во-первых, это методы WriteString и ReadString, которые пишут и читают данные из потока в виде строки. При этом кодировка (в Unicode-ных версиях Delphi) контролируется свойством Encoding. И, равно как и предыдущие классы, TStringStreamвыставляет наружу хранилище в "родном" формате: DataString.

Простой пример использования TStringStreamкак переходника между строками иTStream:

Function EncodeString (const Input: String): String;

Var

InStr, OutStr: TStringStream;

Begin

InStr :=TStringStream.Create (Input); // <- использует текущую кодовую страницу ANSI

// Можнобылотак:

// InStr :=TStringStream.Create (Input, CP_UTF8); // <- использует UTF-8

// илитак:

// InStr :=TStringStream.Create (Input, TEncoding.Unicode); // <- использует UTF-16

Try

OutStr :=TStringStream.Create (''); //<- аналогичные замечания

Try

EncodeStream (InStr, OutStr); // работает с потоками TStream - т.е. двоичными данными

Result := OutStr.DataString;

Finally

FreeAndNil (OutStr);

end;

Finally

FreeAndNil (InStr);

end;

end;

Заметьте, что, несмотря на наличие методов чтения строк и загрузки/сохранения данных из/в файлы, TStringStreamне пригоден для работы с текстовыми файлами. Он не работает с BOM и не позволяет прочитать одну строку (в смысле line) от разделителя до разделителя (он читает только указанное количество символов). По этой причине для работы с текстовыми файлами используют вспомогательный класс – TstringList.

Пример. Шифрование/дешифрование текстового файла, использующие оба этих класса:

Procedure EncodeTextFile (constASourceFileName, ADestFileName: String);

Var

SourceFile: TStringList;

SourceData:TStringStream;

DestFile: TFileStream ;

Begin

SourceData := nil;

Try

// Загрузка данных

SourceFile := TStringList.Create;

Try

// "Правильная" загрузка текстового файла с учётом BOM и кодировок

SourceFile. LoadFromFile (ASourceFileName);

 

// Конвертируем строку в TStream

SourceData :=TStringStream.Create (SourceFile.Text, TEncoding.Unicode);

Finally

FreeAndNil (SourceFile);

end;

 

DestFile := TFileStream .Create (ADestFileName, fmCreate or fmShareExclusive);

Try

EncodeStream (SourceData, DestFile); // двоичное шифрование

Finally

FreeAndNil (DestFile);

end;

Finally

FreeAndNil (SourceData);

end;

end;

 

procedureDecodeToTextFile (constASourceFileName, ADestFileName:String);

Var

SourceFile: TFileStream ;

DestData:TStringStream;

DestFile: TStringList;

Begin

SourceFile := TFileStream.Create (ASourceFileName, fmOpenRead or fmShareDenyWrite);

Try

DestData :=TStringStream.Create ('', TEncoding.Unicode);

Try

DecodeStream (SourceFile, DestData); // двоичное дешифрование

 

DestFile := TStringList.Create;

Try

// Конвертируем TStream в строку

DestFile.Text := DestData.DataString;

 

// "Правильное" сохранение текстового файла с BOM (используем UTF-8)

DestFile. SaveToFile (ADestFileName, TEncoding.UTF8);

Finally

FreeAndNil (DestFile);

end;

Finally

FreeAndNil (DestData);

end;

Finally

FreeAndNil (SourceFile);

end;

end;

Конечно, на практике такой пример не имеет большого смысла, потому что гораздо проще просто работать с текстовым файлом как с двоичным - обработав его через TFileStream . Но код прекрасно показывает пример соединения трёх классов для работы.

TStream Adapter

Понятие "потока данных" есть не только в Delphi, но и практически в любом другом современном языке. Разумеется, другие языки понятия не имеют, как работать с объектами Delphi, и наоборот: Delphi не знает, как устроены классы и объекты в других языках. К счастью, под Windows у нас есть COM и интерфейсы. С ними умеют работать почти все языки, так что это является де-факто стандартом межязыкового взаимодействия. И, конечно же, не могло быть иначе: для такой популярной концепции как "поток данных" существует свой интерфейс - IStream.

Иными словами, если вам нужно передать куда-то поток данных - вы используете IStream. Если вам кто-то передаёт поток данных, то это будет IStream.

Тут возникает маленькая проблемка: ваши Delphi объекты вообще-то не умеют работать с интерфейсомIStream: они работают с классом TStream .Что же делать?

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

TStream Adapte rпредоставляет переходник отTStreamк IStream. Он принимает в конструкторе экземплярTStreamи выставляет наружу IStream, который вы можете передать в чужой код.

TStream Adapterподдерживает те же операции, что и оригинальный поток, который в него завёрнут.

TStream Adapterможет взять на себе ответственность за удаление исходного потока данных, а может оставить её вам. Вот два примера использованияTStream Adapter, иллюстрирующие оба подхода:

var Stream: TMemoryStream ; COMStream: IStream; begin // Готовим поток-источник: это TStream, который приходит от нашего Delphi-кода Stream := TMemoryStream .Create; try Bitmap.SaveToStream (Stream); Stream.Position := 0;   // Создаём адаптер. Исходный поток мы должны удалять сами COMStream := TStream Adapter.Create (Stream, soReference);   // Загрузкарастрав Windows Imaging Component FImagingFactory.CreateDecoderFromStream (COMStream, guid_null, WICDecodeMetadataCacheOnDemand, BitmapDecoder)); ... finally COMStream := nil; FreeAndNil (Stream); end; end;
var Stream: TFileStream ; COMStream: IStream; begin // Готовим поток-источник: это TStream, который приходит от нашего Delphi-кода Stream := TFileStream .Create ('My.bmp', fmOpenRead or fmShareDenyWrite);   // Создаём адаптер. Адаптер удаляет поток, мы не должны его удалять COMStream := TStream Adapter.Create (Stream, soOwned);   // Загрузкарастрав Windows Imaging Component FImagingFactory.CreateDecoderFromStream (COMStream, guid_null, WICDecodeMetadataCacheOnDemand, BitmapDecoder)); ... end;

TOleStream

TOleStream представляет собой обратный класс кTStream Adapter: переходник от IStreamкTStream .

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

TOleStreamподдерживает те же операции, что и оригинальный поток, который в него завёрнут.

Пример использования TOleStream:

// Открывает файл из композитного OLE-хранилища function StreamFileRead (constAPath:String): TStream ; var Strm: IStream; StorageToOpen: IStorage; FileToOpen: WideString; begin // Пропущены проверки и подготовка ...   // ПолучаемфайлввидеIStream IfFailed (StorageToOpen.OpenStream (PWideChar (FileToOpen), nil, STGM_READ or STGM_SHARE_EXCLUSIVE, 0, Strm)) then begin Result := nil; Exit; end;   // Конвертируем IStream в TStream .Оригинальный поток (Strm) удалится сам, когда надо Result := TOleStream.Create (Strm); Result.Position := 0; end;

 

 




Поиск по сайту:

©2015-2020 studopedya.ru Все права принадлежат авторам размещенных материалов.