Pascal 7 & Objects

         

Добавление ресурсов к выполняемой программе


-----------------------------------------------------------------

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

Есть три способа добавления ресурсов к выполняемому файлу:

- Можно использовать редактор ресурсов для копирования ре- сурсов из файла .RES в уже скомпилированный файл программы .EXE. Инструкции по этой операции содержатся в руководстве пользователя по пакету разработчика ресурсов.

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

program SampleProgram; {$r SAMPLE.RES} . . .

добавит файл ресурсов SAMPLE.RES к выполняемому файлу. Каждая программа на языке Паскаль может иметь только один файл ресурсов (хотя этот файл ресурсов может включать дру- гие файлы ресурсов). Все эти файлы должны быть файлами .RES и хранить ресурсы в двоичном формате. Директива ком- пилятора $R позволяет вам задать отдельный файл .RES.

- Использовать компилятор ресурсов.



Более подробно о ресурсах


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

Меню и курсоры - это два примера ресурсов программы Windows. Ресурсы это данные, хранимые в выполняемом (.EXE) файле програм- мы, но они располагаются отдельно от обычного сегмента данных программы. Ресурсы разрабатываются и специфицируются вне кода программы, затем добавляются к скомпилированному коду программы для создания выполняемого файла программы.

Следующие ресурсы вы будете создавать и использовать наибо- лее часто:

- Меню. - Блоки диалога. - Пиктограммы. - Курсоры. - Оперативные клавиши. - Графические изображения. - Строки символов.

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



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


-----------------------------------------------------------------



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

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXBitmap drawing testXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| | File Help | +---------------------------------------------------------------| | | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | #X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X#X | | | +---------------------------------------------------------------+

Рис. 18.1. Заполнение области экрана полосками.

При заполнении области на Рис. 18.1 Windows циклически копи- рует кисть. Действительный размер побитового распределения - лишь 8 на 8 элементов изображения, но кистью можно закрасить весь эк- ран.

XXXXXX###### XXXXXX###### XXXXXX######

Рис. 18.2. Ресурс графического изображения для создания кис- ти по образцу Рис. 18.1.

Следующий код помещает образец графического образа в кисть:

procedure SampleWindow.MakeBrush; var MyLogBrush: TLogBrush; begin HMyBit := LoadBitmap(HInstance, PChar(502)); MyLogBrush.lbStyle := bs_Pattern; MyLogBrush.lbHatch := HMyBit; TheBrush := CreateBrushInderect(@MyLogBrush); end;

Для проверки образца, отобразим его в прямоугольнике:

procedure MyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); begin SelectObject(PaintDC, TheBrush); Rectangle(PaintDC, 20, 20, 200, 200); end;

После использования кисти вы должны удалить и кисть, и гра- фическое изображение:

DeleteObject(HMyBit); DeleteObject(TheBrush);



Отображение графических изображений в меню


-----------------------------------------------------------------

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

type MyLong = record case Integer of 0: (TheLong: Longint); 1: (Lo: Word; Hi: Word); end;

constructor SampleWindow.Init(AParent: PWindowsObject; ATitle: PChar); var ALong: MyLong; begin TWindow.Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, PChar(100)); ALong.Lo := LoadBitmap(HInstance, PChar(503)); ModifyMenu(Attr.Menu, 111, mf_ByCommand or mf_Bitmap, 211, PChar(ALong.TheLong)); . . . end;

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

+-----------------------------------------------------------+-+-+ |#=#XXXXXXXXXXXXXXXXXXXXStepsXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|^|v| +-----------------------------------------------------------+-+-| |XXFileXXX Help | +--------------+------------------------------------------------| |XXXXNewXXXXXXX| | | pick | | | me | | | Save | | | Save As | | +--------------+ | | | | | +---------------------------------------------------------------+

Рис. 18.3. Меню, где в качестве одного из пунктов выбора ис- пользовано графическое изображение.



Создание ресурсов


-----------------------------------------------------------------

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

Двоичный файл ресурса (.RES) добавляется к вашему исполняе- мому файлу (.EXE) в процессе компиляции с использованием директи- вы компилятора $R, как это описано в данной главе. Вы также долж- ны написать код, который будет загружать ресурсы в память. Это придаст вам дополнительную гибкость, поскольку ваша программа бу- дет использовать память лишь для ресурсов, которые используются в данный момент. Загрузка ресурсов в память также рассматривается в данной главе.



Загрузка блоков диалога


-----------------------------------------------------------------

Блоки диалога являются единственным типом ресурса, который непосредственно соответствует типу объекта ObjectWindows. TDialog и его производные типы, включая TDlgWindow, определяют объекты интерфейса, которые используют ресурсы блока диалога. Каждый объ- ект блока диалога обычно связан с одним ресурсом блока диалога, который задает его размер, местоположение и ассортимент управляю- щих элементов (таких, как командные кнопки и блоки списка).

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

