Техника объектно-ориентированного программирования и ObjectWindows дают вам мощные средства инкапсуляции кода и данных и большие возможности построения взаимосвязанных структур объектов. Но что делать, если стоит простая задача, например, по хранению некоторых объектов на диске?
    Когда-то данные хранились исключительно в записях, и помещение данных на диск было тривиальной задачей. Но данные в программах ObjectWindows неразрывно связаны с объектами. Конечно, вы можете отделить данные от объекта и записать их в дисковый файл. Объединение дает вам значительный шаг в направлении прогресса, а разъединение отбрасывает вас назад.
    Есть ли в самом объектно-ориентированном программировании и ObjectWindows некоторые средства, которые могли бы разрешить эту проблему? Есть, и это потоки.
    Поток в ObjectWindows - это набор объектов на их пути куда-либо: обычно в файл, EMS, в последовательный порт или некоторое другое устройство. Потоки обслуживают операции ввода-вывода на уровне объектов, а не на уровне данных. При расширении объекта ObjectWindows вам нужно обеспечить обработку определенных вами дополнительных полей. Все сложные аспекты обработки на уровне объектов будут проделаны за вас.
    Поскольку вы пишете программы на языке Паскаль, то знаете,
что до выполнения операций ввода-вывода с файлом, вы сначала
должны сообщить компилятору, какой тип данных вы будете писать и
считывать из файла. Файл должен иметь тип, и этот тип должен быть
установлен во время компиляции.
    Паскаль реализует в этой связи очень удобное правило: можно
организовать доступ к файлу неопределенного типа с помощью
процедур BlockWrite и BlockRead. Отсутствие проверки типа
возлагает некоторую дополнительную ответственность на программиста,
хотя позволяет очень быстро выполнять двоичные операции
ввода-вывода.
    Вторая проблема состоит в том, что вы не можете
непосредственно использовать файлы с объектами. Паскаль не позволяет вам
создавать файл с объектным типом. Объекты могут содержать
виртуальные методы, адреса которых определяются в процессе выполнения
программы, поэтому хранение информации о виртуальных методах вне
программы лишено смысла, еще более бессмысленно считывать эту
информацию в программу.
    Но эту проблему снова можно обойти. Вы можете выделить
данные из ваших объектов и записать эту информацию в какой-то файл
некоторого вида, а уже позднее восстановить объекты из этих
исходных данных. Подобное решение, однако, будет недостаточно
элегантным и существенно усложняет конструирование объектов.
    ObjectWindows позволяет обойти все эти трудности и даже
сулит вам получение некоторых дополнительных выгод. Потоки дают вам
простое, но изящное средство хранение данных объекта вне вашей
программы.
    Потоки ObjectWindows позволяют вам работать с файлами
определенного и неопределенного типа: проверка типа имеется, но тип
посылаемого объекта не должен обязательно определяться во время
компиляции. Смысл в том, что потоки знают, что они имеют дело с
объектами, и поскольку все объекты являются производными от
TObject, поток может их обработать. В действительности различные
объекты ObjectWindows могут также легко записываться в один
поток, как и группы идентичных объектов.
    Все что вам нужно сделать - это определить для потока, какие
объекты ему нужно будет обрабатывать, чтобы он знал, как
согласовывать данные с таблицами виртуальных методов. Затем без
каких-либо усилий вы можете помещать объекты в поток и извлекать их
из потока.
    Но каким образом один и тот же поток может считывать и
записывать такие разные объекты как TCollection и TDialog, даже не
зная в момент компиляции, какие типы объектов он будет
обрабатывать? Это существенно отличается от традиционных операций
ввода-вывода языка Паскаль. В действительности потоки могут
обрабатывать даже новые типы объектов, которые вообще еще не были
созданы к моменту компиляции потока.
    Ответом на это является так называемая регистрация. Каждому
