ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 23

Глава 21. Потоки
Техника объектно-ориентированного программирования и ObjectWindows дают вам мощные средства объединения кода и данных, и большие возможности построения взаимосвязанных структур объектов. Но что делать, если стоит простая задача, например, по хранению нек
оторых объектов на диске?
Когда-то данные хранились исключительно в записях и помещение данных на диск было тривиальной задачей. Но данные в программах ObjectWindows неразрывно связаны с объектами. Конечно, вы можете отделить данные от объекта и записать их в дисковый файл. Объед
инение дает вам значительный шаг в направлении прогресса, а разъединение отбрасывает вас назад.
Есть ли в самих OOP и ObjectWindows некоторые средства, которые могли бы разрешить эту проблему ? Есть, и это потоки.
Поток в ObjectWindows это набор объектов на их пути куда-либо: обычно в файл, EMS, на последовательный порт или некоторое другое устройство. Потоки обслуживают операции ввода/вывода на уровне объектов, а не на уровне данных. При расширении объекта Object
Windows вам нужно обеспечить обработку определенных вами дополнительных полей. Все сложные аспекты обработки на уровне объектов будут проделаны за вас.
Вопрос: Объект ввода/вывода
Поскольку вы пишете программы на языке Pascal, вы знаете, что до проведения операций ввода/вывода с файлом, вы сначала должны сообщить компилятору, какой тип данных вы будете писать и считывать из файла. Файл должен иметь тип и этот тип должен быть устан
овлен во время компиляции.
Turbo Pascal реализует в этой связи очень удобное правило: можно организовать доступ к файлу неопределенного типа с помощью BlockWrite и BlockRead. Отсутствие проверки типа возлагает некоторую дополнительную ответственность на программиста, хотя позволяе
т очень быстро проводить двоичные операции ввода/вывода.
Вторая проблема состоит в том, что вы не можете непосредственно использовать файлы с объектами. Turbo Pascal не позволяет вам создавать файл с типом объекта. Объекты могут содержать виртуальные методы, адреса которых определяются в процессе исполнения пр
ограммы, поэтому хранение информации VMT вне программы лишено смысла, еще более бессмысленно считывать эту информацию в программу.
Но эту проблему снова можно обойти. Вы можете выделить данные из ваших объектов и записать эту информацию в некоторый сорт файла, а уже позднее восстановить объекты из этих исходных данных. Подобное решение, однако, будет недостаточно элегантным и сущест
венно усложняет конструирование объектов.
Ответ: Поток
ObjectWindows позволяет обойти все эти трудности и даже сулит вам получение некоторых дополнительных выгод. Потоки дают вам простое, но изящное средство хранение данных объекта вне вашей программы. 
Полиморфизм потоков
Потоки ObjectWindow позволяют вам работать с файлами определенного и неопределенного типа: проверка типа имеется, но тип посылаемого объекта не должен быть обязательно определен во время компилирования. Смысл в том, что потоки знают, что они имеют дело с
 объектами, и поскольку все объекты происходят от TObject, поток может их обработать. В действительности различные объекты ObjectWindows могут быть также легко записаны в один поток, как и группы идентичных объектов.
