Королевство Дельфи"Knowledge itself is power"
F.Bacon
 Лицей
  
Главная
О лицее

Список семинаров

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  19:59[Войти] | [Зарегистрироваться]

Урок 5. Интерфейс IUnknown

Инна Аринович
Антон Григорьев
дата публикации 31-01-2005 03:05

урок из цикла: Использование COM/DCOM в Delphi


предыдущий урок содержание семинара следующий урок

Урок 5. Интерфейс IUnknown

Интерфейс IUnknown является базовым интерфейсом для COM/DCOM. Все интерфейсы должны быть его наследниками (прямыми или косвенными). Никакие исключения из этого правила не допускаются.

Интерфейс IUnknown определяется в модуле System.pas следующим образом:

Type
IUnknown = interface
[''{00000000-0000-0000-C000-000000000046}'']
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;

Функция QueryInterface служит для получения указателя на интерфейс COM-объекта. Напомним, что кокласс может иметь несколько интерфейсов. Так как каждый из этих интерфейсов является наследником IUnknown, в каждом из них есть функция QueryInterface. Таким образом, имея указатель на один из интерфейсов COM-объекта, можно получить указатель на любой другой его интерфейс.

Параметр iid специфицирует IID требуемого интерфейса, в параметр Obj будет записан указатель на требуемый интерфейс. Если кокласс не содержит интерфейс с заданным IID, функция должна вернуть E_NOINTERFACE. Кроме того, в Obj в этом случае должно быть значение nil. (Вообще, это общее правило для COM/DCOM: если функция завершается с ошибкой, она должна все свои выходные параметры, являющиеся указателями, установить в nil.)

Рассмотрим ситуацию, когда кокласс содержит некоторый интерфейс IInterface2, унаследованный от IInterface1. Они имеют идентификаторы IID2 и IID1 соответственно. Данный кокласс может использоваться клиентом, который ничего не знает про IInterface2, и поэтому попытается запросить IInterface1. Спецификация COM/DCOM требует, чтобы функция QueryInterface корректно отрабатывала этот вызов и возвращала IInterface1 (те классы, которые реализуют интерфейсы не для COM/DCOM, могут не следовать этому правилу). Если цепочка наследования интерфейсов более длинная, функция QueryInterface должна возвращать указатель на любой из интерфейсов этой цепочки. В частности, это означает, что через QueryInterface можно получить указатель непосредственно на интерфейс IUnknown, так как он стоит в начале любой цепочки наследования.

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

В документации Microsoft по COM/DCOM не используется символ подчеркивания перед функциями _AddRef и _Release. С точки зрения COM/DCOM это не имеет значения, принципиальным является порядок следования, количество и типы параметров и модель вызова функции. Для Delphi эти методы являются особенными. Компилятор автоматически вызывает эти методы для того, чтобы обеспечить правильный подсчёт ссылок. Каждый раз, когда переменная интерфейсного типа меняет своё значение, для её старого значения, если оно не равно nil, вызывается _Release, а для нового, если оно не равно nil - _AddRef. Когда переменная выходит из области видимости, для неё также вызывается _Release, если она не равна nil. Компилятор также устанавливает любым интерфейсным переменным начальное значение nil, чтобы первое присвоение не приводило к вызову _Release по "мусорному" адресу. Программист не должен вызывать функции _AddRef и _Release напрямую.

Спецификация COM/DCOM требует, чтобы функции _AddRef и _Release возвращали любое ненулевое значение, если счётчик ссылок объекта не равен нулю, и нулевое - если равен. На практике они обычно возвращают значение счётчика. Тем не менее, COM/DCOM разрешает клиентам использовать возвращаемые этими функциями значения только в отладочных целях. В окончательной версии программы эти значения должны игнорироваться. Программисту на Delphi очень легко следовать данному правилу: так как _AddRef и _Release вызываются компилятором неявно, получить доступ к возвращаемым значениям просто невозможно.

Рассмотрим пример:

type
  IInterface1=interface  // прямой наследник Inknown
[''{FD054108-7B86-4D1A-BDE3-887465E00099}'']
    function Func1:HRESULT;
   end;

   IInterface2=interface(IInterface1)
[''{193464E5-DD2B-4A1B-B469-26B6A9F1F766}'']
  function Func2: HRESULT;
   end;

   TTest=class(TObject,IUnknown,IInterface2)
   private
     FRefCount: Integer; // счетчик ссылок
    { IUnknown }
     function QueryInterface(const IID: TGUID; out Obj): HRESULT; stdcall;
     function _AddRef: Integer; stdcall;
     function _Release: Integer; stdcall;
    { IInterface1 }
    function Func1: HRESULT;
     procedure Test1;
    { IInterface1 }
    function Func2: HRESULT;
   public
    constructor Create;
   end;

{ TTest реализация }
constructor TTest.Create;
 begin
  inherited Create;
  FRefCount:=0
  { Первоначально на вновь созданный объект нет ссылок, поэтому
    счётчик ссылок должен быть инициализирован нулём }
 end;

function TTest.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
{используем автоматическую поддержку интерфейсов}
    if GetInterface(IID, Obj) then Result := S_OK
    else Result := E_NOINTERFACE;
end;