Adlg := New(PSampleDialog, Init(@Self, 'AboutBox')); или Adlg := New(PSampleDialog, Init(@Self, PChar(120)));

Дополнительная информация по созданию объектов диалога со- держится в Главе 11, "Объекты диалоговых блоков".



Загрузка графических изображений


-----------------------------------------------------------------

Функция Windows LoadBitmap загружает ресурсы графических изображений (битовых отображений). LoadBitmap загружает побитовое распределение в память и возвращает его описатель. Например:

HMyBit:=LoadBitmap(HInstance, PChar(501));

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

В Windows имеется ряд заранее определенных графических изоб- ражений, которые используются как часть графического интерфейса Windows. Ваше приложение может загружать эти изображения (напри- мер, obm_DnArrow, obm_Close и obm_Zoom). Как и предопределенные пиктограммы и курсоры, предопределенные графические изображения могут быть загружены, если в вызове LoadBitmap вместо HInstance задать ноль:

HMyBit:=LoadBitmap(0, PChar(obm_Close));

После загрузки графического образа ваше приложение может ис- пользовать его разными способами:

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

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

* Для отображения картинок вместо текста в элементах меню или элементах блока списка. Например, вы можете вместо слова 'Arrow' (стрелка) в пункте меню поместить изображе- ние стрелки.

Дополнительная информация относительно использования графики с побитовым отображением содержится в Главе 17.

Если побитовое отображение не используется, то его нужно удалить из памяти. В противном случае занимаемая им память будет недоступна другим приложениям. Даже если вы не удаляете его после использования приложением, вы обязательно должны удалить его до прекращения работы приложения. Графический образ удаляется из па- мяти с помощью функции Windows DeleteObject:

if DeleteObject(HMyBit) then { успешно };

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



Загрузка курсоров и пиктограмм


----------------------------------------------------------------- Каждый тип объекта окна имеет специальные атрибуты, называе- мые атрибутами регистрации. Среди этих атрибутов есть курсор окна и пиктограмма. Для установки этих атрибутов для типа окна вы должны определить метод GetWindowClass (как и GetClassName).

Например, вы создаете курсор для выбора элементов в блоке списка. Курсор имеет вид указательного пальца и хранится в ресур- се курсора с именем 'Finger'. Кроме того, вы создаете ресурс пик- тограммы с именем 'SampleIcon', который выглядит как улыбающееся лицо. Вы должны написать метод GetWindowClass следующим образом:

procedure SampleWindow.GetWindowClass(var AWndClass: TWndClass); begin TWindow.GetWindowClass(AWndClass); AWndClass.hCursor:=LoadCursor(HInstance, 'Finger'); AWndClass.hIcon:=LoadIcon(HInstance, 'SampleIcon'); end;

Однако, между курсором и пиктограммой имеется одно отличие. Оно состоит в том, что курсор задается для одного окна, а пиктог- рамма представляет все приложение. Следовательно, пиктограмма ус- танавливается в типе объекта только для основного окна. У этого правила имеется одно исключение: для приложений, которые следуют правилам многодокументального интерфейса (MDI), каждое дочернее окно MDI имеет свою собственную пиктограмму. Для использования одного из уже имеющихся курсоров или пик- тограмм Windows, передайте 0 в HInstance и используйте значение idc_ (например, idc_IBeam) для курсора и значение idi_ (например, idi_Hand) для пиктограммы. Например:

procedure SampleWindow.GetWindowClass(var AWndClass: TWndClass); begin TWindow.GetWindowClass(AWndClass); AWndClass.hCursor := LoadCursor(HInstance, idc_IBeam); AWndClass.hIcon := LoadIcon(HInstance, idi_Hand); end;

Дополнительную информацию по регистрационным атрибутам окна можно найти в Главе 10, "Объекты окна".



Загрузка меню


-----------------------------------------------------------------

Меню окна является одним из атрибутов его создания. Другими словами это характеристика окна, которая должна быть задана до создания соответствующего элемента меню (с помощью метода Create). Следовательно, меню может быть задано в типе конструкто- ра Init или вскоре после конструирования. Ресурсы меню загружают- ся вызовом функции Windows LoadMenu со строкой идентификатора ме- ню при конструировании нового объекта окна. Например:

constructor SampleMainWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin TWindow.Init(AParent, ATitle); Attr.Menu:=LoadMenu(HInstance, PChar(100)); . . end;

Код PChar(100) переводит целое значение 100 в тип PChar, совместимый с Windows тип строки. LoadMenu загружает ресурс меню с идентификатором 100 в новый объект окна. Ресурс может иметь символьное имя (строку), например, 'SampleMenu', а не числовой идентификатор. В этом случае предыдущий код будет выглядеть сле- дующим образом:

