- •Динамически загружаемые библиотеки
- •5.1. Динамически загружаемые библиотеки
- •5.2. Разработка библиотеки
- •5.2.1. Структура библиотеки
- •5.2.2. Экспорт подпрограмм
- •5.2.3. Соглашения о вызове подпрограмм
- •5.3. Использование библиотеки в программе
- •5.3.1. Статический импорт
- •5.3.2. Модуль импорта
- •5.3.3. Динамический импорт
- •5.3.4. Глобальные переменные и константы
- •5.3.5. Инициализация и завершение работы библиотеки
- •5.3.6. Исключительные ситуации и ошибки выполнения подпрограмм
- •5.3.7. Общий менеджер памяти
- •5.3.8. Стандартные системные переменные
- •Программирование на языке Delphi
- •6.1. Понятие интерфейса
- •6.2. Описание интерфейса
- •6.3. Расширение интерфейса
- •6.4. Глобально-уникальный идентификатор интерфейса
- •6.5. Реализация интерфейса
- •6.6. Использование интерфейса
- •6.7. Реализация нескольких интерфейсов
- •6.8. Реализация интерфейса несколькими классами
- •6.9. Связывание методов интерфейса с методами класса
- •6.10. Реализация интерфейса вложенным объектом
- •6.11. Совместимость интерфейсов
- •6.12. Совместимость класса и интерфейса
- •6.13. Получение интерфейса через другой интерфейс
- •6.14. Механизм подсчета ссылок
- •6.15. Представление интерфейса в памяти
- •6.16. Применение интерфейса для доступа к объекту dll-библиотеки
- •6.17. Итоги
- •7.1. Проект
- •7.1.1. Понятие проекта
- •7.1.2. Файлы описания форм
- •7.1.3. Файлы программных модулей
- •7.1.4. Главный файл проекта
- •7.1.5. Другие файлы проекта
- •7.2. Управление проектом
- •7.2.1. Создание, сохранение и открытие проекта
- •7.2.2. Окно управления проектом
- •7.2.3. Группы проектов
- •7.2.4. Настройка параметров проекта
- •7.2.5. Компиляция и сборка проекта
- •7.2.6. Запуск готового приложения
- •7.3. Форма
- •7.3.1. Понятие формы
- •7.3.2. Имя и заголовок формы
- •7.3.3. Стиль формы
- •7.3.4. Размеры и местоположение формы на экране
- •7.3.5. Цвет рабочей области формы
- •7.3.6. Рамка формы
- •7.3.7. Значок формы
- •7.3.8. Невидимая форма
- •7.3.9. Прозрачная форма
- •7.3.10. Полупрозрачная форма
- •7.3.11. Недоступная форма
- •7.3.12. События формы
- •7.4. Несколько форм в приложении
- •7.4.1. Добавление новой формы в проект
- •7.4.2. Добавление новой формы из Хранилища Объектов
- •7.4.3. Переключение между формами во время проектирования
- •7.4.4. Выбор главной формы приложения
- •7.4.5. Вызов формы из программы
- •7.5. Компоненты
- •7.5.1. Понятие компонента
- •7.5.2. Визуальные и невизуальные компоненты
- •7.5.3. «Оконные» и «графические» компоненты
- •7.5.4. Общие свойства визуальных компонентов
- •7.5.5. Общие события визуальных компонентов
- •7.6. Управление компонентами при проектировании
- •7.6.1. Помещение компонентов на форму и их удаление
- •7.6.2. Выделение компонентов на форме
- •7.6.3. Перемещение и изменение размеров компонента
- •7.6.4. Выравнивание компонентов на форме
- •7.6.5. Использование Буфера обмена
- •7.7. Закулисные объекты приложения
- •7.7.1. Application — главный объект, управляющий приложением
- •7.7.2. Screen — объект, управляющий экраном
- •7.7.3. Mouse — объект, представляющий мышь
- •7.7.4. Printer — объект, управляющий принтером
- •7.7.5. Clipboard — объект, управляющий Буфером обмена
- •7.8. Итоги
6.15. Представление интерфейса в памяти
Глубокое понимание работы интерфейсов требует знания их технической реализации. Поэтому вам необходимо разобраться в том, как представляется интерфейс в оперативной памяти компьютера, и что стоит за операторами Intf := Obj и Intf.NextLine.
Интерфейс по сути выступает дополнительной таблицей виртуальных методов, ссылка на которую укладывается среди полей объекта (рисунок 6.5). Эта таблица называется таблицей методов интерфейса. В ней хранятся указатели на методы класса, реализующие методы интерфейса.
Интерфейсная переменная хранит ссылку на скрытое поле объекта, которое содержит указатель на таблицу методов интерфейса. Когда интерфейсной переменной присваивается значение объектой переменной,
|
Intf := Obj; // где Intf: ITextReader и Obj: TTextReader |
к адресу объекта добавляется смещение до скрытого поля внутри объекта и этот результат заносится в интерфейсную переменную. Чтобы убедиться в сказанном, посмотрите в отладчике значения Pointer(Obj) и Pointer(Intf) сразу после выполнения оператора Intf := Obj. Эти значения будут разными! Причина в том, что объектная ссылка указывает на начало объекта, а интерфейсная ссылка — на скрытое поле внутри объекта.
Рисунок
6.5. Представление интерфейса в памяти
Алгоритм вызова метода интерфейса такой же, как алгоритм вызова метода класса. Когда через интерфейсную переменную выполняется вызов метода,
|
Intf.NextLine; |
реализуется следующий алгоритм:
Из интерфейсной переменной извлекается адрес (по нему хранится адрес таблицы методов интерфейса);
По полученному адресу извлекается адрес таблицы методов интерфейса;
На основании порядкового номера метода в интерфейсе из таблицы извлекается адрес соответствующей подпрограммы;
Вызывается код, находящийся по этому адресу. Этот код является переходником от метода интерфейса к методу объекта. Его задача — восстановить из ссылки на интерфейс значение указателя 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 за понятием интерфейса. Причем несмотря на такое количество операторов в примере, вызов метода через интерфейс в машинном коде выполняется весьма эффективно (всего несколько инструкций процессора), поэтому в подавляющем большинстве случаев потерями на вызов можно пренебречь.