function TTest._AddRef: Integer;
begin
  Inc(FRefCount); // увеличение числа ссылок
  Result:=FRefCount;
end;

function TTest._Release: Integer;
begin
  Dec(FRefCount); // уменьшение числа ссылок
  Result := FRefCount;
  if Result = 0 then // удаление объекта
    Destroy;
end;

Рассмотрен случай однонитевого сервера, которому не нужно защищать свои данные от одновременного доступа из нескольких нитей (подробнее нитевые модели серверов рассмотрены в разделе "Нитевые модели").

Интерфейсы "совместимы" с объектами, их поддерживающими. В кавычках, потому что внутренний механизм отличается от совместимости объектов.

var
  I1:IInterface1;
  I2:IInterface2;
begin
  I2:=TTest.Create;
{Получение интерфейса на этапе компиляции}
I1:=TTest.Create;
{А эта сточка выдаст ошибку компиляции
    Incompatible types; класс не объявил о поддержке IInterface1}

Используя один интерфейс, можно получить другой с помощью оператора as. Оператор as, применённый к интерфейсному объекту, означает вызов QueryInterface. Под интерфейсным объектом подразумевается либо другой интерфейс, либо объект, реализующий интерфейс. Например,

I1:=TTest.Create as Interface1;

Это означает получение интерфейса IUnknown на этапе компиляции (из чего следует, что IUnknown должен присутствовать в объявлении класса или его предка). Затем вызов метода QueryInterface. Если QueryInterface не вернет S_OK, возникнет исключительная ситуация "Interface not supported". Т. е. произойдет ошибка времени исполнения.

Вернёмся к приведённому выше примеру реализации TTest. Выкинем из него все упоминания об интерфейсе IInterface2, оставив только реализацию IUnknown. Теперь породим от него наследника, реализующего IInterface2:

class TTest2=class(TTest,IInterface2)

В классе TTest2 мы теперь должны будем реализовать только методы Func1 и Func2, а реализацию методов IUnknown будет унаследована от TTest. Функция GetInterface, использованная в QueryInterface, определяет набор интерфейсов, реализуемых классом, динамически, поэтому сможет "почувствовать", что объект реализует ещё и IInterface2. Легко видеть, что TTest теперь можно использовать как базовый класс для любых других классов, реализующих интерфейсы. Таким образом, IUnknown достаточно реализовать один раз в TTest, а потом эту реализацию можно использовать как базу для всех остальных классов, реализующих любые интерфейсы.

На самом деле нет необходимости самостоятельно создавать класс, реализующий IUnknown, чтобы потом порождать от него другие классы - в модуле System уже есть готовый такой класс, который называется TInterfacedObject. От нашего примитивного TTest TInterfacedObject отличается более ответственным подходом к подсчёту ссылок на этапе создания класса. Пусть в нашем классе TTest2, порождённом от TTest, конструктор выглядит следующим образом:

constructor TTest2.Create;
 var Interface2:IInterface2;
  begin
   inherited Create;
   Interface2:=Self;
   // Далее вызываются методы Interface2
  end;

После выполнения унаследованного конструктора счётчик ссылок будет равным нулю. Получение интерфейса IInterface2 увеличит его на единицу. После завершения конструктора при финализации переменной Interface2 автоматически будет вызван метод _Release, который уменьшит счётчик ссылок до нуля и уничтожит объект. Таким образом, экземпляр наследника TTest невозможно будет создать, если этот наследник с своём конструкторе использует ссылки на свои же интерфейсы.

Чтобы избежать этой проблемы, в конструкторе класса TInterfacedObject (точнее - в его методе NewInstance, который вызывается из конструктора) счётчик ссылок инициализируется не нулём, а единицей, а потом, в методе AfterConstruction, который вызывается после полного выполнения конструктора, счётчик уменьшается на эту лишнюю единицу. Эти дополнительные манипуляции со счётчиком обеспечивают безопасность использования своих интерфейсов в конструкторе наследника.

TInterfacedObject также следит за тем, чтобы объект освобождался только при уменьшении счётчика ссылок до нуля, т.е. за счёт неявных вызовов _Release. Если попытаться освободить такой объект явным вызовом его деструктора, метод BeforeDestruction инициирует ошибку времени выполнения.

Ещё одним отличием TInterfacedObject от TTest является использование синхронизации при работе со счётчиком. Таком образом, реализация IUnknown в TInterfacedObject обеспечивает корректный подсчёт ссылок при использовании объекта несколькими нитями.

TInterfacedObject обеспечивает только реализацию методов IUnknown. Этого достаточно при использовании интерфейсов для внутренних нужд программы, но базовая функциональность COM-объекта должна быть существенно шире. Поэтому для создания COM-объектов классы обычно наследуются не от TInterfacedObject, а от TComObject, который будет рассмотрен позже.

Примечание:
Начиная с версии 6, базовым интерфейсом Delphi является IInterface, который полностью соответствует IUnknown. Рекомендуется использовать IInterface для платформонезависимых приложений и IUnknown для приложений под Windows.


предыдущий урок содержание семинара следующий урок




Смотрите также материалы по темам:


 Обсуждение материала [ 25-05-2007 02:25 ] 11 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования