Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Паскаль / tp3 / tp3 / 17.doc
Скачиваний:
17
Добавлен:
10.12.2013
Размер:
112.64 Кб
Скачать

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

Функция TypeOf

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

Функция TypeOf может использоваться для проверки фактического типа экземпляра. Например:

if TypeOf(Self) = TypeOf(Point) then ...

Вызовы виртуальных методов

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

Соседние файлы в папке tp3