20.4. Работа с потоком Турбо Паскаль В.В. Фаронов

Базовый объект TStream реализует три метода, используемых для непосредственной работы с потоком. Метод TStream.Put предназначен для передачи объектов в поток и выполняет приблизительно такие же функции, как стандартная файловая процедура Write. Метод TStream.Get используется для чтения объектов из потока, его аналогом является процедура Read. Наконец, с помощью метода TStream.Error анализируется состояние потока после завершения некоторой операции: если обнаружена ошибка при обмене данными с потоком, вызывается этот метод, который по умолчанию просто устанавливает признаки ошибки в информационных полях TStream.Status и TStream.Errorlnfo. Приблизительным аналогом метода TStream.Error служит стандартная файловая функция IOResult.
Сразу же замечу, что в случае возникновения ошибки все последующие операции с потоком блокируются до тех пор, пока не будет вызван метод TStream.Reset.
Методы Put и Get практически никогда не перекрываются: для реализации операций с потоком они обращаются к виртуальным методам Store и Load, которые должны быть определены в каждом объекте, если только этот объект помещается в поток или считывается из него. Главное назначение методов Put и Get состоит в обеспечении полиморфизма потока за счет контроля регистрационных номеров объектов. Методы Load и Store никогда не вызываются прямо, но только из методов Put и Get, т.к. они ничего не знают о регистрационных номерах и не могут работать в полиморфных потоках.
20.4.1. Методы Put и Get
Чтобы поместить объект в поток, нужно обратиться к методу Put, передав ему в качестве параметра инициированный экземпляр объекта. Например:
var
MyStream: TBufStream;{Экземпляр потока}
MyWindow: TMyWindow;{Экземпляр объекта}
.....
MyStream.Put(MyWindow);{Помещаем объект в поток}
Предварительно объект должен быть зарегистрирован обращением к RegisterType, а поток - инициирован с помощью TXXXStream.Init.
Метод Put вначале отыскивает объект в регистрационном списке, создаваемом процедурой RegisterType, и получает из этого списка регистрационный номер объекта и адрес его метода Store. Затем в поток записывается регистрационный номер и вызывается метод Store, который делает остальное, т.е. копирует в поток все поля объекта.
По такой же схеме работает и метод Get: вначале он считывает из потока регистрационный номер объекта, затем отыскивает его в регистрационном списке и вызывает соответствующий конструктор Load. Конструктор размещает в динамической памяти экземпляр считываемого объекта, а затем считывает из потока все его поля. Результатом работы Get является нетипизированный указатель на вновь созданный и инициированный объект. Например:
type
MyStream: TBufStream;{Экземпляр потока}
PWindow: PMyWindow;{Указатель на экземпляр объекта}
.....
PWindow := MyStream.Get;{Получаем объект из потока}
Заметим, что количество считываемых из потока данных и тип ТВМ, который назначен вновь созданному объекту, определяется не типом PWindow (см. выше), а регистрационным номером, полученным из потока. Вы можете ошибочно поместить в левой части оператора присваивания указатель на объект другого типа и Turbo Vision не сможет предупредить Вас об этом!
Методы Put и Get позволяют автоматически сохранять в потоке и получать из него сложные объекты (группы). Эта возможность реализуется внутри методов Store и Load.
20.4.2. Методы Store и Load
Метод Store осуществляет запись данных в поток. Для этого он использует метод низкого уровня Write, передавая ему в качестве параметров имя записываемого поля и длину поля в байтах. Заметим, что Вам нет нужды записывать все поля объекта: для записи наследуемых полей достаточно просто обратиться к методу Store объекта-родителя. Ваш метод Store должен записывать только те поля, которые добавляются к полям родителя. Если, например, создан объект
type
TMyDialog = object (TDialog)
St: String;{Новое поле}
Procedure Store(var S: TStream); Virtual;
.....
end ;
то метод TMyDialog.Store может иметь такую реализацию:
Procedure TMyDialog.Store(var S: TStream);
begin
TDialog.Store(S); {Сохраняем наследуемые поля}
SA.Write(St, SizeOf(St)); {Сохраняем новое поле}
end;
Аналогичным образом реализуется и конструктор Load: с помощью обращения к низкоуровневому методу TStream.Read он получает из потока только дополнительные поля и только в том порядке, как они были записаны в поток методом Store:
Constructor TMyDialog.Load(var S: TStream);
begin
TDialog.Load(S); {Получаем наследуемые поля}
S^.Read(St, SizeOf(St)); {Получаем новое поле}
end;
Вы должны тщательно следить за соответствием методов Store и Load: метод Load' должен прочитать ровно столько байт и строго в той последовательности, сколько байт и в какой последовательности поместил в поток метод Store. В Turbo Vision нет средств контроля За правильностью считываемых данных!
Если Ваш объект - группа, следует включить в него поля-указатели на каждый из элементов и использовать методы PutSubViewPtr и GetSubViewPtr соответственно для записи в поток и чтения из него. Например:
type
TMyDialog = object (TDialog)
St: String; {Текстовое поле}
PB: PButton; {Указатель на кнопку}
Procedure Store(var S: TStream); Virtual;
Constructor Load(var S: TStream);
.....
end;
Procedure TMyDialog.Store(var S: TStream);
begin
TDialog.Store(S); {Сохраняем наследуемые поля}
S^.write(ST, SizeOf(St)); {Сохраняем текстовое поле}
PutSubViewPtr(S, PB) ; {Сохраняем кнопку}
end;
Constructor TMyDialog.Load(var S: TStream);
begin
TDialog.Load(S); {Получаем наследуемые поля}
S^.Read(St, SizeOf(St)); {Получаем тестовое поле}
GetSubViewPtr(S, PB); {Получаем кнопку}
end;
20.4.3. Обработка ошибок
При обнаружении ошибки поток вызывает свой метод TStream.Error, который определяет необходимую реакцию программы. По умолчанию этот метод просто записывает информацию об ошибке в поля TStream.Status и TStream.ErrorInfo.
Поле Status определяет тип ошибки, в соответствии со следующими константами модуля Objects:
const
stOk = 0; {Нет ошибки}
stError =-1; {Ошибка доступа}
stInitError =-2; {Ошибка инициации потока}
stReadError =-3; {Чтение за концом потока}
stWriteError =-4; {Нельзя расширить поток}
stGetError =-5; (Get для незарегистрированного объекта}
stPutError =-6; {Put для незарегистрированного объекта}
Поле ErrorInfo определено только для Status - -5 или Status - -6: в первом случае оно содержит регистрационный номер, полученный из потока и не обнаруженный в регистрационном списке; во втором - смещение ТВМ незарегистрированного объекта, который программа пытается поместить в поток.
Сразу после обнаружения ошибки Turbo Vision блокирует все операции с потоком до тех пор, пока аварийная ситуация не будет сброшена обращением к методу TStream.Reset.
20.4.4. Прямой доступ к потокам
Поток имеет методы, имитирующие файловые процедуры прямого доступа к дисковому файлу.
С помощью функции GetPos программа может получить текущую позицию в потоке, т.е. номер байта, начиная с которого будет осуществляться очередная операция с потоком (первый байт потока имеет номер 0).
Метод Seek (Pos: LongInf) перемещает текущую позицию в потоке в байт Pos от начало потока.
Метод GetSize возвращает общий размер потока в байтах.
С помощью метода Truncate можно удалить из потока все данные, начиная с текущей позиции до конца потока.
Как видим, эти процедуры можно использовать только в том случае, если создать вне потока индексную коллекцию, содержащую начальные позиции в потоке для каждого из сохраняемых в нем объектов. Такая коллекция используется в ресурсах, поэтому для обеспечения прямого доступа к потоку лучше использовать файл ресурсов (см. гл.21).
20.4.5. Использование потоков с произвольными данными
Хотя потоки спроектированы в основном для работы с объектами, Вы можете использовать их для хранения не только полей объектов, но и любых других данных. При этом не следует обращаться к методам Put и Get, так как они предполагают доступ к объектам. Вместо этого Вы должны обратиться к низкоуровневым процедурам Write и Read.
Следующая простая программа использует поток для сохранения десяти случайных целых чисел:
Uses Objects; var
S: TBufStream; {Экземпляр потока}
k, j : Integer;
begin
WriteLn('Запись в поток:');
S.lnit('Test.dat', stCreate, 512); {Создаем поток}
for k := 1 to 10 do
begin
j := Random(l00); {Получаем случайное целое}
Write(j:8); {Выводим на экран}
S.Write(j,2) {Помещаем в поток}
end;
Done; {Удаляем поток}
S.lnit('Test.dat', stOpenRead, 512);
WriteLn;
WriteLn('Чтение из потока:');
for k := 1 to 10 do
begin
S.Read(j,2); {Получаем целое из потока}
Write (j:8) {Выводим на экран}
end;
S.Done;
WriteLn
end.
Для простоты в программу не включены средства контроля ошибок. В ходе прогона программы в текущем каталоге диска будет создан файл TEST.DАТ размером в 20 байт, а на экран будут выведены две строки с одинаковыми числами.