типу объекта ObjectWindows (или любому новому производному типу
объекта) присваивается уникальный регистрационный номер. Этот
номер записывается в поток перед данными объекта. Затем, при
считывании объекта из потока, ObjectWindows сначала берет
регистрационный номер и на его основании узнает, сколько данных нужно
считывать и какие таблицы виртуальных методов подключать к данным.
    На фундаментальном уровне вы можете рассматривать потоки как
файлы языка Паскаль. В своей основе файл языка Паскаль
представляет собой последовательное устройство ввода-вывода: вы записываете
в него и считываете из него. Поток - это полиморфическое
устройство последовательного ввода-вывода, т.е. оно ведет себя, как
последовательный файл, но вы можете считывать и записывать различные
типы объектов в каждый момент времени.
    Потоки (как и файлы Паскаля) можно также просматривать, как
устройства ввода-вывода произвольного доступа, искать
определенное место в файле, считывать данные в этой точке или записывать
данные в эту точку, возвращать позицию указателя файла и т.д. Все
эти операции можно выполнять с потоками, и они описаны в разделе
"Потоки с произвольным доступом".
    Есть два разных аспекта использования потоков, которыми вам
нужно овладеть, и к счастью оба они очень простые. Первый - это
установка потока, а второй - считывание и запись файлов в поток.
    Все что нужно сделать для использования потока - это
инициализировать его. Точный синтаксис конструктора Init может быть
разным, в зависимости от типа потока, с которым вы имеете дело.
Например, если вы открываете поток DOS, вам нужно передать имя
файла DOS и режим доступа (только чтение, только запись,
чтение/запись) для содержащего поток файла.
    Например, для инициализации буферизированного потока DOS при
загрузке набора объектов в программу, все что вам нужно это:
    После инициализации потока все готово к работе.
    TStream это абстрактный механизм потока, поэтому вы будет
работать не с ним, а с производными от TStream удобными объектами
потока. Это будет, например, TDosStream, для выполнения дисковых
операций ввода-вывода, TBufStream для буферизованных операций
ввода-вывода (очень удобен для частых операций считывания или
записи небольших объемов информации на диск) и TEmsStream для
передачи объектов в память EMS. Кроме того, ObjectWindows реализует
индексированные потоки с указателем, указывающим место в потоке.
Перемещая этот указатель вы можете организовать произвольный
доступ в потоке.
    Основной объект потока TStream реализует три главных метода,
которые вам нужно четко понимать: Get, Put и Error. Get и Put
грубо соответствуют процедурам Read и Write, которые вы
используете в обычных операциях ввода-вывода. Error - это процедура,
которая вызывается при появлении ошибок потока.
    Давайте сначала рассмотрим процедуру Put. Общий синтаксис
метода Put следующий:
    Специальный интерес для вас, как для программиста,
работающего с ObjectWindows, состоит в том факте, что при помещении в
поток группы с дочерними окнами, дочерние окна также
автоматически помещаются в поток. Таким образом, запись сложных объектов не
так уж и сложна, более того, это делается автоматически! Вы
можете сохранить полное состояние диалога простым помещением объекта
диалога в поток. При повторном запуске вашей программы и загрузке
диалога будет выведено его состояние в момент записи.
    Считывание объектов из потока столь же просто. Все что вам
нужно сделать, это вызвать функцию Get:
где SomeStream - это инициализированный поток ObjectWindows, а
PSomeObject - указатель на некоторый тип объекта ObjectWindows.
Get просто возвращает указатель на нечто, что он взял из потока.
Сколько данных было взято и какой тип таблицы виртуальных методов
(VMT) присвоен данным, определяется не типом PSomeObject, а типом
объекта, обнаруженным в потоке. Следовательно, если объект в
текущей позиции SomeStream имеет не совпадающий с PSomeObject тип,
у вас будет некорректная информация.
    Как и Put, Get ищет сложные объекты. Следовательно, если вы
ищите в потоке окно, которое владеет дочерними окнами, то они
также будут загружены.
    И, наконец, процедура Error определяет что происходит при
