Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
Бесплатный конструктор сайтов и Landing Page

Хостинг с DDoS защитой от 2.5$ + Бесплатный SSL и Домен

SSD VPS в Нидерландах под различные задачи от 2.6$

✅ Дешевый VPS-хостинг на AMD EPYC: 1vCore, 3GB DDR4, 15GB NVMe всего за €3,50!

🔥 Anti-DDoS защита 12 Тбит/с!

VPS в России, Европе и США

Бесплатная поддержка и администрирование

Оплата российскими и международными картами

🔥 VPS до 5.7 ГГц под любые задачи с AntiDDoS в 7 локациях

💸 Гифткод CITFORUM (250р на баланс) и попробуйте уже сейчас!

🛒 Скидка 15% на первый платеж (в течение 24ч)

Глава 20. Потоки

             Техника объектно-ориентированного     программирования     и
        ObjectWindows дают  вам  мощные средства инкапсуляции кода и дан-
        ных  и большие  возможности построения  взаимосвязанных  структур
        объектов.  Но что делать, если стоит простая задача, например, по
        хранению некоторых объектов на диске?

             Когда-то данные хранились исключительно в записях, и помеще-
        ние данных на диск было тривиальной задачей. Но данные в програм-
        мах ObjectWindows неразрывно связаны с объектами. Конечно, вы мо-
        жете отделить  данные  от  объекта и записать их в дисковый файл.
        Объединение дает вам значительный шаг в направлении прогресса,  а
        разъединение отбрасывает вас назад.

             Есть ли  в самом объектно-ориентированном программировании и
        ObjectWindows некоторые средства,  которые могли бы разрешить эту
        проблему? Есть, и это потоки.

             Поток в  ObjectWindows  -  это набор объектов на их пути ку-
        да-либо:  обычно в файл, EMS, в последовательный порт или некото-
        рое  другое устройство.  Потоки обслуживают операции ввода-вывода
        на уровне объектов, а не на уровне данных. При расширении объекта
        ObjectWindows  вам  нужно  обеспечить обработку определенных вами
        дополнительных полей.  Все сложные аспекты  обработки  на  уровне
        объектов будут проделаны за вас.

                             Вопрос: объектный ввод-вывод
        -----------------------------------------------------------------

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

             Паскаль реализует в этой связи очень удобное правило:  можно
        организовать  доступ  к  файлу  неопределенного  типа  с  помощью
        процедур BlockWrite и BlockRead.  Отсутствие проверки типа возла-
        гает  некоторую  дополнительную  ответственность на программиста,
        хотя позволяет очень быстро выполнять двоичные операции ввода-вы-
        вода.

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

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

                                    Ответ: потоки
        -----------------------------------------------------------------

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

                                 Полиморфизм потоков
        -----------------------------------------------------------------

             Потоки ObjectWindows позволяют вам работать с файлами  опре-
        деленного и неопределенного типа:  проверка типа имеется,  но тип
        посылаемого объекта не должен обязательно определяться  во  время
        компиляции.  Смысл в том,  что потоки знают, что они имеют дело с
        объектами, и  поскольку  все  объекты  являются  производными  от
        TObject,  поток может их обработать. В действительности различные
        объекты ObjectWindows могут также легко записываться  в один  по-
        ток, как и группы идентичных объектов.

                             Потоки обрабатывают объекты
        -----------------------------------------------------------------

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

             Но каким образом один и тот же поток может считывать и запи-
        сывать  такие  разные объекты как TCollection и TDialog,  даже не
        зная в момент компиляции,  какие типы объектов он будет обрабаты-
        вать?  Это  существенно  отличается от традиционных операций вво-
        да-вывода языка Паскаль.  В действительности потоки могут обраба-
        тывать даже новые типы объектов,  которые вообще еще не были соз-
        даны к моменту компиляции потока.

             Ответом на это является так называемая регистрация.  Каждому
        типу  объекта  ObjectWindows (или любому новому производному типу
        объекта) присваивается уникальный регистрационный номер. Этот но-
        мер записывается в поток перед данными объекта. Затем, при считы-
        вании объекта из потока,  ObjectWindows сначала берет регистраци-
        онный номер и на его основании узнает,  сколько данных нужно счи-
        тывать и какие таблицы виртуальных методов подключать к данным.


                             Смысл использования потоков
        -----------------------------------------------------------------

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

             Потоки (как и файлы Паскаля) можно также просматривать,  как
        устройства ввода-вывода произвольного доступа,  искать определен-
        ное  место в файле,  считывать данные в этой точке или записывать
        данные в эту точку, возвращать позицию указателя файла и т.д. Все
        эти операции можно выполнять с потоками,  и они описаны в разделе
        "Потоки с произвольным доступом".

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

                                Установка потока

             Все что нужно сделать для использования потока - это инициа-
        лизировать  его.  Точный  синтаксис  конструктора Init может быть
        разным,  в зависимости от типа потока,  с которым вы имеете дело.
        Например,  если  вы открываете поток DOS,  вам нужно передать имя
        файла DOS и режим доступа (только  чтение,  только  запись,  чте-
        ние/запись) для содержащего поток файла.

             Например, для инициализации буферизированного потока 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.  Общий  синтаксис
        метода Put следующий:

             SomeStream.Put(PSomeObject);

        где SomeStream - это некоторый производный от TStream объект, ко-
        торый был инициализирован,  а PSomeObject представляет собой ука-
        затель на некоторый производный от TObject объект,  который заре-
        гистрирован с потоком.  Это все, что вам нужно сделать. Поток мо-
        жет из таблицы виртуальных методов PSomeObject узнать,  какой это
        тип объекта (предполагается, что тип зарегистрирован), поэтому он
        знает какой номер идентификатора писать, и сколько после него бу-
        дет данных.

             Специальный интерес для вас,  как для программиста, работаю-
        щего с ObjectWindows,  состоит в том факте,  что при помещении  в
        поток группы с дочерними окнами, дочерние окна также автоматичес-
        ки помещаются в поток.  Таким образом, запись сложных объектов не
        так уж и сложна, более того, это делается автоматически! Вы може-
        те сохранить полное состояние диалога простым помещением  объекта
        диалога в поток. При повторном запуске вашей программы и загрузке
        диалога будет выведено его состояние в момент записи.

                                    Метод Get

             Считывание объектов из потока столь же просто.  Все что  вам
        нужно сделать, это вызвать функцию Get:

             PSomeObject := SomeStream.Get;

        где SomeStream  - это инициализированный  поток ObjectWindows,  а
        PSomeObject - указатель на некоторый тип  объекта  ObjectWindows.
        Get просто возвращает указатель на нечто,  что он взял из потока.
        Сколько данных было взято и какой тип таблицы виртуальных методов
        (VMT) присвоен данным, определяется не типом PSomeObject, а типом
        объекта,  обнаруженным в потоке. Следовательно, если объект в те-
        кущей  позиции SomeStream имеет не совпадающий с PSomeObject тип,
        у вас будет некорректная информация.

             Как и Put,  Get ищет сложные объекты. Следовательно, если вы
        ищите  в  потоке окно,  которое владеет дочерними окнами,  то они
        также будут загружены.

                                   Метод Error

             И, наконец,  процедура Error определяет что  происходит  при
        возникновении  ошибки  потока.  По умолчанию TStream.Error просто
        устанавливает значение двух полей в потоке (Status и  ErrorInfo).
        Если  вы хотите сделать что-либо более содержательное,  например,
        сгенерировать соответствующее сообщение о сбое в работе программы
        или вывести блок диалога c сообщением об ошибке, то вам нужно пе-
        реопределить процедуру Error.

                                   Закрытие потока
        -----------------------------------------------------------------

             Когда вы закончили использование потока,  вы  вызываете  его
        метод Done, как вы обычно вызывали Close для дискового файла. Как
        и для других объектов ObjectWindows, это делается следующим обра-
        зом:

             Dispose(SomeStream, Done);

        как для уничтожения объекта потока, так и для его закрытия.

                            Как сделать объекты потоковыми
        -----------------------------------------------------------------

             Все стандартные объекты ObjectWindows готовы к использованию
        в потоках, и все потоки ObjectWindows узнают стандартные объекты.
        При изготовлении нового типа объекта, производного от стандартно-
        го объекта, его очень просто подготовить к использованию в потоке
        и известить потоки о его существовании.

                              Методы загрузки и хранения
        -----------------------------------------------------------------

             Действительное чтение и запись объектов в поток производится
        методами Load и Store.  Каждый объект должен иметь эти методы для
        использования  потока,  поэтому  вы никогда не будете вызывать их
        непосредственно (они вызываются из методов Get и  Put.)  Все  что
        вам  нужно сделать,  это убедиться в том,  что объект знает,  как
        послать себя в поток, когда это потребуется.

             Благодаря объектно-ориентированному программированию это де-
        лается очень просто,  т.к.  большинство механизмов наследуются от
        объекта-предка. Все что должен делать ваш объект, это загружать и
        хранить те свои компоненты,  которые вы в него добавляете, об ос-
        тальном позаботится метод предка.  Например,  вы  производите  от
        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
        не имеют регистрационных записей,  поскольку их экземпляры вы ни-
        когда не будете хранить в потоках.

                           Номера идентификаторов объектов
        -----------------------------------------------------------------

             Вам действительно нужно думать только о поле ObjType записи,
        все остальное делается механически.  Каждому новому определяемому
        вами типу требуется его собственный уникальный идентификатор типа
        в виде числа. ObjectWindows резервирует регистрационные номера от
        0 до 99 для стандартных объектов,  поэтому  ваши  регистрационные
        номера  будут лежать в диапазоне от 100 до 65535.

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

                                 Автоматические поля
        -----------------------------------------------------------------

             Поле 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,  которая автоматически регист-
        рирует все объекты этого модуля. Например, модуль OWindows содер-
        жит процедуру RegisterOWindows, а ODialogs - RegisterODialogs.

                                   Механизм потока
        -----------------------------------------------------------------

             После того, как мы посмотрели на процесс использования пото-
        ков,  следует заглянуть во внутреннюю работу,  которую производит
        ObjectWindows c вашими объектами с помощью методов Put и Get. Это
        прекрасный  пример взаимодействия объектов и использования встро-
        енных в них методов.

                                     Процесс Put
        -----------------------------------------------------------------

             Когда вы посылаете объект в поток с помощью метода Put,  по-
        ток сначала берет указатель VMT со смещением 0 от объекта и прос-
        матривает список зарегистрированных типов потоков системы с целью
        найти совпадение.  Когда это совпадение найдено,  поток ищет  ре-
        гистрационный номер идентификатора объекта и записывает его в по-
        ток.  Затем поток вызывает метод Store объекта для завершения за-
        писи объекта.  Метод Store использует процедуру потока Write, ко-
        торая действительно пишет корректное число байт в поток.

             Ваш объект не должен ничего знать о потоке - это может  быть
        файл на диске,  память EMS или любой другой вид потока - ваш объ-
        ект просто говорит "запишите меня в поток",  и поток  делает  все
        остальное.

                                     Процесс Get
        -----------------------------------------------------------------

             Когда вы считываете объект из потока с помощью  метода  Get,
        сначала ищется  номер  его  идентификатора,  и просматривается на
        совпадение список  зарегистрированных  типов.  После  обнаружения
        совпадения  регистрационная запись дает потоку местоположение ме-
        тода Load объект и VMT. Затем для чтения нужного объема данных из
        потока вызывается метод Load.

             Вы опять просто говорите потоку,  что нужно взять (Get) сле-
        дующий объект и поместить его в место, определяемое заданным вами
        указателем. Ваш объект даже не беспокоится о том, с каким потоком
        он имеет дело.  Поток сам беспокоится о считывании нужного объема
        данных  из  потока с помощью метода объекта Load,  который в свою
        очередь опирается на метод потока Read.

             Для программиста все это достаточно прозрачно,  но в  то  же
        время вы ясно должны понять, насколько важно зарегистрировать тип
        до проведения каких-либо попыток ввода-вывода с потоком.

                    Обработка указателей объектов со значением nil
        -----------------------------------------------------------------

             Вы можете записать в поток объект nil. Однако, если это сде-
        лать, то в поток запишется слово 0. При считывании идентификатора
        слова 0 поток возвратит указатель nil.  Поэтому 0 считается заре-
        зервированным и не может использоваться в качестве номера иденти-
        фикатора объекта потока.  ObjectWindows резервирует идентификатор
        потока от 0 до 99 для внутреннего использования.

                               Наборы в потоке: пример
        -----------------------------------------------------------------

             В Главе 19,  "Наборы", вы уже видели как наборы могут содер-
        жать разные, но связанные объекты. Это свойство полиморфизма так-
        же применимо и к потокам,  и их можно использовать для записи на-
        боров на диск для последующего обращения,  даже в другой програм-
        ме. Вернемся к примеру 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.

             Помните о том,  что каждой регистрационной записи присваива-
        ется уникальный номер идентификатора объекта (ObjType). Номера от
        0 до 99 резервируются ObjectWindows для стандартных объектов. Хо-
        рошо бы отслеживать все номера идентификаторов ваших объектов по-
        тока в некотором центральном месте, чтобы избежать дублирования.

             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) "маги-
        чески"  восстанавливаются  все  скрытые связи между набором и его
        элементами,  объекты и их таблицы виртуальных методов.  Следующий
        раздел поясняет,  как помещать в поток объекты,  которые содержат
        связи с другими объектами.

                                  Как все хранится?
        -----------------------------------------------------------------

             Относительно потоков нужно сделать  важное  предостережение:
        только  владелец объекта должен записывать его в поток.  Это пре-
        достережение  аналогично  традиционному   предостережению   языка
        Паскаль, которое вам должно быть известно: только владелец указа-
        теля может уничтожить его.

             В реальных сложных приложениях множество объектов часто име-
        ют  указатель на конкретную структуру.  Когда возникает необходи-
        мость в выполнении операций ввода-вывода,  вы должны решить,  кто
        "владеет" структурой. Только этот владелец должен посылать струк-
        туру в поток.  Иначе у вас может получиться несколько копий одной
        структуры  в  потоке.  При считывании такого потока будет создано
        несколько экземпляров структуры,  и каждый из первоначальных объ-
        ектов будет указывать на собственную персональную копию структуры
        вместо единственной первоначальной структуры.

                                    Поля в потоке
        -----------------------------------------------------------------

             Много раз вы видели,  что удобно хранить указатель на дочер-
        ние окна группы в локальной  переменной  (поле  данных  объекта).
        Например, блок диалога может хранить указатель на его объекты уп-
        равления в полях с мнемоническими именами для более удобного дос-
        тупа (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 языка Паскаль.

             - Процедура  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 потока значение одной из констант,  приведенных в Главе 21
        "Справочник по ObjectWindows" в разделе "Константы stXXXX".

             Поле ErrorInfo  не определено,  если значение Status не есть
        stGetError  или  stPutError.  Если  значение  поля  Status  равно
        stGetError, то поле ErrorInfo содержит номер идентификатора пото-
        ка незарегистрированного типа.  Если значение поля  Status  равно
        stPutError, то поле ErrorInfo содержит смещение VMT типа, который
        вы пытались поместить в поток. Вы можете переписать TStream.Error
        для генерации любого уровня обработки ошибок, включая ошибки эта-
        па выполнения.  
                              Назад | Содержание | Вперед
Скидка до 20% на услуги дата-центра. Аренда серверной стойки. Colocation от 1U!

Миграция в облако #SotelCloud. Виртуальный сервер в облаке. Выбрать конфигурацию на сайте!

Виртуальная АТС для вашего бизнеса. Приветственные бонусы для новых клиентов!

Виртуальные VPS серверы в РФ и ЕС

Dedicated серверы в РФ и ЕС

По промокоду CITFORUM скидка 30% на заказ VPS\VDS

VPS/VDS серверы. 30 локаций на выбор

Серверы VPS/VDS с большим диском

Хорошие условия для реселлеров

4VPS.SU - VPS в 17-ти странах

2Gbit/s безлимит

Современное железо!

Новости мира IT:

Архив новостей

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 495 7861149
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2019 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...