constructor SampleMainWindow.Init(AParent: PWindowsObject; ATitle: PChar); begin TWindow.Init(AParent, ATitle); Attr.Menu:=LoadMenu(HInstance, 'SampleMenu'); . . end;

Дополнительная информация по созданию объектов окна содер- жится в Главе 10, "Объекты окна".

Для обработки выбора варианта меню просто определяется метод для окна, которое владеет этим меню, используя специальное расши- рение заголовка определения метода идентификатором cm_First:

procedure HandleMenu101(var Msg: TMessage); virtual cm_First+101;

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



Загрузка оперативных клавиш


-----------------------------------------------------------------

Оперативные клавиши - это активные клавиши или комбинации клавиш, которые используются для задания команд приложения. Обыч- но оперативные клавиши определяются как эквиваленты выбора пунк- тов меню. Например, клавиша Del - это стандартная оперативная клавиша, которую можно использовать как альтернативу выбора пунк- та Delete в меню Edit. Однако, оперативные клавиши могут реализо- вывать команды, которые не соответствуют элементам меню.

Ресурсы оперативных клавиш хранятся в таблице оперативных клавиш. Для загрузки таблицы оперативных клавиш используется функция Windows LoadAccelerators, которая просто возвращает опи- сатель таблицы. В отличие от ресурса меню, который связан с конк- ретным окном, ресурс оперативной клавиши принадлежит всему прило- жению. Каждое приложение может иметь только один такой ресурс. Объекты приложения резервируют одно поле объекта, HAccTable, для хранения описателя ресурса оперативных клавиш. Обычно вы будете загружать ресурс оперативных клавиш в методе объекта приложения InitInstance:

procedure SampleApplication.InitInstance; begin TApplication.InitInstance; HAccTable := LoadAccelerators(HInstance, 'SampleAccelerators'); end;

Часто вы будете определять оперативные клавиши для быстрого выбора вариантов меню. Например, Shift+Ins обычно используется для быстрого выбора команды Paste. Оперативные клавиши генерируют основанные на команде сообщения, которые идентичны сообщениям, генерируемым выбором пункта меню. Для привязки метода реакции на выбор в меню с соответствующей оперативной клавишей нужно убе- диться в том, что определенное в ресурсе значение оперативной клавиши идентично идентификатору элемента меню.



Загрузка ресурсов в приложение


-----------------------------------------------------------------

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



Загрузка строковых ресурсов


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

Для загрузки строки из таблицы в буфер сегмента данных ваше- го приложения используется функция LoadString. Синтаксис LoadString следующий:

LoadString(HInstance, StringID, @TextItem,SizeOf(TextItem));

* Параметр StringID - это номер идентификатора строки (нап- ример, 601) в таблице строк. Это число можно заменить константой.

* Параметр @TextItem - это указатель на массив символов (PChar), который принимает строку.

* Параметр SizeOf(TextItem) - это максимальное число симво- лов, передаваемых в @TextItem. Максимальный размер ресурса строки 255 символов, поэтому передача буфера из 256 симво- лов гарантирует полную передачу строки.

LoadString возвращает число скопированных в буфер символов, или ноль, если ресурс не существует.

Вы можете использовать ресурс строки для вывода текста в блоке сообщения. Например, вы можете вывести сообщение об ошибке. В данном примере вы определяете строку 'Program unavailable' в таблице строк и определяете константу ids_NoProgrm в качестве идентификатора строки. Для использования этого ресурса строки в блоке сообщения об ошибке, вы можете написать следующую процеду- ру:

procedure TestDialog.RunErrorBox(ErrorNumber: Integer); virtual; var TextItem: array[0255] of Char; begin LoadString(HInstance, ids_NoPrgrm, @TextItem, 20); MessageBox(HWindow, @TextItem, 'Error', mb_OK or mb_IconExclamation); end;


Данный пример загружает отдельную строку в блок сообщения об ошибке. Для загрузки списка строк в блок списка вызывается LoadString для загрузки каждой строки, затем вызывается AddString для добавления ее в блок списка.

Другое использование ресурса строк применяется для элементов меню, которые добавляются в меню вашего исходного кода. В этом случае сначала получается ресурс строки с помощью LoadString. За- тем эта строка передается как параметр в вызовы функций Window CreateMenu и AppendMenu. Например:

procedure SampleWindow.Init(AParent: PWindpwsObject; ATitle: PChar); var TextItem: array[0255] of Char; begin TWindow.Init(AParent, ATitle); Attr.Menu := LoadMenu(HInstance, PChar(100)); LoadString(HInstance, 301, @TextItem, 20); AppendMenu(Attr.Menu, mf_String ormf_Enabled, 501, @TextItem); end;


Динамическая установка размеров наборов


-----------------------------------------------------------------

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

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



