Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
delphi.pdf
Скачиваний:
191
Добавлен:
24.02.2016
Размер:
6.84 Mб
Скачать

type

TExtendedTextReader = class(TInterfacedObject, ITextReader, IExtendedTextReader)

...

end;

Теперь класс TExtendedTextReader совместим и с интерфейсом ITextReader, поэтому следующее присваивание корректно:

Intf := Obj;

Исключением из только что описанного правила является совместимость всех снабженных интерфейсами объектов с интерфейсом IInterface:

var

Obj: TExtendedTextReader; Intf: IInterface;

begin

...

Intf := Obj; // Правильно, IInterface – особый интерфейс.

...

end;

6.13. Получение интерфейса через другой интерфейс

Через интерфейсную переменную у объекта всегда можно запросить интерфейс другого типа. Для этого используется оператор as, например:

var

Intf: IInterface; begin

...

with Intf as ITextReader do Active := True;

...

end;

Если объект действительно поддерживает запрашиваемый интерфейс, то результатом является ссылка соответствующего типа. Если же объект не поддерживает интерфейс, то возникает исключительная ситуация

EIntfCastError.

В действительности оператор as преобразуется компилятором в вызов метода QueryInterface:

var

Intf: IInterface; IntfReader: ITextReader;

255

...

IntfReader := Intf as ITextReader; // Intf.QueryInterface(ITextReader, IntfReader);

Напомним, что метод QueryInterface описан в интерфейсе IInterface и попадает автоматически во все интерфейсы. Стандартная реализация этого метода находится в классе TInterfacedObject.

6.14. Механизм подсчета ссылок

Механизм подсчета ссылок на объект предназначен для автоматического уничтожения неиспользуемых объектов. Неиспользуемым считается объект, на который не ссылается ни одна интерфейсная переменная.

Подсчет ссылок на объект обеспечивают методы _AddRef и _Release интерфейса IInterface. При копировании значения интерфейсной переменной вызывается метод _AddRef, а при уничтожении интерфейсной переменной — метод _Release. Вызовы этих методов генерируются компилятором автоматически:

var

Intf, Copy: IInterface;

begin

 

 

...

// Copy._Release; Intf._AddRef;

Copy := Intf;

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;

256

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

 

...

nil;

// Obj._Release -> Obj.FRefCount = 1

 

Intf :=

-> Obj.Destroy

Copy :=

nil;

// Obj._Release -> Obj.FRefCount = 0

Obj.Free;

// Ошибка! Объект

уже уничтожен и

переменная Obj

указывает

в никуда

 

 

 

end;

 

 

 

 

 

Обратите внимание, что объектные переменные не учитываются при подсчете ссылок. Поэтому мы настоятельно рекомендуем избегать смешивания интерфейсных и объектных переменных. Если вы планируете использовать объект через интерфейс, то лучше всего результат работы конструктора сразу присвоить интерфейсной переменной:

var

Intf: ITextReader; begin

Intf := TDelimitedReader.Create('MyData.del', ';'); // FRefCount = 1

...

Intf := nil; // FRefCount = 0 - > Destroy

end;

257

Если интерфейс является входным параметром подпрограммы, то при вызове подпрограммы создается копия интерфейсной переменной с вызовом метода _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

6.15. Представление интерфейса в памяти

Глубокое понимание работы интерфейсов требует знания их технической реализации. Поэтому вам необходимо разобраться в том, как представляется интерфейс в оперативной памяти компьютера, и что стоит за операторами

Intf := Obj и Intf.NextLine.

258

Интерфейс по сути выступает дополнительной таблицей виртуальных методов, ссылка на которую укладывается среди полей объекта (рисунок 6.5). Эта таблица называется таблицей методов интерфейса. В ней хранятся указатели на методы класса, реализующие методы интерфейса.

Интерфейсная переменная хранит ссылку на скрытое поле объекта, которое содержит указатель на таблицу методов интерфейса. Когда интерфейсной переменной присваивается значение объектой переменной,

Intf := Obj; // где Intf: ITextReader и Obj: TTextReader

к адресу объекта добавляется смещение до скрытого поля внутри объекта и этот результат заносится в интерфейсную переменную. Чтобы убедиться в сказанном, посмотрите в отладчике значения Pointer(Obj) и Pointer(Intf) сразу после выполнения оператора Intf := Obj. Эти значения будут разными! Причина в том, что объектная ссылка указывает на начало объекта, а интерфейсная ссылка — на скрытое поле внутри объекта.