возникновении ошибки потока. По умолчанию TStream.Error просто
устанавливает значение двух полей в потоке (Status и ErrorInfo).
Если вы хотите сделать что-либо более содержательное, например,
сгенерировать соответствующее сообщение о сбое в работе программы
или вывести блок диалога c сообщением об ошибке, то вам нужно
переопределить процедуру Error.
    Когда вы закончили использование потока, вы вызываете его
метод Done, как вы обычно вызывали Close для дискового файла. Как
и для других объектов ObjectWindows, это делается следующим
образом:
как для уничтожения объекта потока, так и для его закрытия.
    Все стандартные объекты ObjectWindows готовы к использованию
в потоках, и все потоки ObjectWindows узнают стандартные объекты.
При изготовлении нового типа объекта, производного от
стандартного объекта, его очень просто подготовить к использованию в потоке
и известить потоки о его существовании.
    Действительное чтение и запись объектов в поток производится
методами Load и Store. Каждый объект должен иметь эти методы для
использования потока, поэтому вы никогда не будете вызывать их
непосредственно (они вызываются из методов Get и Put.) Все что
вам нужно сделать, это убедиться в том, что объект знает, как
послать себя в поток, когда это потребуется.
    Благодаря объектно-ориентированному программированию это
делается очень просто, т.к. большинство механизмов наследуются от
объекта-предка. Все что должен делать ваш объект, это загружать и
хранить те свои компоненты, которые вы в него добавляете, об
остальном позаботится метод предка. Например, вы производите от
TWindow новый вид окна с именем художника-сюрреалиста Рене
Магритте, который нарисовал много известных картин с окнами:
    Все что было добавлено к данным окна - это одно булевское
поле. Для загрузки объекта вы просто считываете стандартный
TWindow, а затем считываете дополнительный байт булевского поля.
Типичные методы Load и Store для производных объектов будут
выглядеть следующим образом:
    Вы должны контролировать, что записывается и загружается
один и тот же объем данных, и загрузка данных производится в той
же последовательности, что и их запись. Компилятор не покажет
ошибки. Если вы будете недостаточно аккуратны, то могут
возникнуть серьезные проблемы. Если вы изменяете поля объекта, то нужно
изменить и метод Load, и метод Store.
    Кроме определения методов Load и Store для новых объектов,
вы также должны зарегистрировать этот новый тип объекта в
потоках. Регистрация - это простой процесс, который состоит из двух
этапов: сначала определяется запись регистрации потока, а затем
она передается глобальной процедуре регистрации RegisterType.
    Примечание: ObjectWindows уже имеет
зарегистрированными все стандартные объекты, поэтому вам нужно
регистрировать только новые, определяемые вами объекты.
    Для определения записи регистрации потока нужно следовать
приводимому ниже формату. Запись регистрации потока это запись
языка Pascal типа TStreamRec, которая определяется следующим
образом:
    По соглашению всем регистрационным записям потока
ObjectWindows присваивается то же имя, что и соответствующим
типам объектов, но начальное "T" заменяется на "R". Следовательно,
регистрационная запись для TCollection будет иметь имя
RCollection. Такие абстрактные типы как TObject и TWindowsObject
не имеют регистрационных записей, поскольку их экземпляры вы
никогда не будете хранить в потоках.
    Вам действительно нужно думать только о поле ObjType записи,
все остальное делается механически. Каждому новому определяемому
вами типу требуется его собственный уникальный идентификатор типа
в виде числа. ObjectWindows резервирует регистрационные номера от
0 до 99 для стандартных объектов, поэтому ваши регистрационные
номера будут лежать в диапазоне от 100 до 65535.
    Ответственность за создание и ведение библиотеки номеров
идентификаторов для всех ваших новых объектов, которые будут
использоваться в потоках ввода-вывода, ложиться целиком на вас.
Нужно сделать эти идентификаторы доступными для пользователей
ваших модулей. Как и для идентификатора меню и определенных
пользователем сообщений, присваиваемые вами числа могут быть
произвольными, но они должны быть уникальными и попадать в указанный
диапазон.
    Поле VmtLink это связь с таблицей виртуальных методов