Наборы


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

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

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



Итератор ForEach


-----------------------------------------------------------------

ForEach воспринимает указатель на процедуру. Процедура имеет один параметр, который является указателем на хранимый в наборе элемент. Для каждого элемента набора ForEach вызывает процедуру один раз, в той последовательности, в которой элементы появляются в наборе. Процедура PrintAll в Collect1 показывает пример итера- тора FoeEach.

procedure PrintAll(C: PCollection); procedure CallPrint(P: PClient); far; begin P^.Print; {Вызов метода Print} end; begin {Print} Writeln; Writeln; Writeln('Client list:'); C^.ForEach(@CallPrint); { распечатка для каждого клиента } end;

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

Примечание: Итераторы должны вызывать локальные проце- дуры far.

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

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

* Быть локальной (вложенной) относительно вызывающей ее прог- раммы.

* Описываться как дальняя процедура директивой far или ди- рективой компилятора {$F+}.

* Воспринимать указатель на элемент набора в качестве своего единственного параметра.



Итераторы FirstThat и LastThat


-----------------------------------------------------------------

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

FirstThat и LastThat возвращают указатель на первый (или последний) элемент, который удовлетворяет условию поиска. Предпо- ложим, что в приведенном ранее примере списка клиентов, вы не мо- жете вспомнить номер счета клиента или не помните точно написание имени клиента. К счастью, вы точно помните, что это был ваш пер- вый клиент из штата Монтана. Следовательно, вы можете организо- вать поиск первого клиента с кодом штата 406 (поскольку ваш спи- сок клиентов ведется хронологически). Данная процедура использует метод FirstThat, который и сделает всю работу:

procedure SearchPhone(C: PCollection; PhoneToFind: PChar); function PhoneMatch(Client: PClient: PClient): Boolean; far; begin PhoneMatch := StrPos(Client^.Phone, PhoneToFind) <> nil; end; var FoundClient: PClient; begin { SearchPhone } Writeln; FoundClient := C^.FirstThat(@PhoneMatch); if FoundClient = nil then Writeln('Такому требованию не отвечает ни один клиент') else begin Writeln('Найден клиент:'); FoundClient^.Print; end; end;

Снова обратите внимание на то, что PhoneMatch вложена и ис- пользует удаленную модель вызова. В этом случае эта функция возв- ращает True только при совпадении номера телефона клиента и за- данного образца поиска. Если в наборе нет объекта, который соот- ветствовал бы критерию поиска, FirstThat возвращает указатель nil.

Запомните: ForEach вызывает определенную пользователем про- цедуру, а FirstThat и LastThat каждая вызывает определенную поль- зователем булевскую функцию. В любом случае определенная пользо- вателем процедура или функция передают указатель на объект набо- ра.



Методы итератора


-----------------------------------------------------------------

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



Наборы и управление памятью


-----------------------------------------------------------------

TCollection может динамически расти от начального размера, установленного Init, до максимального размера в 16380 элементов. ObjectWindows хранит максимальный размер набора в переменной MaxCollectionSize. Каждый добавляемый в набор элемент занимает четыре байта памяти, т.к. он хранится в виде указателя.

Ни одна библиотека динамических структур данных не будет полной, если она не снабжена средствами обнаружения ошибок. Если для инициализации набора не хватает памяти, то возвращается ука- затель nil.

Если не хватает памяти при добавлении элемента в набор, то вызывается метод TCollection.Error, и возникает ошибка этапа вы- полнения в динамически распределяемой области памяти. Вы можете переписать TCollection.Error для организации собственного метода информирования или исправления ошибки.

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



Наборы строк


-----------------------------------------------------------------

Многим программам требуется работать с отсортированными строками. Для этих целей ObjectWindows предоставляет набор специ- ального назначения TStrCollection (он совпадает с типом TStringCollection, определенным для хранения строк Паскаля). Об- ратите внимание, что элементы TStrCollection - это не объекты. Они представляют собой указатели на строки, заканчивающиеся ну- лем. Поскольку наборы строк происходят от TSortedCollection, мож- но хранить и дублированные строки.

Использовать наборы строк несложно. Просто определяется пе- ременная указателя для хранения набора строк. Разместим набор, задав его начальный размер и приращение для роста при добавлении новых строк (см. COLLECT3.PAS):

var WordList: PCollection; WordRead: PChar; . . . begin WordList:=New(PStrCollection, Init(10,5)); . . .

WordList первоначально рассчитан для хранения 10 строк с последующим приращением по 5 строк. Все что вам нужно сделать - это вставить несколько строк в набор. В данном примере слова счи- тываются из текстового файла и вставляются в набор:

repeat . . . if GetWord(WordRead, WordFile)^ <> #0 then WordList^.Insert(StrNew(WordRead)); . . . until WordRead[0]=#0; . . . Dispose(WordList, Done);

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