Потоки обрабатывают объекты
Все что вам нужно сделать, это определить для потока, какие объекты ему нужно будет обрабатывать, чтобы он знал, как согласовывать данные с VMT. Затем без каких-либо усилий вы можете помещать объекты в поток и извлекать их из потока.
Но каким образом один и тот же поток может считывать и записывать такие разные объекты как TCollection и TDialog, даже не зная в момент компилирования, какие типы объектов он будет обрабатывать ? Это существенно отличается от традиционных операций ввода/
вывода языка Pascal. В действительности потоки могут обрабатывать даже новые типы объектов, которые вообще еще не были созданы к моменту компиляции потока.
Ответом на это является так называемая регистрация. Каждому типу объекта ObjectWindows (или любому новому производному типу объекта) присваивается уникальный регистрационный номер. Этот номер записывается в поток до данных объекта. Затем, при считывании 
объекта из потока, ObjectWindows сначала берет регистрационный номер и на его основании узнает, сколько данных ему считывать и какие VMT подключать к данным.
Смысл использования потоков
На фундаментальном уровне вы можете рассматривать потоки как файлы языка Pascal. В своей основе файл языка Pascal представляет собой последовательное устройство ввода/вывода: вы записываете в него и считываете из него. Поток это полиморфное устройство вв
ода/вывода, т.е. оно ведет себя, как последовательный файл, но вы можете считывать и записывать различные типы объектов в каждый момент времени.
Потоки (как и файлы Pascal) можно просматривать, как устройства ввода/вывода произвольного доступа, искать определенное место в файле, считывать данные в этой точке или записывать данные в эту точку, возвращать позицию указателя файла и т.д. Все эти опер
ации можно проводить с потоками и они описаны в разделе "Потоки с произвольным доступом".
Есть два разных аспекта использования потоков, которыми вам нужно овладеть, и к счастью оба они очень простые. Первый - это установление потока, а второй - считывание и запись файлов в поток.
Установка потока
Все что нужно сделать для использования потока это инициализировать его. Точный синтаксис конструктора Init может быть разным, в зависимости от типа потока, с которым вы имеете дело. Например, если вы открываете поток DOS, вам нужно передать имя файла DO
S и режим доступа (только чтение, только запись, чтение/запись) для содержащего поток файла.
Например, для инициализации буфферизованного потока DOS при загрузке набора объектов в программу, все что вам нужно это:
var
 SaveFile: TBufStream;
begin
 SaveFile.Init('COLLECT.DTA', stOpen, 1024);
 ...
После инициализации потока все готово к работе.
TStream это абстрактный механизм потока, поэтому вы будет работать не с ним, а с производными от TStream удобными объектами потока. Это будет, например, TDosStream для проведения дисковых операций ввода/вывода, TBufStream для буфферизованных операций вво
да/вывода (очень удобен для частых операций считывания или записи небольших объемов информации на диск) и TEmsStream для посылки объектов в память EMS.
Кроме того, ObjectWindows реализует индексированные потоки с указателем, указывающим место в потоке. Перемещая этот указатель вы можете организовать произвольный доступ в потоке.
Чтение и запись потока
Основной объект потока TStream реализует три основные метода, которые вам нужно четко усвоить: Get, Put и Error. Get и Put грубо соответствуют процедурам Read и Write, которые вы используете в обычных операциях ввода/вывода. Error это процедура, которая 
вызывается при появлении ошибок потока.
Поместить
Давайте сначала рассмотрим процедуру Put. Общий синтаксис метода Put следующий:
SomeStream.Put(PSomeObject);
где SomeStream есть некоторый производный от TStream объект, который был инициализирован, а PSomeObject есть указатель на некоторый производный от TObject объект, который зарегистрирован с потоком. Это все, что вам нужно сделать. Поток может из VMT PSome
Object узнать, какой это тип объекта (предполагается, что тип зарегистрирован), поэтому он знает какой номер ID писать, и сколько после него будет данных.
Специальный интерес для вас, как для программиста ObjectWindows, состоит в том факте, что при помещении в поток группы с дочерними окнами, дочерние окна также автоматически помещаются в поток. Т.о. записывание сложных объектов не так уж и сложно, более т
ого - это делается автоматически ! Вы можете сохранить полное состояние диалога простым помещением объекта диалога в поток. При повторном запуске вашей программы и загрузке диалога будет выставлено его состояние в момент записи.
Взять
Считывание объектов из потока столь же просто. Все что вам нужно сделать, это вызвать функцию Get:
PSomeObject:=SomeStream.Get;
где SomeStream это инициализированный поток ObjectWindows, а PSomeObject это указатель на некоторый тип объекта ObjectWindows. Get просто возвращает указатель на нечто, что он взял из потока. Сколько данных было взято и какой тип VMT присвоен данным, опр
еделяется не типом PSomeObject, а типом объекта, обнаруженным в потоке. Следовательно, если объект в текущей позиции SomeStream имеет не совпадающий с PSomeObject тип, у вас будет некорректная информация.
Как и Put, Get ищет сложные объекты. Следовательно, если вы ищите в потоке окно, которое владеет дочерними окнами, то они также будут загружены.
В случае ошибки
И, наконец, процедура Error определяет что происходит при возникновении ошибки потока. По умолчанию TStream.Error просто  устанавливает значение двух полей в потоке (Status и ErrorInfo). Если вы хотите сделать что-либо более содержательное, например, сге
нерировать соответствующее сообщение о сбое в работе программы или вывести блок диалога об ошибке, то вам нужно переписать процедуру Error.
Закрытие потока
Когда вы закончили использование потока, вы вызываете его метод Done, как вы обычно вызывали Close для дискового файла. Как и для других объектов ObjectWindows вы делаете это следующим образом:
Dispose(SomeStream, Done);
как для разрушения объекта потока, так и для его закрытия.
Приспособление объектов к потоку
Все стандартные объекты ObjectWindows готовы к использованию в потоках, и все потоки ObjectWindows узнают стандартные объекты. При изготовлении нового типа объекта, производного от стандартного объекта, его очень просто подготовить к использованию в пото
ке и известить потоки о его существовании.
Методы загрузки и хранения
Действительное чтение и запись объектов в поток производится методами Load и Store. Каждый объект должен иметь эти методы для использования потока, поэтому вы никогда не будете вызывать их непосредственно. (Они вызываются из Get и Put.) Все что вам нужно
 сделать, это убедиться в том, что объект знает, как послать себя в поток, когда это потребуется.