объектов (VMT). Вы просто задаете его как отклонение типа вашего
объекта:
    Поля Load и Store содержат соответственно адреса методов
Load и Store.
    Значение последнего поля, Next, задается RegisterType и не
требует никакого вмешательства с вашей стороны. Оно просто
обеспечивает внутреннее использование скомпонованного списка
регистрационных записей потока.
    После конструирования регистрационной записи потока вы
вызываете RegisterType с вашей записью в качестве параметра. Поэтому
для регистрации вашего нового объекта TMagritte для его
использования в потоке вы включаете следующий код:
    Вот и все. Теперь вы можете помещать (Put) экземпляры вашего
нового типа объекта в любой поток ObjectWindows и считывать их из
потоков.
    ObjectWindows определяет регистрационные записи потоков для
всех его стандартных объектов. Кроме того, модуль WObjects
определяет процедуру RegisterWObjects, которая автоматически
регистрирует все объекты этого модуля. Например, модуль OWindows
содержит процедуру RegisterOWindows, а ODialogs - RegisterODialogs.
    После того, как мы посмотрели на процесс использования
потоков, следует заглянуть во внутреннюю работу, которую производит
ObjectWindows c вашими объектами с помощью методов Put и Get. Это
прекрасный пример взаимодействия объектов и использования
встроенных в них методов.
    Когда вы посылаете объект в поток с помощью метода Put,
поток сначала берет указатель VMT со смещением 0 от объекта и
просматривает список зарегистрированных типов потоков системы с целью
найти совпадение. Когда это совпадение найдено, поток ищет
регистрационный номер идентификатора объекта и записывает его в
поток. Затем поток вызывает метод Store объекта для завершения
записи объекта. Метод Store использует процедуру потока Write,
которая действительно пишет корректное число байт в поток.
    Ваш объект не должен ничего знать о потоке - это может быть
файл на диске, память EMS или любой другой вид потока - ваш
объект просто говорит "запишите меня в поток", и поток делает все
остальное.
    Когда вы считываете объект из потока с помощью метода Get,
сначала ищется номер его идентификатора, и просматривается на
совпадение список зарегистрированных типов. После обнаружения
совпадения регистрационная запись дает потоку местоположение
метода Load объект и VMT. Затем для чтения нужного объема данных из
потока вызывается метод Load.
    Вы опять просто говорите потоку, что нужно взять (Get)
следующий объект и поместить его в место, определяемое заданным вами
указателем. Ваш объект даже не беспокоится о том, с каким потоком
он имеет дело. Поток сам беспокоится о считывании нужного объема
данных из потока с помощью метода объекта Load, который в свою
очередь опирается на метод потока Read.
    Для программиста все это достаточно прозрачно, но в то же
время вы ясно должны понять, насколько важно зарегистрировать тип
до проведения каких-либо попыток ввода-вывода с потоком.
    Вы можете записать в поток объект nil. Однако, если это
сделать, то в поток запишется слово 0. При считывании идентификатора
слова 0 поток возвратит указатель nil. Поэтому 0 считается
зарезервированным и не может использоваться в качестве номера
идентификатора объекта потока. ObjectWindows резервирует идентификатор
потока от 0 до 99 для внутреннего использования.
    В Главе 19, "Наборы", вы уже видели как наборы могут
содержать разные, но связанные объекты. Это свойство полиморфизма
также применимо и к потокам, и их можно использовать для записи
наборов на диск для последующего обращения, даже в другой
программе. Вернемся к примеру COLLECT4.PAS. Что еще нужно добавить в эту
программу для помещения набора в поток?
    Ответ будет очень простым. Сначала возьмем базовый объект
TGraphObject и "научим" его хранить его данные (X и Y) в потоке.
Для этого нужен метод Store. Затем определим новый метод Store
для любого производного от TGraphObject объекта, в котором
добавляются дополнительные поля (например, TGraphPie добавляет
ArcStart и ArcEnd).
    Затем построим регистрационную запись для каждого типа
объекта, который предполагается хранить, и зарегистрируем все эти
типы при первом запуске вашей программы. Вот и все. Остальное
будет подобно обычным операциям ввода-вывода в файл: определяется
переменная потока; создается новый поток; одним простым
оператором весь набор помещается в поток, и поток закрывается.
    Приведем методы Store. Обратите внимание, что для
PGraphEllipse и PGraphRect не требуются свои собственные методы,
т.к. они не добавляют новых полей к унаследованным от
PGraphObject:
    Реализация метода Store вполне очевидна. Каждый объект
вызывает свой унаследованный метод Store, который хранит все
унаследованные данные. Затем вызывается метод Write для записи
дополнительных данных:
    Обратите внимание, что метод TStream Write делает двоичную
запись. Его первый параметр может быть переменной любого типа, но
TStream.Write не может узнать размеры этой переменной. Второй
параметр содержит эту информацию, и вы должны придерживаться
соглашения относительно использования стандартной функции SizeOf.
Таким образом, компилятор всегда может гарантировать, что вы всегда
считываете и записываете нужное количество данных.
    Наш последний шаг состоит в определении константы
регистрационной записи для каждого производного типа. Хороший прием
программирования состоит в следовании соглашению ObjectWindows
относительно использования для имени типа идентификатора, где вместо
первой буквы T ставится R.
    Помните о том, что каждой регистрационной записи
присваивается уникальный номер идентификатора объекта (ObjType). Номера от
0 до 99 резервируются ObjectWindows для стандартных объектов.
Хорошо бы отслеживать все номера идентификаторов ваших объектов
потока в некотором центральном месте, чтобы избежать дублирования.
    Вам не нужно регистрационная запись для TGraphObject, так
как это абстрактный тип, и он никогда не будет помещаться в набор
или в поток. Указатель Load каждой регистрационной записи
устанавливается в nil, поскольку в данном примере рассматривается
только помещение данных в поток. В следующем примере методы Load
будут определены, и изменены регистрационные записи (см.
STREAM2.PAS).
    Всегда нужно зарегистрировать каждую из этих записей до
проведения каких-либо операций ввода-вывода с потоком. Самый простой
способ сделать это состоит в том, чтобы объединить их все в одну
процедуру и вызвать ее в самом начале вашей программы (или в
методе Init вашего приложения):
    Обратите внимание, что вам нужно зарегистрировать
TCollection (используя его запись RCollection - теперь вы видите,
что соглашения о присваивании имен упрощают программирование),
хотя вы и не определили TCollection. Правило очень простое и
незабываемое именно вы отвечаете за регистрацию каждого типа
объекта, который ваша программа будет помещать в поток.
    Нужно следовать обычной последовательности операций
ввода-вывода в файл: создать поток; поместить в него данные (набор);
закрыть поток. Вам не нужно писать итератор ForEach для помещения
в поток каждого элемента набора. Вы просто говорите потоку, что
нужно поместить (Put) набор в поток:
    В результате создастся файл на диске, который содержит всю
информацию, необходимую для "считывания" набора назад в память.
Когда поток открыт, и ищется набор, то (см. STREAM2.PAS)
"магически" восстанавливаются все скрытые связи между набором и его
элементами, объекты и их таблицы виртуальных методов. Следующий
раздел поясняет, как помещать в поток объекты, которые содержат
связи с другими объектами.
    Относительно потоков нужно сделать важное предостережение:
только владелец объекта должен записывать его в поток. Это
предостережение аналогично традиционному предостережению языка
Паскаль, которое вам должно быть известно: только владелец
указателя может уничтожить его.
    В реальных сложных приложениях множество объектов часто
имеют указатель на конкретную структуру. Когда возникает
необходимость в выполнении операций ввода-вывода, вы должны решить, кто
"владеет" структурой. Только этот владелец должен посылать
структуру в поток. Иначе у вас может получиться несколько копий одной
структуры в потоке. При считывании такого потока будет создано
несколько экземпляров структуры, и каждый из первоначальных
объектов будет указывать на собственную персональную копию структуры
вместо единственной первоначальной структуры.
    Много раз вы видели, что удобно хранить указатель на
