 |
Например, вызов в виде:
CALL es:di method list:insert uses ds:bx pascal,es di,es
dx,es cx
генерирует последовательность вида:
mov bx,[es:di.@Mptr_list]
CALL [ds:bx.insert] pascal,es di,es dx,es cx
Заметим, для объектов, описанных с таблицами NEAR, инструк-
цией CALL...METHOD будет загружаться только регистр смещения.
Сегментный регистр всегда должен содержать корректное значение. В
следующем примере показано, как обеспечить правильную установку
сегментного регистра:
; Добавить узел к концу объекта связанного списка.
; Это виртуальный метод "list_append".
list_append PROC PASCAL NEAR
ARG @@list:dword,\
@@new:dword
USES dx,bx, es,di
mov ax,@Data
mov ds,ax
les di,@@list
sub ax,ax
CALL es:di method list:insert uses DS:bx pascal, es
di,@@new,ax ax
ret
ENDP
Примечание: Пока вы не инициализируете в данных объекта
указатель таблицы виртуальных методов, ни один виртуальный
метод вызвать нельзя. Это вызвано тем, что указатель загру-
жает адрес ТВМ (из которой извлекается адрес нужной проце-
дуры виртуального метода). Таким образом, если вы не иници-
ализировали указатель на таблицу виртуальных методов, любой
вызов виртуального метода приведет к вызову по некоторому
случайному адресу.
В качестве другого примера рассмотрим базовый объект node
(узел), который вы можете включить в любой объект, помещенный в
связанный список или очередь.
Турбо Ассемблер 3.0/tasm/#1-2 = 85 =
node STRUC GLOBAL METHOD {
construct:dword = node_construct ; подпрограмма
; конструктора узла
destroy:dword = node_destroy ; подпрограмма
; деструктора узла
init:dword = node_init ; подпрограмма
; инициализации узла
deinit:dword = node_deinit ; подпрограмма
; деинициализации узла
routine
virtual next:word = node_adv ; подпрограмма
; следующего узла
virtual prev:word = node_back ; подпрограмма
; предыдущего узла
virtual print:word = node_print ; подпрограмма
; содержимого узла
}
node_next dd ? ; указатель следующего
; узла
node_prev dd ? ; указатель
; предыдущего узла
ends
Чтобы можно было использовать связанный список или очередь,
вы можете определить любое число объектов, наследующих объект
node. Приведем два примера:
mlabel STRUC GLOBAL node METHOD {
virtual print:word = label_print
}
label_name db 80 dup (?)
label_addr db 80*2 dup (?)
label_city db 80 dup (?)
label_state db 2 dup (?)
label_zip db 10 dup (?)
ENDS
book STRUC GLOBAL node METHOD {
virtual print:word = book_print
}
book_title db 80 dup (?)
book_author db 80 dup (?)
ENDS
В следующем примере вы для объектов label и book вызываем
методы путем вызова printit. Если "предком" является node, не
важно, какой объект передается printit. Так как метод печати -
это виртуальный метод, вызов выполняется косвенно через ТВМ объ-
екта. При первом вызове printit, так как мы передаем экземпляр
объекта label, вызывается процедура метода label_print. При вто-
ром вызове printit вызывается процедура метода book_print, пос-
кольку мы передаем экземпляр объекта book. Заметим, что если бы
метод print был статическим, то при вызове node_print всегда вы-
Турбо Ассемблер 3.0/tasm/#1-2 = 86 =
зывалась бы процедура node_print (что нежелательно).
call printit pascal,<<адрес экземпляра объекта label>>
call printit pascal,<<адрес экземпляра объекта book>>
.
.
.
printit proc pascal near
arg @@obj:dword
uses ds,si,es,bx
mov ax,@data
mov es,ax
lds si@@obj
call ds:si method node:print uses es:bx pascal,ds si
ret
endp
Вызов виртуальных методов "предков"
ДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДДД
Благодаря тому, что вы повторно сможете использовать исход-
ный код, применение виртуальных методов "предков" может помочь
вам записывать методы порожденных классов. Например, пока вы не
зададите, является элемент очередью или списком, для очереди мож-
но использовать те же методы вывода, что и для списка. В классе
списка вы можете записать:
virtual show:word = list_show
а в классе очереди:
virtual show:word = queue_show
Подпрограмма list_show может печатать LIST SHOW: с последую-
щим выводом отдельных элементов списка. Однако в порожденном
классе, если queue_show использует подпрограмму печати, она долж-
на печатать собственный заголовок QUEUE SHOW: и использовать list
_show только как механизм последовательного прохода по списку и
печати отдельных элементов. list_show может определить передавае-
мый ей тип структуры и в зависимости от этого печатать заголовок
списка. Если подпрограмма для list_show посмотрит на указатель
таблицы виртуальных методов передаваемой ей структуры, она сможет
определить, совпадает ли указатель с указателем, установленным в
подпрограмме list_init для списков (или они различны). Если ука-
затель ТВМ в структуре не указывает на таблицу виртуальных мето-
дов для списков, то вероятно структура является порожденным ти-
пом. list_show может выполнить эту проверку с помощью следующих
операторов:
cmp [([es:di]).@mptr_list],offset @TableAddr_LIST
jne @@not_a_list ; пропустить печать заголовка списка
; Если мы попали сюда, то это список, и следует
Турбо Ассемблер 3.0/tasm/#1-2 = 87 =
; распечатать его заголовок.
...
@@not_a_list:
; Теперь вывести отдельные элементы списка.
Как можно вызвать класс списка и метод вывода из подпрограм-
мы queue_show? Если бы вы вызвали list_show непосредственно, то в
подпрограмме могла бы возникнуть проблема, если имя используемого
для вывода метода изменилось. (Вы можете не помнить об изменениях
в вызове queue_show.) Если в queue_show вы поместите следующий
оператор:
call(es:di) method list:show
то получите бесконечный цикл, поскольку, хотя список задан как
класс, для которого вызывается метод вывода, так как метод вывода
является виртуальным, будет использоваться ТВМ. Поскольку ТВМ для
структуры указывала бы на queue_show, вы вернулись бы к той же
подпрограмме.
Наилучшим способом вызова метода класса является следующий:
call +@table_list | show
Поскольку при описании класса list_show было задано как зна-
чение элемента вывода @table_list, Турбо Ассемблер автоматически
транслирует данный оператор в непосредственный вызов list_show.
Заметим, что хотя в списке метод вывода описывается как виртуаль-
ный, задание вызова приводит к тому, что Турбо Ассемблер выполня-
ет непосредственный вызов без просмотра ТВМ.
Примечание: Виртуальные подпрограммы обычно вызываются
косвенным образом через просмотр ТВМ.
В том случае, если вам нужно использовать для вывода элемен-
тов класса ТВМ (например, некоторые подпрограммы инициализации
могут изменить элемент вывода в таблице, и, в зависимости от то-
го, какое устройство используется для вывода всех элементов клас-
са, он будет указывать на другую подпрограмму), можно использо-
вать следующие операторы (которые работают для вывода элементов
класса с таблицей виртуальных методов):
|