Объединение в набор элементов, не являющихся объектами


-----------------------------------------------------------------

Вы даже можете добавить в набор нечто, что вообще не являет- ся объектом, но это также может явиться серьезным предметом оза- боченности. Наборы ожидают получения нетипизированных указателей незаданного типа на нечто. Но некоторые методы TCollection пред- назначены специально для работы с наборами элементов, производных от TObject. Это касается методов доступа к потоку PutItem и GetItem, и стандартной процедуры FreeItem.

Например, это означает, что вы можете хранить PChar в набо- ре, но при попытке послать этот набор в поток, результаты будут не столь успешными, если вы не перепишете стандартные методы на- бора GetItem и PutItem. Аналогично, при попытке освобождения на- бора будет сделана попытка удаления каждого элемента с помощью FreeItem. Например, это делает TStrCollection.

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



Объекты наборов


-----------------------------------------------------------------

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



Отсортированные наборы


-----------------------------------------------------------------

Иногда вам бывает нужно, чтобы ваши данные были определенным образом отсортированы. ObjectWindows имеет специальный тип набо- ра, который позволяет вам упорядочить ваши данные произвольным образом. Это тип TSortedCollection.

TSortedCollection является производным от TCollection и ав- томатически сортирует задаваемые ему объекты. При добавлении но- вого элемента он автоматически проверяет набор на дублирование ключей. Булевское поле Duplicates контролирует разрешение дубли- рования ключей. Если для поля Duplicates установлено значение False (по умолчанию), то новый элемент добавляется к набору, за- меняя существующий член с тем же самым ключом. Если Duplicates имеет значение True, то новый член просто вставляется в набор.

TSortedCollection - это набор абстрактного типа. Для его ис- пользования вы должны сначала решить, какой тип данных вы собира- етесь собирать и определить два метода, отвечающих вашим конкрет- ным требованиям сортировки. Для этого вам нужно создать новый тип, производный от TSortedCollection. В данном случае назовем его TClientCollection. Ваш TClientCollection уже знает, как де- лать всю реальную работу с набором. Он может вставить (Insert) запись о новом клиенте и удалять (Delete) существующие записи - он унаследовал эти основные черты поведения от TCollection. Все что нужно сделать - это научить TClientCollection, какое поле ис- пользовать в качестве ключа сортировки и как сравнивать двух кли- ентов при решении вопроса о том, какой из них должен стоять в на- боре выше другого. Это делается переписыванием методов KeyOf и Compare и реализации их следующим образом:

PClientCollection = ^TClientCollection; TClientCollection = object(TSortedCollection) function KeyOf(Item: Pointer): Pointer; virtual; function Compare(Key1, Key2: Pointer): Integer; virtual; end;

function TClientCollection.KeyOf(Item: Pointer): Pointer; begin KeyOf := PClient(Item)^.Account; end;

function TClientCollection.Compare(Key1, Key2: Pointer): Integer; begin Compare := StrIComp(PChar(Key1), PChar(Key2)); end;


Примечание: Так как ключи являются нетипизированными указателями, для них нужно выполнять приведение типа.

KeyOf определяет, какое поле или поля используются в качест- ве ключей сортировки. В данном случае это поле клиента Account. Compare воспринимает два ключа сортировки и определяет, какой из них должен идти первым в соответствии с правилами сортировки. Compare возвращает -1, 0 или 1 в зависимости от того, Key1 мень- ше, равен или больше Key2, соответственно. В данном примере ис- пользуется сортировка по алфавиту (для букв верхнего и нижнего регистра) ключевой строки (Account) путем вызова модуля Strings функции StrIComp. Вы можете легко сортировать набор по именам, вместо номера счета, если замените возвращаемое KeyOf поле на Name.

Обратите внимание на то, что ключи, возвращаемые KeyOf и пе- редаваемые в Compare являются нетипизированными указателями, поэ- тому до их разыменования и передачи в StrIComp в данном примере вы должны привести их тип к PChar.

Это практически все, что вам нужно определить! Теперь, если вы переопределите ClientList как PClientCollection вместо PCollection (сменив объявление var и вызов New), то легко сможете распечатать ваших клиентов в алфавитном порядке (COLLECT2.PAS):

var ClientList: PClientCollection; . . begin ClientList:=New(PClientCollection, Init(10,5)); . . end.

Обратите внимание и на то, как легко будет сменить сортиров- ку списка клиентов по номеру счета на сортировку по имени. Все что вам нужно сделать, это сменить метод KeyOf на возврат поля Account на поле Name.


Пересмотренные итераторы


-----------------------------------------------------------------