дочерние окна группы в локальной переменной (поле данных объекта).
Например, блок диалога может хранить указатель на его объекты
управления в полях с мнемоническими именами для более удобного
доступа (OKButton или FileINputLine). При создании такого дочернего
окна порождающее окно будет иметь на него два указателя, один
в поле, и еще один - в списке дочерних окон. Если на это не
обратить внимания, то считывание такого объекта из потока приведет к
дублированию.
    Решение состоит в использовании методов TWindowsObject
GetChildPtr и PutChildPtr. При хранении поля, которое является
дочерним окном, вместо записи указателя, как если бы это была
простая переменная, вы вызываете метод PutChildPtr, который
записывает ссылку на позицию дочернего окна в списке дочерних окон
группы. Аналогично, при загрузке (Load) группы из потока, вы
вызываете GetChildPtr, который гарантирует, что поле и список
дочерних окон указывают на один и тот же объект. Приведем короткий
пример использования GetChildPtr и PutChildPtr в простом окне:
    Давайте рассмотрим, чем этот метод Store отличается от
обычного Store. После обычного сохранения окна все что нам нужно
сделать, это записать ссылку на поле Msg, вместо записи самого поля,
как мы это обычно делали. Действительный объект кнопки хранится в
виде дочернего окна для окна, которое вызывается TWindow.Store.
    Кроме этого нужно поместить эту информацию в поток с
указанием того, что Msg указывает на это дочернее окно. Метод Load
производит обратные действия, сначала загружая окно и его
дочернее окно командной кнопки, а уже затем восстанавливая указатель
на это дочернее окно через Msg.
    Аналогичная ситуация может возникнуть, если окно имеет поле,
указывающее на одного из его родственников. Окно называется
родственным другому окну, если они оба принадлежат одному и тому же
порождающему окну. Например, у вас есть управляющий элемент
редактированием и две кнопки с независимой фиксацией, которые
управляют активизацией управляющего элемента редактирования. При
изменении состояния кнопки с независимой фиксацией, она
соответственно активизирует или дезактивизирует управляющий элемент
редактирования. TActivateRadioButton должна знать управляющий
элемент редактирования, который также является компонентом этого же
окна, поэтому управляющий элемент редактирования добавляется в
качестве переменной.
    Как и для дочерних окон, при чтении и записи родственных
ссылок в поток могут возникнуть проблемы. Решение также будет
аналогичным. Методы TWindowsObject PutSiblingPtr и GetSiblingPtr
предоставляют средства доступа к родственникам:
    Единственно о чем нужно побеспокоиться, так это о загрузке
ссылок на родственные окна, которые еще не загружены (т.е. в
списке дочерних окон они идут ниже и, следовательно, позднее в
потоке). ObjectWindows автоматически обрабатывает эту ситуацию,
отслеживая все подобные будущие ссылки и разрешая их после
загрузки всех дочерних окон. Вам нужно иметь в виду, что родственные
ссылки не будут действовать, пока Load не выполнится целиком.
Принимая это во внимание, вы не должны помещать в Load никакой
код, который использует дочерние окна, которые зависят от их
родственных окон, поскольку в этом случае результат может быть
непредсказуемым.
    TStream имеет метод CopyFrom(S,Count), который копирует
заданное число байт (Count) из заданного потока S. Метод CopyFrom
может быть использован для копирования содержимого одного потока
в другой. Если, например, вы циклически обращаетесь к дисковому
потоку, то можете скопировать его в поток EMS для организации
более быстрого доступа:
    До этого момента мы работали с потоками как с устройствами
последовательного доступа: вы помещали (Put) объекты в конец
вашего потока и считывали их назад (Get) в той же
последовательности. Но ObjectWindows имеет и более мощные средства. Имеется
возможность рассматривать поток как виртуальное устройство
произвольного доступа. Кроме методов Get и Put, которые соответствуют
Read и Write при работе с файлом, потоки обладают средствами
проведения операций Seek, FilePos, FileSize и Truncate.
    - Процедура потока Seek перемещает текущий указатель потока
