Глубокое понимание работы интерфейсов требует знания их технической реализации. Поэтому вам необходимо разобраться в том, как представляется интерфейс в оперативной памяти компьютера, и что стоит за операторами Intf := Obj и Intf.NextLine.
Интерфейс по сути выступает дополнительной таблицей виртуальных методов, ссылка на которую укладывается среди полей объекта (рисунок 6.5). Эта таблица называется таблицей методов интерфейса. В ней хранятся указатели на методы класса, реализующие методы интерфейса.
Интерфейсная переменная хранит ссылку на скрытое поле объекта, которое содержит указатель на таблицу методов интерфейса. Когда интерфейсной переменной присваивается значение объектой переменной,
Intf := Obj; // где Intf: ITextReader и Obj: TTextReader |
к адресу объекта добавляется смещение до скрытого поля внутри объекта и этот результат заносится в интерфейсную переменную. Чтобы убедиться в сказанном, посмотрите в отладчике значения Pointer(Obj) и Pointer(Intf) сразу после выполнения оператора Intf := Obj. Эти значения будут разными! Причина в том, что объектная ссылка указывает на начало объекта, а интерфейсная ссылка — на скрытое поле внутри объекта.
Рисунок 6.5. Представление интерфейса в памяти
Алгоритм вызова метода интерфейса такой же, как алгоритм вызова метода класса. Когда через интерфейсную переменную выполняется вызов метода,
реализуется следующий алгоритм:
- Из интерфейсной переменной извлекается адрес (по нему хранится адрес таблицы методов интерфейса);
- По полученному адресу извлекается адрес таблицы методов интерфейса;
- На основании порядкового номера метода в интерфейсе из таблицы извлекается адрес соответствующей подпрограммы;
- Вызывается код, находящийся по этому адресу. Этот код является переходником от метода интерфейса к методу объекта. Его задача — восстановить из ссылки на интерфейс значение указателя Self (путем вычитания заранее известного значения) и выполнить прямой переход на код метода класса.
Обычными средствами процедурного программирования этот алгоритм реализуется так:
type TMethodTable = array[0..9999] of Pointer; TNextLineFunc = function (Self: ITextReader): Boolean;var Intf: ITextReader; // интерфейсная переменна IntfPtr: Pointer; // адрес внутри интерфейсной переменной TablePtr: ^TMethodTable; // указатель на таблицу методов интерфейса MethodPtr: Pointer; // указатель на методbegin ... IntfPtr := Pointer(Intf); // 1) извлечение адреса из интерфейсной переменной TablePtr := Pointer(IntfPtr^); // 2) извлечение адреса таблицы методов интерфейса MethodPtr := TablePtr^[3]; // 3) извлечение адреса нужного метода из таблицы TNextLineFunc(MethodPtr)(Intf); // 4) вызов метода через переходник ...end. |
Вся эта сложность скрыта в языке Delphi за понятием интерфейса. Причем несмотря на такое количество операторов в примере, вызов метода через интерфейс в машинном коде выполняется весьма эффективно (всего несколько инструкций процессора), поэтому в подавляющем большинстве случаев потерями на вызов можно пренебречь.