ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
Часть 19 Глава 17. Объекты Внутренние форматы данных объектов Внутренний формат данных объекта имеет сходство с внутренним форматом записи. Поля объекта записываются в порядке их описаний как непрерывная последовательность переменных. Любое поле, унаследованное от родительского (порождающего) типа, записывается пер ед новыми полями, определенными в производном (порожденном) типе. Если объектный тип определяет виртуальные методы, конструктор или деструктор, то компилятор размещает в объектном типе дополнительное поле данных. Это 16-битовое поле, называемое полем таблицы виртуальных методов (ТВМ), используется для запоминания смещения таблицы виртуальных методов в сегменте данных. Поле таблицы виртуальных методов следует непосредственно после обычных полей объектного типа. Если объектный тип наследует виртуальные методы, конструкторы или деструкторы, то он также наследует и п оле ТВМ, благодаря чему дополнительное поле таблицы виртуальных методов не размещается. Инициализация поля таблицы виртуальных методов экземпляра объекта осуществляется конструктором (или конструкторами) объектного типа. Программа никогда не инициализирует поле таблицы виртуальных методов явно и не имеет к нему доступа. Следующие примеры иллюстрируют внутренние форматы данных объектных типов. type TLocationPtr = ^Location; PLocation = object X,Y: integer; procedure Init(PX, PY: integer); function GetX: integer; function GetY: integer; end; PPointPtr = ^Point; TPoint = object(Location) Color: integer; constructor Init(PX, PY, PColor: integer); destructor Done; virtual; procedure Show; virtual; procedure Hide; virtual; procedure MoveTo(PX, PY: integer); virtual; end; PCirclePtr = ^Circle; TCircle = object(Point) Radius: integer; constructor Init(PX, PY, PColor, PRadius: integer); procedure Show; virtual; procedure Hide; virtual; procedure Fill; virtual; end; Рисунок 17.1 показывает размещение экземпляров типов TLocation, TPoint и TCircle: каждый прямоугольник соответствует одному слову памяти. TLocation TPoint TCircle ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї ЪДДДДДДДДДДДї і X і і X і і X і ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ і Y і і Y і і Y і АДДДДДДДДДДДЩ ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ і Color і і Color і ГДДДДДДДДДДДґ ГДДДДДДДДДДДґ і NDV і і NDV і АДДДДДДДДДДДЩ ГДДДДДДДДДДДґ і Radius і АДДДДДДДДДДДЩ Рис. 17.1 Схема экземпляров типов TLocation, TPoint и TCircle Таблица виртуальных методов Каждый объектный тип, содержащий или наследующий виртуальные методы, конструкторы или деструкторы, имеет связанную с ним таблицу виртуальных методов, в которой запоминается инициализируемая часть сегмента данных программы. Для каждого объектного тип а (но не для каждого экземпляра) имеется только одна таблица виртуальных методов, однако два различных объектных типа никогда не разделяют одну таблицу виртуальных методов, независимо от того, насколько эти типы идентичны. Таблицы виртуальных методов соз даются автоматически компилятором, и программа никогда не манипулирует ими непосредственно. Аналогично, указатели на таблицы виртуальных методов автоматически запоминаются в экземплярах объектных типов с помощью конструкторов, программа никогда не работа ет с этими указателями непосредственно. Первое слово таблицы виртуальных методов содержит размер экземпляров соответствующего объектного типа. Эта информация используется конструкторами и деструкторами для определения того, сколько байт выделяется или освождается при использовании расшир енного синтаксиса стандартных процедур New и Dispose. Второе слово таблицы виртуальных методов содержит отрицательный размер экземпляров соответствующего объектного типа эта информация используется ратификационным (т.е. подтверждающим действительность) механизмом вызова виртуального метода для выявлени я инициализируемых объектов (экземпляров, для которых должен выполняться конструктор) и для проверки согласованности таблицы виртуальных методов. Когда разрешена ратификация виртуального вызова (с помощью директивы {$R+} компилятора, которая расширена и включает в себя проверку виртуальных методов), компилятор генерирует вызов программы проверки допустимости таблицы виртуальных методов перед каждым вызовом виртуального метода. Программа ратификации таблицы виртуальных методов проверяет, что первое слово таблицы виртуальных методов не равно нулю и что что сумма первого и второго слов равна нулю. Если любая из проверок неудачна, то генерируется ошибка 210 исполняющей системы Турбо Паскаля. Разрешение проверок границ диапазонов и проверок вызовов виртуальных методов замедляет выполнение программы и делает ее несколько больше, поэтому используйте {$R+} только во время отладки и переключите эту директиву в состояние {$R-} в окончательной версии программы. Третье слово ТВМ содержит смещение сегмента данных объектного типа в таблице динамических методов (ТДМ), или 0, если объект не содержит динамических методов. Четвертое слово ТВМ резервируется и всегда равно 0. Наконец, начиная со смещения 8 таблицы виртуальных методов следует список 32-разрядных указателей методов (один указатель на каждый виртуальный метод в порядке их описаний). Каждая позиция содержит адрес точки входа соответствующего виртуального мет ода. На Рис. 17.2 показано размещение таблиц виртуальных методов типов TPoint и TCircle (тип TLocation не имеет таблицы виртуальных методов, т.к. не содержит в себе виртуальных методов, конструкторов и деструкторов): каждый маленький прямоугольник соотве тствует одному слову памяти, а каждый большой прямоугольник - двум словам памяти. ТВМ TPoint ТВМ TCircle ЪДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДї і $0008 і і $000A і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і $FFFB і і $FFF6 і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і і і і і @TPoint.Done і і @TPoint.Done і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і і і і і @TPoint.Show і і @TCircle.Show і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і і і і і @TPoint.Hide і і @TCircle.Hide і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і і і і і @TPoint.MoveTo і і @TPoint.MoveTo і АДДДДДДДДДДДДДДДДДДЩ ГДДДДДДДДДДДДДДДДДДґ і і і @Circle.Fill і АДДДДДДДДДДДДДДДДДДЩ Рис. 17.2 Схемы таблиц виртуальных методов для TPoint и TCircle Обратите внимание на то, как Circle наследует методы Done и MoveTo типа Point и как он переопределяет Show и Hide. Как уже упоминалось, конструкторы объектных типов содержат специальный код, который запоминает смещение таблицы виртуальных методов объектного типа в инициализируемых экземплярах. Например, если имеется экземпляр P типа TPoint и экземпляр C типа TCi rcle, то вызов P.Init будет автоматически записывать смещение таблицы виртуальных методов типа TPoint в поле таблицы виртуальных методов экземпляра P, а вызов C.Init точно так же запишет смещение таблицы виртуальных методов типа TCircle в поле таблицы ви ртуальных методов экземпляра C. Эта автоматическая инициализация является частью кода входа конструктора, поэтому если управление передается в начало операторной секции, то поле Self таблицы виртуальных методов также будет установлено. Таким образом, при возникновении необходимости, конструктор может выполнить вызов виртуального метода. Таблица динамических методов Таблица вирутальных методов (ТДМ) объектного типа содержит для каждого описанного в объектном типе виртуального метода и его предков четырехбайтовую запись. В тех случаях, когда в порождающих типах (предках) определяется большее число виртуальных ме тодов, в процессе создания производных типов может использоваться достаточно большой объем памяти, особенно если создается много производных типов. Хотя в производных типах могут переопределяться только некоторые из наследуемых методов, таблица вирутальн ых методов каждого производного типа содержит указатели метода для всех наследуемых виртуальных методов, даже если они не изменялись. Динамические методы обеспечивают в таких ситуациях альтернативу. В Турбо Паскале для Windows вводится новый формат таблицы методов и новый способ диспетчеризации методов с поздним связыванием. Вместо кодирования для всех методов объектного типа с по здним связыванием, в таблице динамических методов кодируются только те методы, которые были в объектном типе переопределены. Если в наследующих типах переопределяются только некоторые из большого числа методов с поздним связыванием, формат таблицы динами ческих методов использует меньшее пространство, чем формат таблицы вирутальных методов. Формат таблицы динамических методов иллюструют следующие два объектных типа: type TBase = object X: Integer; constructor Init; destructor Done; virtual; procedure P10; virtual 10; procedure P20; virtual 20; procedure P30; virtual 30; procedure P30; virtual 30; end; type TDerived = object(TBase) Y: Integer; constructor Init; destructor Done; virtual; procedure P10; virtual 10; procedure P30; virtual 30; procedure P50; virtual 50; end; На Рис. 17.3 и 17.4 показаны схемы таблицы вирутальных методов и таблицы динамических методов для TBase и TDerived. Каждая ячейка соответствует слову памяти, а каждая большая ячейка - двум словам памяти. ТВМ TBase ТДМ TBase ЪДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДї і 4 і і 0 і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і -4 і і индекс в кеше і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і Смещ. ТДМ TBase і і смещение записи і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і 0 і і 4 і ГДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і і і 10 і і @TBase.Done і ГДДДДДДДДДДДДДДДДДДґ і і і 20 і АДДДДДДДДДДДДДДДДДДЩ ГДДДДДДДДДДДДДДДДДДґ і 30 і ГДДДДДДДДДДДДДДДДДДґ і 40 і ГДДДДДДДДДДДДДДДДДДґ і і і @TBase.P10 і і і ГДДДДДДДДДДДДДДДДДДґ і і і @TBase.P20 і і і ГДДДДДДДДДДДДДДДДДДґ і і і @TBase.P30 і і і ГДДДДДДДДДДДДДДДДДДґ і і і @TBase.P40 і і і АДДДДДДДДДДДДДДДДДДЩ Рис. 17.3 Схемы таблицы вирутальных методов и таблицы динамических методов для TBase Объектный тип имеет таблицу динамических методов только в том случае, если в нем вводятся или переопределяются динамические методы. Если объектный тип наследует динамические методы, но они не переопределяются, и новые динамические методы не вводятся , то он просто наследует таблицу динамических методов своего предка. Как и в случае таблицы вирутальных методов, таблица динамических методов записывается в инициализированную часть сегмента данных прикладной программы. ТВМ TDerived ТДМ TDerived ЪДДДДДДДДДДДДДДДДДДДї ЪДДДДДДДДДДДДДДДДДДї і 6 і і Смещ. ТДМ TBase і ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і -6 і і индекс в кеше і ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і Смещ. ТДМ TDerivedі і смещение записи і ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і 0 і і 3 і ГДДДДДДДДДДДДДДДДДДДґ ГДДДДДДДДДДДДДДДДДДґ і і і 10 і і @TBase.Done і ГДДДДДДДДДДДДДДДДДДґ і і і 30 і АДДДДДДДДДДДДДДДДДДДЩ ГДДДДДДДДДДДДДДДДДДґ і 50 і ГДДДДДДДДДДДДДДДДДДґ і і і @TDerived.P10 і і і ГДДДДДДДДДДДДДДДДДДґ і і і @TDerived.P30 і і і ГДДДДДДДДДДДДДДДДДДґ і і і @TDerived.T50 і і і АДДДДДДДДДДДДДДДДДДЩ Рис. 17.4 Схемы таблицы вирутальных методов и таблицы динамических методов для TDerived Первое слово таблицы динамических методов содержит смещение сегмента данных родительской таблицы динамических методов, или 0, если родительская таблица динамических методов отсутствует. Второе и третье слово таблицы динамических методов используется в кеш-буфере просмотра динамических методов (см. далее). Четвертое слово таблицы динамических методов содержит счетчик записи таблицы динамических методов. Непосредственно за ним следует список слов, каждое из которых содержит индекс динамического метода, а затем список соответствующих указателей методов. Длина каждого списка задается счетчиком записи таблицы динамических методов. Функция SizeOf Будучи примененной к экземпляру объектного типа, имеющего таблицу виртуальных методов, стандартная функция SizeOf возвратит записанный в таблице виртуальных методов размер. Таким образом, для объектов, имеющих таблицу виртуальных методов, функция Si zeOf всегда возвращает действительный размер экземпляра, а не описанный его размер. Функция TypeOf Турбо Паскаль предоставляет новую стандартную функцию TypeOf, которая возвращает указатель на таблицу виртуальных методов объектного типа. Функция TypeOf принимает единственный параметр, который может быть либо идентификатором объектного типа либо э кземпляром объектного типа. В обоих случаях результат типа pointer является указателем на таблицу виртуальных методов объектного типа. TypeOf может применяться только к объектным типам, имеющим таблицы виртуальных методов. Применение этой функции к други м типам приведет к ошибке. Функция TypeOf может использоваться для проверки фактического типа экземпляра. Например: if TypeOf(Self) = TypeOf(Point) then ... Вызовы виртуальных методов Для вызова виртуального метода компилятор генерирует код, который выбирает адрес таблицы виртуальных методов из поля таблицы виртуальных методов объекта, и затем вызывает метод, используя связанную с ним точку входа. Например, если дана переменная P P типа PointPtr, то вызов PP^.Show будет генерировать следующий код: les di, PP ; загрузить РР в ES:DI push es ; передать, как параметр Self push di mov di, es:[di + 6] ; извлечь смещение ТВМ из поля ТВМ call DWORD PTR [di + 8] ; вызвать запись ТВМ для Show Правила совместимости типов для объектных типов позволяют PP указывать на TPoint и на TCircle или на любых других потомков TPoint. И если вы просмотрите показанные здесь таблицы виртуальных методов, то вы увидите, что для типа TPoint точка входа со смещением 8 в таблицы виртуальных методов указывает на TPoint.Show. Таким образом, в зависимости от фактического во время выполнеия типа PP, инструкция CALL вызывает либо TPoint.Show, либо TCircle.Show, либо метод любого другого потомка TPoint. Если Show является статическим методом, то для вызова PP^.Show будет генерироваться следующий код: les di, PP ; загрузить PP в ES:DI push es ; передать, как параметр Self push di call Point.Show ; непосредственно вызвать TPoint.Show В данном случае не имеет значения, на что указывает PP, и код всегда будет вызывать метод TPoint.Show. Вызовы динамических методов Диспетчеризация вызова динамического метода несколько более сложна и требует больше времени, чем диспетчеризация виртуального метода. Вместо использования инструкции CALL для вызова через указатель метода по статическому смещению в таблице вирутальн ых методов, таблица динамических методов объетного типа и таблица динамических методов его предка должны просматриваться в поиске "самого верхнего" вхождения индекса конкретного динамического метода, а вызов затем должен выполняться через соответствующий указатель метода. Этот процесс требует использования существенно большего числа инструкций, которые можно записать, как "встроенные" (inline), поэтому Турбо Паскаль обеспечивает подпрограмму диспетчеризации, используемую при вызове динамического метода. Если бы метод Show показанного выше типа TPoint описывался как динамический метод (с индексом динамического метода 200), то вызов PP^.Show, где PP имеет тип TPointPtr, привел бы к генерации следующего кода: les di,PP ; загрузка PP в ED:DI push es ; передача, как параметра ; Self push di mow di,es:[di+6] ; извлечение смещения ; таблицы вирутальных методов ; из поля таблицы ; вирутальных методов mov ax,200 ; загрузка в AX индекса ; динамического метода call Dispatch ; вызов подпрограммы ; диспетчеризации Диспетчер выбирает сначала смещение таблицы динамических методов от таблицы вирутальных методов, на которое указывает регистр DI. Затем используется "индекс в кеше" - поле таблицы динамических методов. Диспетчер проверяет, является ли индекс вызванн ого динамического метода индексом того динамического метода, который вызывался последним. Если это так, он немедленно передает этому методу управление (путем перехода с помощью указателя метода, записанного по смещению, заданному полем "смещение записи") . Если динамический индекс вызванного метода не совпадает с тем, который записан в кеше, то диспетчер просматривает таблицу динамических методов и родительскую таблицу динамических методов (следуя по связям в таблице динамических методов), пока он не най дет запись, с данным индексом динамического метода. Индекс и смещение соответствующего указателя метода записываются затем в поле таблицы динамических методов, а управление передается методу. Если по каким-либо причинам диспетчер не может найти запись с данным индексом динамического метода, он завершает прикладную программу с кодом ошибки этапа выполнения 210. Вопреки кешированию и высокооптимизированной подпрограмме диспетчеризации, диспетчеризация динамического метода может потребовать существенно больше времени, чем вызов вирутального метода. Однако в тех случаях, когда сами дествия, выполняемые динами ческим методом, требуют много времени, дополнительное пространство, сохраняемое таблицами динамических методов, может перевесить этот недостаток. Соглашения о вызовах методов Методы используют те же соглашения о вызовах, что и обычные процедуры и функции, за тем исключением, что каждый метод имеет неявный дополнительный параметр Self, который соответствует параметру-переменной того же типа, что и объектный тип данного ме тода. Параметр Self всегда передается последним и всегда имеет форму 32 -разрядного указателя на экземпляр, из которого вызывается метод. Например, если переменная PP имеет тип TPointPtr, как определено выше, то вызов PP^.MoveTo (10, 20) кодируется следу ющим образом: mov ax, 10 ; загрузить 10 в AX push ax ; передать PX как параметр mov ax, 20 ; загрузить 20 в AX push ax ; передать PY как параметр les di, PP ; загрузить PP в ES:DI push es ; передать, как параметр Self push di mov di, es:[di + 6] ; извлечь смещение ТВМ из поля ТВМ call DWORD PTR [di + 16] ; вызвать запись ТВМ для MoveTo Во время возврата метод должен удалить параметр Self из стека точно так же, как он удаляет обычные параметры. Методы всегда используют дальний тип вызова, независимо от состояния директивы $F компилятора. Конструкторы и деструкторы Конструкторы и деструкторы используют те же соглашения о вызовах, что и обычные методы, за тем исключением, что дополнительный параметр размером в слово, называемый параметром таблицы виртуальных методов, передается через стек непосредственно перед параметром Self. Для конструкторов параметр таблицы виртуальных методов содержит смещение таблицы виртуальных методов для запоминания поля Self таблицы виртуального метода, чтобы инициализировать Self. Более того, если конструктор вызывается для размещения динамического объекта с помощью расширенного синтаксиса стандартной процедуры New, через параметр Self передается указатель nil. Это заставляет конструктор размещать новый динамический объект, а дрес которого передается вызывающей программе через DX:AX при возврате из конструктора. Если конструктор не может разместить объект, то в DX:AX возвращается пустой указатель nil. (См. далее "Восстановление ошибок конструктора"). Наконец, если конструктор вызывается с использованием уточненного идентификатора метода (т.е. идентификатора типа объекта, за которым следуют точка и идентификатор метода), то в параметре таблицы виртуальных методов передается нулевое значение. Это является указанием конструктору на то, что ему не следует инициализировать поле Self таблицы виртуальных методов. Для деструкторов нулевое значение параметра таблицы виртуальных методов означает обычный вызов, а ненулевое указывает, что деструктор был вызван с использованием расширенного синтаксиса стандартной процедуры Dispose. Это заставляет деструктор удалит ь Self непосредственно перед возвратом (размер Self определяется из первого слова Self в ТВМ). Расширения процедур New и Dispose Стандартные процедуры New и Dispose расширены таким образом, что допускают использование в качестве второго параметра конструктора или деструктора. Это позвляет создать или уничтожить динамическую переменную объектного типа. Синтаксис вызова этих пр оцедур следующий: New(P, Construct) и Dispose(P, Destruct) где P - это переменная-указатель, указывающая на объектный тип, а Construct и Destruct представляют собой вызовы конструкторов или деструкторов данного объектного типа. Для процедуры New действие расширенного синтаксиса эквивалентно выполнению следующих операторов: New(P); P^.Consctruct; Для процедуры Dispose действие расширенного синтаксиса эквивалентно выполнению следующих операторов: P^.Destruct; Dispose(P); Без расширенного синтаксиса вхождение таких пар вызовов New с последующим вызовом конструктора и вызовов десруктора, за которым следует вызов Dispose, было бы достаточно общим. Расширенный синтаксис улучшает читаемость кода и позволяет генеровать бо лее короткий и эффективный код. Использование расширенного синтаксиса New и Dispose иллюстрирует следующий пример: var SP: StrFieldPtr; ZP: ZipFieldPtr; begin New(SP, Init(1, 1, 25, 'Firstname')); New(SP, Init(1, 2, 5, 'Zip code', 0, 99999)); SP^.Edit; SP^.Edit; ... Dispose(ZP, Done); Dispose(SP, Done); end; Дополнительное расширение позволяет использовать New, как функцию, которая распределяет и возвращает дополнительную переменную заданного типа. При этом используется следующий синтаксис: New(T); или New(T, Construct); В первой форме T может быть любым типом указателей. Во втором случае T должно указывать на объектный тип, а Construct должно быть вызовом конструктора данного объектного типа. В обоих случаях типом результата функции будет T. Приведем пример: var F1, F2: FieldPtr; begin F1 := New(StrFieldPtr, Init(1, 1, 25, 'Firstname')); F2 := New(SP, Init(1, 2, 5, 'Zip code', 0, 99999)); ... Writeln(F1^.GetStr); { вызов StrField.GetStr } Writeln(F1^.GetStr); { вызов ZipField.GetStr } ... Dispose(F2, Done) { вызов Field.Done } Dispose(F1, Done) { вызов StrField.Done } end; Обратите внимание, что хотя F1 и F2 имеют тип FieldPtr, расширенные правила совместимости указателей по присваиванию позволяют присваивать F1 и F2 любому потомку Field, и поскольку GetStr и Done являются виртуальными методами, механизм диспетчирован ия виртуальных методов корректно вызывает StrField.GetStr, ZipField.GetStr, Field. Done и StrField.Done соответственно. Методы на языке Ассемблера Написанные на языке Ассемблера реализации методов могут компоноваться с программами на Турбо Паскале с помощью директивы $L компилятора и зарезервированного слова external. Описание внешнего метода в объектном типе не отличается от описания обычного метода, однако, реализация списка методов осуществляется путем приведения только заголовков методов, за каждым из которых следует зарезервированное слово external. В исходном тексте на языке Ассемблера для записи уточненного идентификатора вместо точки (.) используется @ (точка в языке Ассемблера имеет другой смысл и не может быть частью идентификатора). Например, идентификатор Паскаля Rect.Init записывается н а языке Ассемблера, как Rect@Init. Синтаксис операции @ может использоваться для объявления как общедоступных (PUBLIC), так и для внешних (EXTRN) идентификаторов. В качестве примера методов на языке Ассемблера введем следующий простой объект Rect: type Rect = object X1, Y1, X2, Y2: integer; procedure Init(XA, YA, XB, YB: integer); procedure Union(var R: Rect); function Contains(X, Y: integer): boolean; end; Rect представляет собой прямоугольник, ограниченный четырьмя координатами X1, Y1, X2 и Y2. Левый верхний угол прямоугольника определяется X1 и Y1, а правый нижний - координатами X2 и Y2. Метод Init присваивает значения координатам прямоугольника. Ме тод Union вычисляет наименьший прямоугольник, который содержит как сам прямоугольник, так и некоторый другой прямоугольник. Contains возвращает True, если данная точка лежит внутри прямоугольника, и False, если вне его. Другие методы, такие как перемещен ие, изменение размеров, вычисление точек пересечений и проверка на равенство, могут быть легко дописаны, чтобы сделать Rect более полезным объектом. Реализация методов объекта Rect на Паскале приводит только заголовки методов, за каждым из которых следует зарезервированное слово external. {$L RECT} procedure Rect.Init(XA,YA, XB, YB: integer); external; procedure Rect.Union(var R: Rect); external; function Rect.Contains(X, Y: integer): boolean; external; Разумеется, не существует никаких требований, чтобы методы были реализованы как внешние. Каждый отдельный метод может быть реализован как на Паскале, так и на Ассемблере, в зависимости от желания. Исходный файл на языке Ассемблера RECT.ASM, который реализует три внешних метода, приводится ниже: TITLE Rect LOCALS@@ ; структура Rect Rect STRUCT X1 DW ? Y1 DW ? X2 DW ? Y2 DW ? Rect ENDS code SEGMENT BYTE PUBLIC ASSUME cs:code ; процедура Rect.Init(XA, YA, XB, YB: Integer); PUBLIC Rect@Init Rect@Init PROC FAR @XA EQU (WORD PTR [bp + 16]) @YA EQU (WORD PTR [bp + 14]) @XB EQU (WORD PTR [bp + 12]) @YB EQU (WORD PTR [bp + 10]) @Self EQU (DWORD PTR [bp + 6]) push bp ; сохранить bp mov bp, sp ; установить границу стека les di, @Self; загрузить Self d ES:DI cld ; продвинуться вперед mov ax, @XA;X1 := XA stosw mov ax, @YA;Y1 := YA stosw mov ax, @XB;X2 := XB stosw mov ax, @YB;Y2 := YB stosw pop bp ; восстановить BP ret12 ; излечь параметры и выполнить возврат Rect@Init ENDP ; Процедура Rect.Union (var R: Rect) PUBLIC Rect@Union Rect@Union PROC FAR @R EQU (DWORD PTR [bp + 10]) @Self EQU (DWORDPTR [bp + 6]) push bp ; сохранить bp mov bp, sp ; установить границу стека push ds ; сохранить ds lds si, @R ; загрузить R в DS:SI les di, @Self ; загрузить Self в ES:DI cld ; продвинуться вперед lodsw ; если R.X1 >= X1, перейти к @@1 scasw jge @@1 dec di ; X1 := R.X1 dec di stosw @@1: lod sw ; если R.Y1 >= Y1, перейти к @@2 scasw jge @@2 decdi;Y1 := R.Y1 decdi stos @@2: lod sw ; если R.X2 <= X2, перейти к @@3 scasw jle @@3 decdi ; X2 := R.X2 decdi stosw @@3: lodsw ; если R.Y2 <= Y2, перейти к @@4 scasw jle @@4 decdi ; Y2 := R.Y2 decdi stosw @@4: popds ; восстановить ds pop bp ; восстановить bp ret8 ; извлечь параметры и выполнить возврат Rect@Union ENDP ; функция Rect.Contains(X, Y: integer): boolean PUBLIC Rect@Contains Rect@Contains PROC FAR @X EQU (WORD PTR [bp + 12]) @Y EQU (WORD PTR [bp + 10]) @Self EQU (DWORD PTR [bp + 6]) push bp ; сохранить bp mov bp, sp ; установить границу стека les di, @Self ; загрузить Self в ES:DI mov al, 0 ; возвратить false mov dx, @X ; если (X < X1) or (X > X2) - перейти ; на @@1 cmp dx, es:[di].X1 jl @@1 cmp dx, es:[di].X2 jg @@1 mov dx, @Y ; если (Y < Y1) or (Y > Y2) - на @@1 cmp dx, es:[di].Y1 jl @@1 cmp dx, es:[di].Y2 jg @@1 inc ax ; возвратить true @@1: pop bp ; восстановить bp ret8 ; извлечь параметры и выйти Rect@ContainsENDP code ENDS END Обнаружение ошибок конструктора Как описано в Главе 16, Турбо Паскаль позволяет вам установить функцию ошибок динамически распределяемой области памяти посредством переменной HeapError модуля System. Эта функциональность поддерживается и в Турбо Паскале, однако теперь она оказывае т воздействие на способ работы конструкторов. По умолчанию, если не хватает памяти для размещения динамического экземпляра объектного типа, то вызов конструктора, использующий расширенный синтаксис стандартной процедуры New, генерирует ошибку 203 исполняющей системы. Если вы устанавливаете функ цию ошибок динамической памяти, которая возвращает 1, а не стандартный результат функции 0, то вызов конструктора через New будет возвращать nil, если конструктор не сможет завершить запрос (вместо прекращения выполнения программы). Код, который выполняет размещение и инициализацию поля таблицы виртуальных методов динамического экземпляра, является частью последовательности точки входа в конструктор. Если управление передается в точку begin операторной части конструктора, то эк земпляр уже будет размещен и инициализирован. Если размещение завершилось неудачно, и если функция ошибки динамически распределяемой области памяти возвратила 1, то конструктор пропускает выполнение операторной части и возвращает указатель nil. Таким обр азом, указатель, который задан в расширенной процедуре New, вызывающей конструктор, будет установлен в nil (пусто). Примечание: Имеется новая стандартная процедура Fail. Если только управление передается в точку begin операторной части конструктора, то это гарантирует, что экземпляр объектного типа уже успешно размещен и инициализирован. Однако, сам конструктор может попытаться разместить динамическую переменную, чт обы инициализировать поле указателя экземпляра, и эта попытка может потерпеть неудачу. Если это произойдет, то конструктор с хорошо продуманным поведением должен дать обратный ход успешному размещению и в конце концов удалить размещенный экземпляр объект ного типа, чтобы конечным результатом явился указатель nil. Чтобы сделать возможным этот обратный ход, Турбо Паскаль предоставляет новую стандартную процедуру Fail, которая не имеет параметров и которая может вызываться только изнутри конструктора. Вызов Fail заставляет конструктор удалить динамический экземпляр, который был размещен при входе в конструктор, и ведет к возврату указателя nil для индикации неудачной попытки. Если динамические экземпляры размещаются с помощью расширенного синтаксиса New, то результирующее значение nil специфицированного указателя указывает на неудачную операцию. Несомненно, нет таких переменных типа указатель, которые можно было бы прове рить после создания статического экземпляра или после вызова наследованного конструктора. Вместо этого, Турбо Паскаль позволяет использовать в выражениях конструкторы как функции, возвращающие результат типа boolean. Возвращаемое значение True означает у спех, а False - неудачу, благодаря вызову Fail внутри конструктора. Следующая программа предоставляет два простых объектных типа, содержащих указатели. Эта первая версия программы не использует обнаружение ошибок конструктора. type LinePtr = ^Line; Line = string [79]; BasePtr = ^Base; Base = object L1, L2: LinePtr; constructor Init(S1, S2: Line); destructor Done; virtual; procedure Dump; virtual; end; DerivedPtr = ^Derived; Derived = object(base) L3, L4: LinePtr; constructor Init(S1, S2, S3, S4: Line); destructor Done; virtual; procedure Dump; virtual; end; var BP: BasePtr; DP: DerivedPtr; constructor Base.Init(S1, S2: Line); begin New (L1); New (L2); L1^ := S1; L2^ := S2; end; destructor Base.Done; begin Dispose(L2); Dispose(L1); end; procedure Base.Dump; begin Writeln('B: ', L1^, ', ', L2^, '.'); end; constructor Derived.Init(S1, S2, S3, S4: Line); begin Base.Init(S1, S2); New(L3); New(L4); L3^ := S3; L4^ := S4; end; destructor Derived.Done; begin Dispose(L4); Dispose(L3); Base.Done; end; procedure Derived.Dump; begin Writeln('D: ',L1^,', ',L2^,', ',L3^,', ',L4^,'.'); end; begin New(BP, Init('Турбо', 'Паскаль'); New(DP, Init('Север', 'Восток', 'Юг', 'Запад'); BP^.Dump; DP^.Dump; Dispose(DP, Done); Dispose(BP, Done); end. Следующий пример демонстрирует, как можно переписать предыдущий пример для реализации обнаружения ошибок. Описания типов и переменных не приводятся, т.к. они остаются без изменений. constructor Base.Init(S1, S2: Line); begin New(L1); New(L2); if (L1 = nil) or (L2 = nil) then begin Base.Done; Fail; end; L1^ := S1; L2^ := S2; end; destructor Base.Done; begin if L2 <> nil then Dispose (L2); if L1 <> nil then Dispose(L1); end; constructor Derived.Init(S1, S2, S3, S4: Line); begin if not Base.Init(S1, S2) then Fail; New(L3); New(L4); if (L3 = nil) or (L4 = nil) then begin Derived.Done; Fail; end; L3^ := S3; L4^ := S4; end; destructor Derived.Done; begin if L4 <> nil then Dispose (L4); if L3 <> nil then Dispose(L3); Base.Done; end; {$F+} function HeapFunc(Size: word): integer; begin HeapFunc := 1; end; {$F-} begin HeapError := @HeapFunc; { установить обработчик ошибок динамически распределяемой области } New (BP, Init ('Турбо', 'Паскаль'); New (DP, Init ('Север', 'Восток', 'Юг', 'Запад'); if (BP = nil) or (DP = nil) then Writeln('Ошибка выделения памяти') else begin BP^.Dump; DP^.Dump; end; if DP <> nil then Dispose(DP, Done); if BP <> nil then Dispose(BP, Done); end. Обратите внимание, как используются соответствующие деструкторы конструкторов Base.Init и Derived.Init для отмены любого успешного размещения перед тем, как Fail вызывается, чтобы окончательно привести к неудачному выполнению операции. Заметьте такж е, что в Derived.Init вызов конструктора Base.Init записан внутри выражения, благодаря чему можно проверять успешное завершение в наследуемом конструкторе. |