Благодаря OOP это делается очень просто, т.к. большинство механизмов наследуются от объекта-предка. Все что должен делать ваш объект, это загружать и хранить те свои компоненты, которые вы в него добавляете, об остальном позаботится метод предка.
Например, вы производите от TWindow новый вид окна с именем художника-сюрреалиста Рене Магритте, который нарисовал много известных картин с окнами:
type
 TMagritte=object(TWindow)
  Surreal: Boolean;
  constructor Load(var S: TStream);
  procedure Store(var S: TStream);
  ...
 end;
Все что было добавлено к данным окна, это одно булевское поле. Для загрузки объекта вы просто считываете стандартный TWindow, а затем считываете дополнительный байт булевского поля. Типичные методы Load и Store для производных объектов будут выглядеть сл
едующим образом:
constructor TMagritte.Load(var S: Stream);
begin
 TWindow.Load(S);                       {загрузка типа}
 S.Read(Surreal, SizeOf(Surreal));      {чтение дополнит. полей}
end;
procedure TMagritte.Store(var S: Stream);
begin
 TWindow.Store(S);                       {сохранение типа}
 S.Write(Surreal, SizeOf(Surreal));      {запись дополнит. полей}
end;
Предостережение: Это целиком ваша забота - позаботиться о одно и тоже количество данных записывается и загружается, и загрузка данных производится в той же последовательности, что и их запись. Компилятор не покажет ошибки. Если вы будете недостаточно акк
уратны, то могут возникнуть серьезные проблемы. Если вы изменяете поля объекта, то нужно изменить и метод Load и метод Store.
Регистрация потока
Кроме определения методов Load и Store для новых объектов, вы также долны зарегистрировать этот новый тип объекта в потоках. Регистрация это простой процесс, который состоит из двух этапов: сначала определяется запись регистрации потока, а затем она пере
дается глобальной процедуре регистрации RegisterType.
ObjectWindows уже имеет зарегистрированными все стандартные объекты, поэтому вам нужно регистрировать только новые, определяемые вами объекты.
Для определения записи регистрации потока нужно следовать приводимому ниже формату. Запись регистрации потока это запись языка Pascal типа TStreamRec, которая определяется следующим образом:
PStreamRec=^TStreamRec;
TStreamRec=record
 ObjType: Word;
 VmtLink: Word;
 Load: Pointer;
 Store: Pointer;
 Next: Word;
end;
По соглашению всем регистрационным записям потока ObjectWindows присваивается то же имя, что и соответствующим типам объектов, но начальное "T" заменяется на "R". Следовательно, регистрационная запись для TCollection будет иметь имя RCollection. Такие аб
страктные типы как TObject и TWindowsObject не имеют регистрационных записей, поскольку их экземпляры вы никогда не будете хранить в потоках.
ID номера объектов
Вам действительно нужно задуматься только о поле ObjType записи, все остальное делается механически. Каждому новому определяемому вами типу нужен его собственный уникальный идентификатор типа в виде числа. ObjectWindows резервирует регистрационные номера
 от 0 до 99 для стандартных объектов, поэтому ваши регистрационные номера будут лежать в диапазоне от 100 до 65535.
Целиком на вас ложится ответственность за создание и ведение библиотеки номеров ID для всех ваших новых объектов, которые будут использоваться в потоках ввода/вывода, и сделать эти ID доступными для пользователей ваших модулей. Как для ID меню и определе
нных пользователем сообщений, присваиваемые вами числа могут быть произвольными, но они должны быть уникальными и попадать в указанный диапазон.
Автоматические поля
Поле VmtLink это связь с таблицей виртуальных методов объектов (VMT). Вы просто задаете его как отклонение типа вашего объекта:
RSomeObject.VmtLink:=Ofs(TypeOf(TSomeObject)^);
Поля Load и Store содержат соответственно адреса методов Load и Store.
RSomeObject.Load:=@TSomeObject.Load;
RSomeObject.Store:=@TSomeObject.Store;
Значение последнего поля, Next, задается RegisterType и не требует никакого вмешательства с вашей стороны. Оно просто обеспечивает внутреннее использование скомпонованного списка регистрационных записей потока.
Регистрация на месте
После конструирования регистрационной записи потока вы вызываете RegisterType с вашей записью в качестве параметра. Поэтому для регистрации вашего нового объекта TMagritte для его использования в потоке, вы включаете следующий код:
const
 RMagritte: TStreamRec=(
  ObjType: 100;
  VmtLink: Ofs(TypeOf(TMagritte)^);
  Load: @TMagritte.Load;
  Store: @TMagritte.Store
 );
RegisterType(RMagritte);
Вот и все. Теперь вы можете помещать (Put) экземпляры вашего нового типа объекта в любой поток ObjectWindows и считывать их из потоков.
Регистрация стандартных объектов
ObjectWindows определяет регистрационные записи потоков для всех его стандартных объектов. Кроме того, модуль WObjects определяет процедуру RegisterWObjects, которая автоматически регистрирует все объекты этого модуля.
Механизм потока
После того, как мы посмотрели на процесс использования потоков, следует заглянуть во внутреннюю работу, которую производит ObjectWindows c вашими объектами с помощью Put и Get. Это прекрасный пример взаимодействия объектов и использования встроенных в ни
х методов.
Процесс Put
Когда вы посылаете объект в поток с помощью метода Put, поток сначала берет указатель VMT с отклонением 0 от объекта и просматривает список зарегистрированных типов потоков системы с целью найти совпадение. Когда это совпадение найдено, поток ищет регист
рационный номер ID объекта и записывает его в поток. Затем поток вызывает метод Store объекта для завершения записи объекта. Метод Store использует процедуру потока Write, которая действительно пишет корректное число байт в поток.
Ваш объект не должен ничего знать о потоке - это может быть дисковый файл, память EMS или любой другой вид потока - ваш объект просто говорит "Запишите меня в поток", и поток делает все остальное.
Процесс Get
Когда вы считываете объект из потока с помощью метода Get, сначала ищется его номер ID, и просматривается на совпадение список зарегистрированных типов. После обнаружения совпадения регистрационная запись дает потоку местоположение метода Load объекта и 
VMT. Затем для чтения нужного объема данных из потока вызывается метод Load.
Вы опять просто говорите потоку взять (Get) следующий объект и поместить его в место, определяемое заданным вами указателем. Ваш объект даже не беспокоится о том, с каким потоком он имеет дело. Поток сам беспокоится о считывании нужного объема данных из 
потока с помощью метода объекта Load, который в свою очередь опирается на метод потока Read.
Для программиста все это достаточно прозрачно, но в то же время вы ясно должны понять, насколько важно зарегистрировать тип до проведения каких-либо попыток ввода/вывода с потоком.
Обработка указателей объектов
Вы можете записать в поток объект nil. Однако, если это сделать, то в поток запишется слово 0. При считывании ID слова 0 поток возвратит указатель nil. Поэтому 0 считается зарезервированным и не может быть использовано в качестве номера ID объекта потока
. ObjectWindows резервирует ID потока от 0 до 99 для внутреннего использования.
Наборы в потоке: Полный пример
В Главе 20, "Наборы", вы уже видели как наборы могут содержать разные, но связанные объекты. Это свойство полиморфии также применимо и к потокам и их можно использовать для записи наборов на диск для последующего обращения, даже другой программой. Вернем
ся к примеру COLLECT4.PAS. Что еще нужно добавить в эту программу для помещения набора в поток ?
Ответ будет очень простым. Сначала возьмем базовый объект TGraphObject и "научим" его хранить его данные (X и Y) в потоке. Для этого нужен метод Store. Затем определим новый метод Store для любого производного от TGraphObject объекта, в котором добавляют
ся дополнительные поля (например, TGraphPie добавляет ArcStart и ArcEnd).
Затем построим регистрационную запись для каждого типа объекта, который предполагается хранить, и зарегистрируем все эти типы при первом запуске вашей программы. Вот и все. Остальное будет подобно обычным операциям ввода/вывода в файл:  определяется пере
менная потока; создается новый поток; одним простым оператором весь набор помещается в поток и поток закрывается.
Добавление методов Store
Приведем методы Store. Обратите внимание, что для PGraphEllipse и PGraphRect не требуются свои собственные методы, т.к. они не добавляют новых полей к унаследованным от PGraphObject:
type
 PGraphObject=^TGraphObject;
 TGraphObject=object(TObject)
  Rect: TRect;
  constructor Init(Bounds: TRect);
  procedure Draw(DC: HDC); virtual;
  procedure Store(var S: TStream); virtual;
end;
PGraphEllipse=^TGraphEllipse;
TGraphEllipse=object(TGraphObject)
 procedure Draw(DC: HDC); virtual;
end;
PGraphRect=^TGraphRect;
TGraphRect=object(TGraphObject)
 procedure Draw(DC: HDC); virtual;
end;
PGraphPie=^TGraphPie;
TGraphPie=object(TGraphObject)
 ArcStart, ArcEnd: TPoint;
 constructor Init(Bounds: TRect);
 procedure Draw(DC: HDC); virtual;
 procedure Store(var S: TStream); virtual;
end;
Реализация метода Store вполне очевидна. Каждый объект вызывает свой унаследованный метод Store, который хранит все унаследованные данные. Затем вызывается метод Write для записи дополнительных данных:
procedure TGraphObject.Store(var S: TStream);
begin
 S.Write(Rect, SizeOf(Rect));
end;
procedure TGraphPie.Store(var S: TStream);
begin
 TGraphObject.Store(S);
 S.Write(ArcStart, SizeOf(ArcStart));
 S.Write(ArcEnd, SizeOf(ArcEnd));
end;
Обратите внимание, что метод TStream Write делает двоичную запись. Его первый параметр может быть переменной любого типа, но TStream.Write не может узнать размеры этой переменной. Второй параметр содержит эту информацию и вы должны придерживаться соглаше
ния относительно использования стандартной функции SizeOf. Т.о. компилятор всегда может гарантировать, что вы всегда считываете и записываете нужное количество информации.
Регистрация записей
Наш последний шаг состоит в определении константы регистрационной записи для каждого производного типа. Хороший прием программирования состоит в следовании соглашения ObjectWindows относительно использования для имени имени типа, где вместо первой буквы 
T ставится R.
Помните о том, что каждой регистрационной записи присваивается уникальный номер ID объекта (ObjType). Номера от 0 до 99 резервируются ObjectWindows для стандартных объектов. Хорошо бы отслеживать все номера ID ваших объектов потока в некотором центрально
м месте, чтобы избежать дублирования.
const
 RGraphEllipse: TStreamRec=(
  ObjType: 150;
  VmtLink: Ofs(TypeOf(TGraphEllipse)^);
  Load: nil;                            {Метод загрузки отсутствует}
  Store: @TGraphEllipse.Store);
 RGraphRect: TStreamRec=(
  ObjType: 151;
  VmtLink: Ofs(TypeOf(TGraphRect)^);
  Load: nil;                            {Метод загрузки отсутствует}
  Store: @TGraphRect.Store);
 RGraphPie: TStreamRec=(
  ObjType: 152;
  VmtLink: Ofs(TypeOf(TGraphPie)^);
  Load: nil;                            {Метод загрузки отсутствует}
  Store: @TGraphPie.Store);
Вам не нужно регистрационная запись для TGraphObject, т.к. это абстрактный тип и он никогда не будет помещаться в набор или в поток. Указатель Load каждой регистрационной записи устанавливается в nil, поскольку в данном примере рассматривается только пом
ещение данных в поток. Методы Load будут определены и изменены регистрационные записи в следующем примере (STREAM2.PAS).
Регистрация
Всегда нужно зарегистрировать каждую из этих записей до проведения каких-либо операций ввода/вывода с потоком. Самый простой способ сделать это состоит в том, чтобы объединить их все в одну процедуру и вызвать ее в самом начале вашей программы (или в мет
оде Init вашего приложения):
procedure StreamRegistration;
begin
 RegisterType(RCollection);
 RegisterType(RGraphEllipse);
 RegisterType(RGraphRect);
 RegisterType(RGraphPie);
end;
Обратите внимание, что вам нужно зарегистрировать TCollection (используя его запись RCollection - теперь вы видите, что соглашения о присвоении имен упрощают программирование), хотя вы и не определили TCollection. Правило очень простое и незабываемое: им
енно вы отвечаете за регистрацию каждого типа объекта, который ваша программа будет помещать в поток.
Запись в поток
Нужно следовать обычной последовательности операций ввода/вывода в файл: создать поток; поместить в него данные (набор); закрыть поток. Вам не нужно писать итератор ForEach для помещения в поток каждого элемента набора. Вы просто говорите потоку поместит
ь (Put) набор в поток:
var
 ...
 GraphicsStream: TBufStream;
begin
...
 StreamRegistration;          {Регистрация всех объектов потока}
 GraphicsStream.Init('GRAPH.SMT', stCreate, 1024);
 GraphicsStream.Put(GraphicsList);      {Выходной набор}
 if GraphicsStream.Status <> 0 then
  Status:=em_Stream;
 GraphicsStream.Done;                   {Сброс потока}
