- •Динамически загружаемые библиотеки
- •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.13. Получение интерфейса через другой интерфейс
Через интерфейсную переменную у объекта всегда можно запросить интерфейс другого типа. Для этого используется оператор as, например:
var Intf: IInterface; begin ... with Intf as ITextReader do Active := True; ... end; |
Если объект действительно поддерживает запрашиваемый интерфейс, то результатом является ссылка соответствующего типа. Если же объект не поддерживает интерфейс, то возникает исключительная ситуация EIntfCastError.
В действительности оператор as преобразуется компилятором в вызов метода QueryInterface:
var Intf: IInterface; IntfReader: ITextReader; ... IntfReader := Intf as ITextReader; // Intf.QueryInterface(ITextReader, IntfReader); |
Напомним, что метод QueryInterface описан в интерфейсе IInterface и попадает автоматически во все интерфейсы. Стандартная реализация этого метода находится в классе TInterfacedObject.
6.14. Механизм подсчета ссылок
Механизм подсчета ссылок на объект предназначен для автоматического уничтожения неиспользуемых объектов. Неиспользуемым считается объект, на который не ссылается ни одна интерфейсная переменная.
Подсчет ссылок на объект обеспечивают методы _AddRef и _Release интерфейса IInterface. При копировании значения интерфейсной переменной вызывается метод _AddRef, а при уничтожении интерфейсной переменной — метод _Release. Вызовы этих методов генерируются компилятором автоматически:
var Intf, Copy: IInterface; begin ... Copy := Intf; // Copy._Release; Intf._AddRef; Intf := nil; // Intf._Release; end; // Copy._Release |
Стандартная реализация методов _AddRef и _Release находится в классе TInterfacedObject. Она достаточно проста и вы легко разберетесь с ней, читая комментарии в исходном тексте.
type TInterfacedObject = class(TObject, IInterface) ... FRefCount: Integer; // Счетчик ссылок function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; ... end;
function TInterfacedObject._AddRef: Integer; begin Result := InterlockedIncrement(FRefCount); // Увеличение счетчика ссылок end;
function TInterfacedObject._Release: Integer; begin Result := InterlockedDecrement(FRefCount); // Уменьшение счетчика ссылок if Result = 0 then // Если ссылок больше нет, то Destroy; // уничтожение объекта end; |
Заметим, что функции InterlockedIncrement и InterlockedDecrement просто увеличивают значение целочисленной переменной на единицу. В отличие от обычного оператора сложения, они обеспечивают атомарное изменение значения переменной, что очень важно для правильной работы распараллеленных (многопоточных) программ.
Приведенную выше реализацию методов _AddRef и _Release автоматически получают все наследники класса TInterfacedObject, в том числе и классы TTextReader, TDelimitedReader и TFixedReader. Поэтому неиспользуемые объекты классов TDelimitedReader и TFixedReader тоже автоматически уничтожаются при работе с ними через интерфейсные переменные:
var Obj: TDelimitedReader; Intf, Copy: ITextReader; begin Obj := TDelimitedReader.Create('MyData.del', ';'); Intf := Obj; // Obj._AddRef -> Obj.FRefCount = 1 Copy := Intf; // Obj._AddRef -> Obj.FRefCount = 2 ... Intf := nil; // Obj._Release -> Obj.FRefCount = 1 Copy := nil; // Obj._Release -> Obj.FRefCount = 0 -> Obj.Destroy Obj.Free; // Ошибка! Объект уже уничтожен и переменная Obj указывает в никуда end; |
Обратите внимание, что объектные переменные не учитываются при подсчете ссылок. Поэтому мы настоятельно рекомендуем избегать смешивания интерфейсных и объектных переменных. Если вы планируете использовать объект через интерфейс, то лучше всего результат работы конструктора сразу присвоить интерфейсной переменной:
var Intf: ITextReader; begin Intf := TDelimitedReader.Create('MyData.del', ';'); // FRefCount = 1 ... Intf := nil; // FRefCount = 0 -> Destroy end; |
Если интерфейс является входным параметром подпрограммы, то при вызове подпрограммы создается копия интерфейсной переменной с вызовом метода _AddRef:
procedure LoadItems(R: ITextReader); begin ... end;
var Reader: ITextReader; begin ... LoadItems(Reader); // Создается копия переменной Reader и вызывается Reader._AddRef end; |
Копия не создается, если входной параметр описан с ключевым словом const:
procedure LoadItems(const R: ITextReader); begin ... end;
var Reader: ITextRedaer; begin ... LoadItems(Reader); // Копия не создается, метод _AddRef не вызывается end; |
Интерфейсная переменная уничтожается при выходе из области действия переменной, а это значит, что у нее автоматически вызывается метод _Release:
var Intf: ITextRedaer; begin Intf := TDelimitedReader.Create('MyData.del', ';'); ... end; // Intf._Release |