Рисунок 6.5. Представление интерфейса в памяти

Алгоритм вызова метода интерфейса такой же, как алгоритм вызова метода класса. Когда через интерфейсную переменную выполняется вызов метода,

Intf.NextLine;

реализуется следующий алгоритм:

1.Из интерфейсной переменной извлекается адрес (по нему хранится адрес таблицы методов интерфейса);

2.По полученному адресу извлекается адрес таблицы методов интерфейса;

259

3.На основании порядкового номера метода в интерфейсе из таблицы извлекается адрес соответствующей подпрограммы;

4.Вызывается код, находящийся по этому адресу. Этот код является переходником от метода интерфейса к методу объекта. Его задача — восстановить из ссылки на интерфейс значение указателя Self (путем вычитания заранее известного значения) и выполнить прямой переход на код метода класса.

Обычными средствами процедурного программирования этот алгоритм реализуется так:

type

TMethodTable = array[0..9999] of Pointer; TNextLineFunc = function (Self: ITextReader): Boolean;

var

// интерфейсная переменна

Intf: ITextReader;

IntfPtr: Pointer;

// адрес внутри интерфейсной

переменной

// указатель на таблицу методов

TablePtr: ^TMethodTable;

интерфейса

// указатель на метод

MethodPtr: Pointer;

begin

 

 

...

// 1)

извлечение адреса из

IntfPtr := Pointer(Intf);

интерфейсной переменной

// 2)

извлечение адреса таблицы

TablePtr := Pointer(IntfPtr^);

методов интерфейса

// 3)

извлечение адреса нужного метода

MethodPtr := TablePtr^[3];

из таблицы

// 4)

вызов метода через переходник

TNextLineFunc(MethodPtr)(Intf);

...

 

 

end.

 

 

Вся эта сложность скрыта в языке Delphi за понятием интерфейса. Причем несмотря на такое количество операторов в примере, вызов метода через интерфейс в машинном коде выполняется весьма эффективно (всего несколько инструкций процессора), поэтому в подавляющем большинстве случаев потерями на вызов можно пренебречь.

6.16. Применение интерфейса для доступа к объекту DLLбиблиотеки

Если вы поместите свой класс в DLL-библиотеку, то при необходимости использовать его в главной программе столкнетесь с проблемой. Подключение модуля с классом к главной программе приведет к включению в нее кода всех методов класса, т.е. задача выделения класса в DLL-

260

библиотеку не будет решена. Если же не подключить модуль с описанием класса, главная программа вообще не будет знать о существовании класса, и воспользоваться классом будет невозможно. Эта проблема решается с помощью интерфейсов. Покажем это на примере модуля ReadersUnit.

Сначала вынесем описание интерфейса ITextReader в отдельный модуль (например, ReaderIntf), чтобы этот модуль в дальнейшем можно было подключить к главной программе:

unit ReadersIntf;

interface type

ITextReader = interface(IInterface)

...

end;

implementation

end.

Затем удалим описание интерфейса из модуля ReadersUnit, а вместо него подключим модуль ReaderIntf:

unit ReadersUnit;

interface

uses ReaderIntf;

...

Наконец включим скорректированный модуль ReadersUnit в DLLбиблиотеку, которую назовем ReadersLib:

library ReadersLib;

uses

SysUtils, Classes, ReadersUnit;

{$R *.res}

begin end.

Вроде бы все готово, и теперь в главной программе достаточно подключить модуль ReaderIntf и работать с объектами через интерфейс ITextReader (рисунок 6.6).

261

Рисунок 6.6. Схема получения программы и DLL-библиотеки

Но постойте! А как в программе создавать объекты классов, находящихся в DLL-библиотеке? Ведь в интерфейсе нет методов для создания объектов! Для этого определим в DLL-библиотеке специальную функцию и экспортируем ее:

library ReadersLib;

...

function GetDelimitedReader(const FileName: string; const Delimiter: Char = ';'): ITextReader;

begin

Result := TDelimitedReader.Create(FileName, Delimiter); end;

exports GetDelimitedReader;

begin end.

Вглавной программе импортируйте функцию GetDelimitedReader, чтобы с

еепомощью создавать объекты класса TDelimitedReader:

program Example;

uses ReadersIntf;

function GetDelimitedReader(const FileName: string; const Delimiter: Char = ';'): ITextReader;

external 'ReadersLib.dll' name 'GetDelimitedReader';

var

Intf: ITextReader; begin

Intf := GetDelimitedReader;

262

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]