end;
В результате создастся дисковый файл, который содержит всю информацию, которая нужна для "считывания" набора назад в память. Когда поток открыт и ищется набор, то (см. STREAM2.PAS), магически восстанавливаются все скрытые связи между набором и его элемен
тами, объекты и их таблицы виртуальных методов. Следующий раздел поясняет, как помещать в поток объекты, которые содержат связи с другими объектами.
Как все хранится ?
Важное предостережение относительно потоков: только владелец объекта должен записывать его в поток. Это предостережение аналогично традиционному предостережению языка Pascal, которое вам должно быть известно: только владелец указателя может разрушить его
.
В реальных приложениях множество объектов часто имеют указатель на конкретную структуру. Когда возникает необходимость в проведении операций ввода/вывода вы должны решить, кто "владеет" структурой. Только этот владелец должен посылать структуру в поток. 
Иначе у вас может получиться несколько копий одной структуры в потоке. При считывании такого потока будет создано несколько экземпляров структуры и каждый из первоначальных объектов будет указывать на собственную персональную копию структуры вместо единс
твенной первоначальной структуры.
Поля в потоке
Много раз вы видели, что удобно хранить указатель на дочерние окна группы в локальной переменной (поле данных объекта). Например, блок диалога может хранить указатель на его объекты управления в полях с мнемоническими именами для более удобного доступа (
OKButton или FileINputLine). При создании такого дочернего окна родительское окно будет иметь на него два указателя, один - в поле, и еще один - в списке дочерних окон. Если на это не обратить внимания, то считывание такого объекта из потока приведет к д
ублированию.
Решение состоит в использовании методов TWindowsObject GetChildPtr и PutChildPtr. При хранении поля, которое является дочерним окном, вместо записи указателя, как если бы это была простая переменная, вы вызываете PutChildPtr, который записывает ссылку на
 позицию дочернего окна в списке дочерних окон группы. Аналогично, при загрузке (Load) группы из потока, вы вызываете GetChildPtr, который гарантирует, что поле и список дочерних окон указывают на один и тот же объект.
Приведем короткий пример использования GetChildPtr и PutChildPtr в простом окне:
type
 TDemoWinodw=object(TWindow)
 Msg: PStatic;
 constructor Load(var S: TStream);
 procedure Store(var S: TStream);
end;
constructor TDemoWindow.Load(var S: TStream);
begin
 TWindow.Load(S);
 GetChildPtr(S, Msg);
end;
procedure TDemoWindow.Store(var S: TStream);
begin
 TWindow.Store(S);
 PutChildPtr(S, Msg);
end;
Давайте рассмотрим, чем этот Store отличается от обычного Store. После обычного сохранения окна все что нам нужно сделать, это записать ссылку на поле Msg, вместо записи самого поля, как мы это обычно делали. Действительный объект кнопки хранится в виде 
дочернего окна для окна, которое вызывается TWindow.Store. Кроме этого нужно поместить эту информацию в поток с указанием того, что Msg указывает на это дочернее окно. Метод Load производит обратные действия, сначала загружая окно и его дочернее окно кно
пки, а уже затем восстанавливая указатель на это дочернее окно через Msg.
Родство окон
Аналогичная ситуация может возникнуть, если окно имеет поле, которое указывает на одного из его родственников. Окно называется родственным другому окну, если они оба принадлежат одному и тому же родительскому окну. Например, у вас есть управление редакти
рованием и две кнопки выбора, которые управляют активизацией управления редактированием. При изменении состояния кнопки выбора она соответственно активизирует или дезактивизирует управление редактированием. TActivateRadioButton должна знать управление ре
дактированием, которое также есть компонента этого же окна, поэтому управление редактированием добавляется в качестве переменной.
Как и для дочерних окон, при чтении и записи родственных ссылок в поток могут возникнуть проблемы. Решение также будет аналогичным. Методы TWindowsObject PutSiblingPtr и GetSiblingPtr предоставляют средства доступа к родственникам:
type
 TActivateRadioButton=object(TRadioButton)
 EditControl: PEdit;
 ...
 constructor Load(var S: TStream);
 procedure Store(var S: TStream); virtual;
 ...
end;
constructor TActivateRadioButton.Load(var S: TStream);
begin
 TRadioButton.Load(S);
 GetPeerPtr(S, EditControl);
end;
procedure TActivateRadioButton.Store(var S: TStream);
begin
 TRadioButton.Load(S);
 PutPeerPtr(S, EditControl);