Метод ForEach просматривает весь набор, элемент за элемен- том, и выполняет над каждым из них заданную процедуру. В предыду- щем примере процедуре PrintWord передавался указатель строки для ее отображения. Обратите внимание, что процедура PrintWord вло- женная (или локальная). Она работает в другой процедуре, Print, которой передается указатель на TstrCollection. Print использует метод итератора ForEach для передачи каждого элемента своего на- бора в процедуру PrintWord.

procedure Print(C: PCollection); procedure PrintWord(P: PChar); far; begin Writeln(P); { вывести строку } end; begin {Print} Writeln; Writeln; C^.ForEach(@PrintWord); { вызов PrintWord } end;

PrintWord должен выглядеть как уже знакомая процедура. Она просто берет указатель строки и передает его значение Writeln. Обратите внимание на директиву far после описания PrintWord. PrintWord не может быть методом, это просто процедура. Кроме того это должна быть вложенная процедура. Print надо рассматривать как некую оболочку вокруг процедуры, которая выполняет некоторую ра- боту над каждым элементом набора (может быть отображает или моди- фицирует данные). Вы можете иметь несколько аналогичных PrintWord процедур, но каждая из них должна быть вложена в Print и должна быть дальней процедурой (использовать директиву far или {$F+}).

Нахождение элемента

Отсортированные наборы (и следовательно наборы строк) имеют метод Search, который возвращает индекс элемента с конкретным значением ключа. Но как найти элемент в неотсортированном наборе? Или когда критерий поиска не использует сам ключ? Конечно же, следует использовать FirstThat и LastThat. Вы просто определяете булевскую функцию для проверки нужного вам критерия и вызываете FirstThat.



Полиморфические наборы


-----------------------------------------------------------------

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

Теперь пришло время рассмотреть реальные возможности набо- ров, элементы могут обрабатываться полиморфически. Это значит, что вы не просто можете хранить определенный тип объекта в набо- ре; вы можете хранить несколько разных типов объектов, взятых произвольно из вашей иерархии объектов.

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

В качестве примера рассмотрим программу, которая помещает в набор три различных графических объекта. Затем итератор ForEach используется для просмотра набора и отображения каждого объекта. В отличие от других примеров данной главы данный пример (Collect4) использует функции Windows для рисования в окне. Обя- зательно включите WinProcs и WinTypes в uses данного примера. Сначала определяется абстрактный объект-предок (см. COLLECT4.PAS).

type PGraphObject = ^TGraphObject; TGraphObject = object(TObject) Rect: TRect; constructor Init(Bounds: TRect); procedure Draw(DC: HDC); virtual; end;

Из этого объявления вы можете видеть, что каждый графический объект может инициализировать себя (Init) и отобразить себя на графическом экране (Draw). Теперь определим эллипс, прямоугольник и сектор как производные от этого общего предка:


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; end;

Все эти три типа объекта наследуют поле Rect из TGraphObject, но все они разного размера. TGraphEllipse и TGraphRect нужно только добавить их новые методы рисования, т.к. их методам рисования нужны только размеры и расположение, а TGraphPie нужны дополнительные поля и другой конструктор для их корректного представления. Приведем исходный код для помещения этих фигур в набор:

. . . GraphicsList := New(PCollection, Init(10,5)); { создать набор } for I := 1 to NumToDraw do begin case I mod 3 of { создать объект } 0: P := New(GraphRect, Init(Bounds)); 1: P := New(GraphEllipse, Init(Bounds)); 2: P := New(GraphPie, Init(Bounds)); end; GraphicsList^.Insert(P); { добавить в набор } end; . .

Как вы можете видеть цикл, for вставляет графические объекты в набор GraphicsList. Вы знаете только то, что каждый объект в GraphicsList представляет собой некоторый вид TGraphObject. После помещения в набор у вас уже нет информации о том, является ли элемент набора прямоугольником, эллипсом или сектором. Благодаря полиморфизму, вам этого и не нужно знать, поскольку каждый объект содержит все данные и код (Draw), который ему нужен. Просмотрим набор с использованием итеративного метода и каждый набор будет сам отображать себя:

procedure DrawAll(C: PCollection);

procedure CallDraw(P: PGraphObject); far; begin P^.Draw(PaintDC); { вызов метода Draw } end;

begin {DrawAll} C^.ForEach(@CallDraw); { прорисовать каждый объект } end;

var GraphicsList: PCollection; begin . . . if GraphicsList <> nil then DrawAll(GraphicsList); . . . end.

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


Полиморфизм наборов


-----------------------------------------------------------------

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

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



Проверка типа и наборы


-----------------------------------------------------------------

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

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



Создание набора


-----------------------------------------------------------------

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

type PClient=^TClient; TClient=object(TObject) Account, Name, Phone: PChar; constructor Init(NewAccount, NewName, NewPhone: PChar); destructor Done; virtual; procedure Print; virtual; end;