к заданной позиции (число байт от начала потока), как
стандартная процедура Seek языка Паскаль.
    - Процедура GetPos по своему действию обратна процедуре
Seek. Она возвращает значение Longint с текущей позицией
потока.
    - Функция GetSize возвращает размер потока в байтах.
    - Процедура Truncate удаляет все данные, которые расположены
после текущей позиции потока, при этом текущая позиция
потока становится концом потока.
    Поскольку работа с этими программами очень удобна, потоки
произвольного доступа требуют от вас отслеживать индекс,
отмечающий начальную позицию каждого объекта в потоке. В этом случае для
хранения индекса вы можете использовать набор.
    В поток можно записывать и элементы, которые не являются
объектами, но для этого следует использовать несколько иной
подход. Стандартные методы потока Get и Put требуют загрузки или
записи объекта, производного от TObject. Если вам нужно создать
поток, который состоит не из объектов, переходите на нижний уровень
процедур Read и Write, где в поток записывается или из него
считывается заданное число байт. Этот же механизм используют методы
Get и Put для чтения и записи данных об объектах. Вы просто
обходите механизм VMT, который заложен в Put и Get.
Вопрос: объектный ввод-вывод
Ответ: потоки
Полиморфизм потоков
Потоки обрабатывают объекты
Смысл использования потоков
var
SaveFile: TBufStream;
begin
SaveFile.Init('COLLECT.DTA', stOpen, 1024);
.
.
Чтение из потока и запись в поток
SomeStream.Put(PSomeObject);
где SomeStream - это некоторый производный от TStream объект,
который был инициализирован, а PSomeObject представляет собой
указатель на некоторый производный от TObject объект, который
зарегистрирован с потоком. Это все, что вам нужно сделать. Поток
может из таблицы виртуальных методов PSomeObject узнать, какой это
тип объекта (предполагается, что тип зарегистрирован), поэтому он
знает какой номер идентификатора писать, и сколько после него
будет данных.
PSomeObject := SomeStream.Get;
Закрытие потока
Dispose(SomeStream, Done);
Как сделать объекты потоковыми
Методы загрузки и хранения
type
TMagritte = object(TWindow)
Surreal: Boolean;
constructor Load(var S: TStream);
procedure Store(var S: TStream);
.
.
.
end;
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;
Регистрация потока
PStreamRec = ^TStreamRec;
TStreamRec = record
ObjType: Word;
VmtLink: Word;
Load: Pointer;
Store: Pointer;
Next: Word;
end;
Номера идентификаторов объектов
Автоматические поля
RSomeObject.VmtLink := Ofs(TypeOf(TSomeObject)^);
RSomeObject.Load := @TSomeObject.Load;
RSomeObject.Store := @TSomeObject.Store;
Регистрация на месте
const
RMagritte: TStreamRec = (
ObjType: 100;
VmtLink: Ofs(TypeOf(TMagritte)^);
Load: @TMagritte.Load;
Store: @TMagritte.Store
);
RegisterType(RMagritte);
Регистрация стандартных объектов
Механизм потока
Процесс Put
Процесс Get
Обработка указателей объектов со значением nil
Наборы в потоке: пример
Добавление методов Store
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;
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;
Регистрация записей
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);
Регистрация
procedure StreamRegistration;
begin
RegisterType(RCollection);
RegisterType(RGraphEllipse);
RegisterType(RGraphRect);
RegisterType(RGraphPie);
end;
Запись в поток
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;
Как все хранится?
Поля в потоке
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;
Родство экземпляров окон
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;
Копирование потока
NewStream := New(TEmsStream, Init(OldStream^.GetSize));
OldStream^.Seek(0);
NewStream^.CopyFrom(OldStream, OldStream^.GetSize);
Потоки произвольного доступа
Необъектные элементы потоков