end;
Единственно о чем нужно побеспокоиться, так это о загрузке ссылок на родственные окна, которые еще не загружены (т.е. в списке дочерних окон они идут ниже и, следовательно, позднее в потоке). ObjectWindows автоматически обрабатывает эту ситуацию, отслежи
вая все подобные будущие ссылки и разрешая их после загрузки всех дочерних окон. Вам нужно иметь в виду, что родственные ссылки не будут действовать, пока Load не выполнится целиком. Принимая это во внимание, вы не должны помещать в Load никакой код, кот
орый использует дочерние окна, которые зависят от их родственных окон, поскольку в этом случае результат может быть непредсказуемым.  
Копирование потока
TStream имеет метод CopyFrom(S,Count), который копирует заданное число байт (Count) из заданного потока S. CopyFrom может быть использован для копирования содержимого одного потока в другой. Если, например, вы циклически обращаетесь к дисковому потоку, в
ы можете скопировать его в поток EMS для организации более быстрого доступа:
NewStream:=New(TEmsStream, Init(OldStream^.GetSize));
OldStream^.Seek(0);
NewStream^.CopyFrom(OldStream, OldStream^.GetSize);
Потоки произвольного доступа
До этого момента мы работали с потоками как с устройствами последовательного доступа: вы помещали (Put) объекты в конец вашего потока и считывали их назад (Get) в той же последовательности. Но ObjectWindows имеет и более мощные средства. Имеется возможно
сть рассматривать поток как виртуальное устройство произвольного доступа. Кроме Get и Put, которые соответствуют Read и Write при работе с файлом, потоки обладают средствами проведения операций Seek, FilePos, FileSize и Truncate.
- Процедура потока Seek перемещает текущий указатель потока к заданной позиции (число байт от начала потока), как стандартная  процедура Seek языка Turbo Pascal.
- Процедура GetPos по своему действию обратна процедуре Seek. Она возвращает Longint с текущей позицией потока.
- Функция GetSize возвращает размер потока в байтах. 
- Процедура Truncate удаляет все данные, которые расположены после текущей позиции потока, при этом текущая позиция потока становится концом потока.
Поскольку работа с этими программами очень удобна, потоки произвольного доступа требуют от вас отслеживать индекс, отмечая начальную позицию каждого объекта в потоке. В этом случае для хранения индекса вы можете использовать набор.  
Необъектные элементы потоков
В поток можно записывать и элементы, которые не являются объектами, но для этого следует использовать несколько иной подход. Стандартные методы потока Get и Put требуют загрузки или записи объекта, производного от TObject. Если вам нужно создать поток, к
оторый состоит не из объектов, переходите на нижний уровень процедур Read и Write, где в поток записывается или считывается заданное число байт. Этот же механизм используют Get и Put для чтения и записи данных об объектах. Вы просто обходите механизм VMT
, который заложен в Put и Get. 
Разработка пользователем собственных потоков
Данный раздел суммирует возможности методов и обработки ошибок потоков ObjectWindows, чтобы вы знали, что можно использовать для создания новых типов потоков.
Сам TStream является абстрактным объектом и его можно расширить для создания удобного типа потока. Большинство методов TStream являются абстрактными и должны быть реализованы как их производные методы, основывающиеся на абстрактных методах TStream. Полно
стью реализованы только методы Error, Get и Put. GetPos, GetSize, Read, Seek, SetPos, Truncate и Write должны быть переписаны. Если производный тип объекта имеет буфер, то должен быть переписан и метод Flush.
Обработка ошибок потока
TStream имеет метод Error(Code, Info), который вызывается при обнаружении ошибки потока. Error просто присваивает полю Status потока значение одной из констант, приведенных в Главе 6, "Глобальные ссылки" в разделе "Константы stXXXX".
Поле ErrorInfo не определено, если если значение Status не есть stGetError или stPutError. Если значение поля Status есть stGetError, то поле ErrorInfo содержит номер ID потока незарегистрированного типа. Если значение поля Status есть stPutError, то пол
е ErrorInfo содержит отклонение VMT типа, который вы пытались поместить в поток. Вы можете переписать TStream.Error для генерации любого уровня обработки ошибок, включая ошибки исполнения.



Яндекс цитирования