Затем реализуем методы Init и Done для размещения и удаления данных о клиенте и метод Print для отображения данных о клиенте в виде таблицы. Обратите внимание, что поля объекта имеют тип PChar, поэтому память выделяется только для той части строки, ко- торая действительно используется. Функции StrNew и StrDispose очень эффективно обрабатывают динамические строки.

constructor TClient.Init(NewAccount, NewName, NewPhone: PChar); begin Account := StrNew(NewAccount); Name := StrNew(NewName); Phone := StrNew(NewPhone); end;

destructor TClientDone; begin StrDispose(Account); StrDispose(Name); StrDispose(Phone); end;

procedure TClient.Print; begin Writeln( ' ', Account, '':10 - StrLen(Account), Name, '':20 - StrLen(Name), Phone, '':16 - StrLen(Phone)); end;

TClient.Done будет автоматически вызываться для каждого кли- ента при удалении всего набора. Сейчас вы просто инициируете на- бор для хранения ваших клиентов и вставляете в него записи о кли- ентах. Головное тело программы (COLLECT1.PAS) будет выглядеть следующим образом:

var ClientList: PCollection; begin ClientList:=New(PCollection, Init(10,5)); with ClientList^ do begin Insert(New(PClient, Init('91-100', 'Anders, Smitty', '(406) 111-2222'))); Insert(New(PClient, Init('90-167', 'Smith, Zelda', '(800) 555-1212'))); Insert(New(PClient, Init('90-177', 'Smitty, John', '(406) 987-4321'))); Insert(New(PClient, Init('90-160', 'Johnson, Agatha', '(302) 139-8913'))); end; PrintAll(ClientList); SearchPhone(ClientList, '(406)'); Dispose(ClientList, Done); end.

Примечание: Процедуры PrintAll и SearchPhone будут рассмотрены позднее.

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

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



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


-----------------------------------------------------------------

Поле VmtLink это связь с таблицей виртуальных методов объек- тов (VMT). Вы просто задаете его как отклонение типа вашего объ- екта:

RSomeObject.VmtLink := Ofs(TypeOf(TSomeObject)^);

Поля Load и Store содержат соответственно адреса методов Load и Store.

RSomeObject.Load := @TSomeObject.Load; RSomeObject.Store := @TSomeObject.Store;

Значение последнего поля, Next, задается RegisterType и не требует никакого вмешательства с вашей стороны. Оно просто обес- печивает внутреннее использование скомпонованного списка регист- рационных записей потока.



Чтение из потока и запись в поток


-----------------------------------------------------------------

Основной объект потока 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.



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

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

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

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



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


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

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

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



Копирование потока


-----------------------------------------------------------------

TStream имеет метод CopyFrom(S,Count), который копирует за- данное число байт (Count) из заданного потока S. Метод CopyFrom может быть использован для копирования содержимого одного потока в другой. Если, например, вы циклически обращаетесь к дисковому потоку, то можете скопировать его в поток EMS для организации бо- лее быстрого доступа:

NewStream := New(TEmsStream, Init(OldStream^.GetSize)); OldStream^.Seek(0); NewStream^.CopyFrom(OldStream, OldStream^.GetSize);



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


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

Действительное чтение и запись объектов в поток производится методами 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.



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


-----------------------------------------------------------------

В Главе 19, "Наборы", вы уже видели как наборы могут содер- жать разные, но связанные объекты. Это свойство полиморфизма так- же применимо и к потокам, и их можно использовать для записи на- боров на диск для последующего обращения, даже в другой програм- ме. Вернемся к примеру COLLECT4.PAS. Что еще нужно добавить в эту программу для помещения набора в поток?

Ответ будет очень простым. Сначала возьмем базовый объект TGraphObject и "научим" его хранить его данные (X и Y) в потоке. Для этого нужен метод Store. Затем определим новый метод Store для любого производного от TGraphObject объекта, в котором до- бавляются дополнительные поля (например, TGraphPie добавляет ArcStart и ArcEnd).

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



Необъектные элементы потоков


-----------------------------------------------------------------

В поток можно записывать и элементы, которые не являются объектами, но для этого следует использовать несколько иной под- ход. Стандартные методы потока Get и Put требуют загрузки или за- писи объекта, производного от TObject. Если вам нужно создать по- ток, который состоит не из объектов, переходите на нижний уровень процедур Read и Write, где в поток записывается или из него счи- тывается заданное число байт. Этот же механизм используют методы Get и Put для чтения и записи данных об объектах. Вы просто обхо- дите механизм VMT, который заложен в Put и Get.



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


-----------------------------------------------------------------

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

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



Обработка ошибок потока


-----------------------------------------------------------------

TStream имеет метод Error(Code, Info), который вызывается при обнаружении ошибки потока. Error просто присваивает полю Status потока значение одной из констант, приведенных в Главе 21 "Справочник по ObjectWindows" в разделе "Константы stXXXX".

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



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


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

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



