пятница, 30 августа 2013 г.

Обращение к интерфейсам

Перевод раздела Interface References из справочной системы Delphi

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



Реализация ссылок на интерфейс

Переменные типа интерфейс позволяют вызывать методы интерфейса при том, что во время компиляции не известно, где реализован интерфейс. Тем не менее, они подчиняются следующим правилам:

  • Выражение, возвращающее тип интерфейса, предоставляет доступ только к методам и свойствам, объявленным в интерфейсе, но не к прочим компонентам класса, реализующего интерфейс.
  • Выражение, возвращающее тип интерфейса, не может ссылаться на объект, класс которого реализует интерфейс-потомок, кроме тех случаев, когда класс (или его предок) явным образом реализует и интерфейс-предок.

Например:

 type
   IAncestor = interface
   end;
   IDescendant = interface(IAncestor)
     procedure P1;
   end;
   TSomething = class(TInterfacedObject, IDescendant)
     procedure P1;
     procedure P2;
   end;
      // ...
 var
   D: IDescendant;
   A: IAncestor;
 begin
   D := TSomething.Create;  // работает!
   A := TSomething.Create;  // ошибка
   D.P1;  // работает!
   D.P2;  // ошибка
 end;

В этом примере А объявлена как переменная типа IAncestor. Поскольку в объявлении класса TSomething не указывается, что этот класс реализует интерфейс IAncestor, ссылка на экземпляр TSomething не может быть присвоена переменной A. Это можно будет сделать, только если изменить объявление Tsomething следующим образом:

 TSomething = class(TInterfacedObject, IAncestor, IDescendant)
  // ...

В этом случае первая ошибка станет допустимым присваиванием. D объявлена как переменная типа IDescendant. Пока D ссылается на экземпляр класса TSomething, вы не сможете с ее помощью получить доступ к методу P2 класса Tsomething, так как этот метод не является методом IDescendant. Но если изменить объявление D следующим образом:

 D: TSomething;
 

Вторая ошибка станет корректным вызовом метода.

На платформе Win32 обращения к интерфейсам обычно обрабатываются подсчетом ссылок, реализованного через методы _AddRef и _Release , наследуемые от System.IInterface. При использовании механизма подсчета ссылок по умолчанию, когда к доступ к объекту выполняется только через интерфейсы, разрушать его вручную нет необходимости. Объект автоматически разрушается в тот момент, когда последняя ссылка на него выходит из области видимости. Некоторые классы поддерживают интерфейсы для изменения управления жизненным циклом, определенным по умолчанию, а некоторые гибридные объекты используют подсчет ссылок только в том случае, когда объект не имеет владельца.

Глобальные переменные типа интерфейс могут быть инициализированы только значением nil.

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



Совместимость по присваиванию для интерфейсов

Переменные типа класс совместимы по присваиванию с любым интерфейсным типом, поддерживаемым этим классом. Переменные интерфейсного типа совместимы по присваиванию с любым интерфейсным типом-предком. Значение nil может быть присвоено любой переменной типа интерфейс.

Переменной типа вариант может быть присвоено значение выражения интерфейсного типа. Если интерфейс имеет тип IDispatch или его потомка, значение кода типа вариантной переменной изменится на varDispatch. В остальных случаях код типа варианта будет varUnknown.

Значение переменных типа вариант, код типа которых равен varEmpty, varUnknown или varDispatch, может быть присвоено переменной типа IInterface. Значение переменных типа вариант с кодом типа varEmpty или varDispatch могут быть присвоены переменным с типом IDispatch.


Преобразование типов интерфейсов

Выражение типа интерфейс может быть преобразовано к типу вариант. Если интерфейс имеет тип IDispatch или его потомок, код типа полученного в результате преобразования варианта примет значение varDispatch. В остальных случаях вариант будет иметь код типа varUnknown.

Варианты с кодом типа varEmpty, varUnknown или varDispatch могут быть преобразованы к IInterface. Вариант с кодом типа varEmpty или varDispatch могут быть преобразованы к IDispatch.


Запрос интерфейса

Вы можете использовать оператор as для преобразования типа с проверкой. Такой способ известен как запрос интерфейса и позволяет получить выражение типа интерфейс из ссылки на объект или другой интерфейс основываясь на действительном типе (тип во время выполнения). Запрос интерфейса имеет вид:

object as interface

где object – это выражение интерфейсного или вариантного типа или ссылка на класс, поддерживающий интерфейс, а interface – любой интерфейс, объявленный с GUID.

Запрос интерфейса возвращает значение nil, когда object имеет значение nil. В других случаях он передает GUID интерфейса в метод QueryInterface объекта, вызывая исключительную ситуацию, когда QueryInterface не возвращает ноль. Если QueryInterface возвращает ноль (это обозначает, что класс объекта поддерживает интерфейс), запрос интерфейса возвращает объекту ссылку на интерфейс.



Преобразование ссылок на интерфейс в объекты

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

 var
   LIntfRef: IInterface;
   LObj: TInterfacedObject;
 begin
   {Создание интерфейсного объекта и получения его интерфейса. }
   LIntfRef := TInterfacedObject.Create();
 
   {Преобразование интерфейса обратно в оригинальный объект. }
   LObj := LIntfRef as TInterfacedObject;
 end;

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

Оператор as вызывает исключительную ситуацию при неудачной попытке получения интерфейса заданного класса:

 var
   LIntfRef: IInterface;
   LObj: TInterfacedObject;
 begin
   { Создаем интерфейсный объект и получаем его интерфейс. }
   LIntfRef := TInterfacedObject.Create();
 
   try
     {Преобразуем интерфейс к TComponent. }
     LObj := LIntfRef as TComponent;
   except
     Writeln('LIntfRef не ссылается на экземпляр TComponent');
   end;  
 end;

Вы можете использовать и обычное (небезопасное) преобразование типов для получения из ссылки на интерфейс ссылку на объект. Как и в случае с небезопасным преобразованием типов объектов, этот метод не вызывает никаких исключительных ситуаций. Различия между небезопасным преобразованием типов объектов и преобразованием тип интерфейс-объект заключаются в том, что в первом случае операция преобразования при несоответствии типов возвращает нормальный указатель, во втором результатом операции будет значение nil. Пример показывает небезопасное преобразование типов:

 var
   LIntfRef: IInterface;
   LObj: TInterfacedObject;
 begin
   {создание интерфейсного объекта и получение его интерфейса. }
   LIntfRef := TInterfacedObject.Create();
 
   { Преобразование интерфейса к TComponent. }
   LObj := TComponent(LIntfRef);
 
   if LObj = nil then
     Writeln('LIntfRef не ссылается на экземпляр TComponent');
 
   {Преобразование интерфейса к TObject. }
   LObj := TObject(LIntfRef);
 
   if LObj <> nil then
     Writeln('LIntfRef не ссылается на TObject (или его наследников).');
 end;
 

Для того, чтобы избежать получения пустых ссылок при преобразовании следует использовать оператор is для проверки, может ли ссылка на интерфейс быть получена из заданного класса:

 if Intf is TCustomObject then ...
 
Замечание: Убедитесь, что при небезопасном преобразовании типов (или применении операторов as и is) вы используете именно объекты Delphi.

Комментариев нет:

Отправить комментарий