Поля в потоке


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

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

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

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



Потоки произвольного доступа


-----------------------------------------------------------------

До этого момента мы работали с потоками как с устройствами последовательного доступа: вы помещали (Put) объекты в конец ва- шего потока и считывали их назад (Get) в той же последовательнос- ти. Но ObjectWindows имеет и более мощные средства. Имеется воз- можность рассматривать поток как виртуальное устройство произ- вольного доступа. Кроме методов Get и Put, которые соответствуют Read и Write при работе с файлом, потоки обладают средствами про- ведения операций Seek, FilePos, FileSize и Truncate.

- Процедура потока Seek перемещает текущий указатель потока к заданной позиции (число байт от начала потока), как стандартная процедура Seek языка Паскаль.

- Процедура GetPos по своему действию обратна процедуре Seek. Она возвращает значение Longint с текущей позицией потока.

- Функция GetSize возвращает размер потока в байтах.

- Процедура Truncate удаляет все данные, которые расположены после текущей позиции потока, при этом текущая позиция по- тока становится концом потока.

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



Процесс Get


-----------------------------------------------------------------

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

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

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



Процесс Put


-----------------------------------------------------------------

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

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



Разработка пользователем собственных потоков


-----------------------------------------------------------------

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

Сам TStream является абстрактным объектом и его можно расши- рить для создания удобного типа потока. Большинство методов TStream являются абстрактными и должны быть реализованы как их производные методы, основывающиеся на абстрактных методах TStream. Полностью реализованы только методы Error, Get и Put. GetPos, GetSize, Read, Seek, SetPos, Truncate и Write должны быть переписаны. Если производный тип объекта имеет буфер, то должен быть переписан и метод Flush.



Регистрация


-----------------------------------------------------------------

Всегда нужно зарегистрировать каждую из этих записей до про- ведения каких-либо операций ввода-вывода с потоком. Самый простой способ сделать это состоит в том, чтобы объединить их все в одну процедуру и вызвать ее в самом начале вашей программы (или в ме- тоде Init вашего приложения):

procedure StreamRegistration; begin RegisterType(RCollection); RegisterType(RGraphEllipse); RegisterType(RGraphRect); RegisterType(RGraphPie); end;

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



Регистрация на месте


-----------------------------------------------------------------

После конструирования регистрационной записи потока вы вызы- ваете RegisterType с вашей записью в качестве параметра. Поэтому для регистрации вашего нового объекта TMagritte для его использо- вания в потоке вы включаете следующий код:

const RMagritte: TStreamRec = ( ObjType: 100; VmtLink: Ofs(TypeOf(TMagritte)^); Load: @TMagritte.Load; Store: @TMagritte.Store ); RegisterType(RMagritte);

Вот и все. Теперь вы можете помещать (Put) экземпляры вашего нового типа объекта в любой поток ObjectWindows и считывать их из потоков.



Регистрация потока


-----------------------------------------------------------------

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



Регистрация стандартных объектов


-----------------------------------------------------------------

ObjectWindows определяет регистрационные записи потоков для всех его стандартных объектов. Кроме того, модуль WObjects опре- деляет процедуру RegisterWObjects, которая автоматически регист- рирует все объекты этого модуля. Например, модуль OWindows содер- жит процедуру RegisterOWindows, а ODialogs - RegisterODialogs.



Регистрация записей


-----------------------------------------------------------------

Наш последний шаг состоит в определении константы регистра- ционной записи для каждого производного типа. Хороший прием прог- раммирования состоит в следовании соглашению 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).



Родство экземпляров окон


-----------------------------------------------------------------

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



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


-----------------------------------------------------------------

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

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

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

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

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

Например, для инициализации буферизированного потока DOS при загрузке набора объектов в программу, все что вам нужно это:

var SaveFile: TBufStream; begin SaveFile.Init('COLLECT.DTA', stOpen, 1024); . .

После инициализации потока все готово к работе.

TStream это абстрактный механизм потока, поэтому вы будет работать не с ним, а с производными от TStream удобными объектами потока. Это будет, например, TDosStream, для выполнения дисковых операций ввода-вывода, TBufStream для буферизованных операций ввода-вывода (очень удобен для частых операций считывания или за- писи небольших объемов информации на диск) и TEmsStream для пере- дачи объектов в память EMS. Кроме того, ObjectWindows реализует индексированные потоки с указателем, указывающим место в потоке. Перемещая этот указатель вы можете организовать произвольный дос- туп в потоке.



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


-----------------------------------------------------------------

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

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

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

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



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


-----------------------------------------------------------------

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

Dispose(SomeStream, Done);

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



Запись в поток


-----------------------------------------------------------------

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