Интерфейс IUnknown

Базовым интерфейсом в модели СОМ является интерфейс IUnknown. Любой другой интерфейс наследуется от IUnknown и должен реализовать объявленные в нем методы. Интерфейс IUnknown объявлен в модуле System.pas следующим образом:

type IInterface = interface

[‘{00000000-0000-0000-C000-000000000046}’]

function QueryInterface(const IID: TGUID; outObj): HResult; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

end;

IUnknown = IInterface;

 

В связи с поддержкой мультиплатформенной разработки (Kylix) в последних версиях Delphi базовым интерфейсом является интерфейс IInterface, описание которого полностью совпадает с описанием интерфейса IUnknown. Поскольку рассматриваются технологии СОМ, в дальнейшем в качестве базового интерфейса будем считать интерфейс IUnknown.

На уровне интерфейса IUnknown, который является предком для всех интерфейсов, уже реализовано корректное решение проблемы резервирования и освобождения ресурсов операционной системы. Напомним эту проблему: если в каком-нибудь модуле были затребованы ресурсы операционной системы, то их нельзя возвратить системе в другом модуле.

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

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

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

Для решения подобных проблем в интерфейсах при их реализации осуществляется подсчет ссылок. Специально для подсчета ссылок во всех интерфейсах имеются методы _AddRef и _Release. Метод _AddRef при реализации обязан увеличивать счетчик внутренней переменной. «Обязан» потому, что реализация методов интерфейсов возложена на разработчика, который создает СОМ-сервер. Он может этого и не сделать, но тогда интерфейс будет работать некорректно. Соответственно, метод _Release обязан уменьшать этот счетчик. Кроме того, этот метод должен проверять, равен ли счетчик ссылок нулю, и, если равен, вызывать деструктор объекта, в котором реализован интерфейс.

Таким образом, когда клиент требует ссылку на интерфейс, клиентом в адресном пространстве сервера создается объект, резервируются ресурсы и вызывается метод _AddRef. Если другой клиент затребует тот же самый интерфейс, то, чаще всего, увеличивается счетчик ссылок на уже имеющийся объект, и указатель передается второму клиенту (допустима ситуация, когда создается вторая копия объекта в памяти, но счетчик ссылок в обеих копиях остается равным единице). Соответственно, когда клиенту уже не нужен интерфейс, он вызывает метод _Release. Этот метод уменьшает счетчик ссылок на единицу. Одновременно проверяется, равен ли счетчик ссылок нулю, и, если равен, вызывается деструктор объекта.

Вызов деструктора происходит из сервера — поэтому ресурсы освобождаются корректно.

Интерфейс IUnknown содержит также метод QueryInterface, который используется для получения клиентом ссылок на интерфейс. Клиент вызывает метод QueryInterface интерфейса IUnknown COM-сервера и указывает идентификатор IID (Interface Identifier — идентификатор интерфейса) того интерфейса, ссылку на который он хочет получить. IID — это тот же самый тип данных, что и GUID, и применяется он для идентификации интерфейсов. Ссылка на интерфейс IUnknown сервера становится доступной при помощи фабрики классов ICIassFactory. Метод QueryInterface обязан проверять все идентификаторы IID интерфейсов, реализованные в данном классе многокомпонентного объекта (CoClass, СОМ-класс). Если будет найден совпадающий идентификатор IID, то метод QueryInterface обязан сделать следующее.

1. Вызвать конструктор, если не был создан экземпляр класса, в котором реализован интерфейс.

2. Вызвать метод _AddRef для затребованного интерфейса и тем самым увеличить счетчик ссылок на 1. Иногда для группы интерфейсов реализуется общий счетчик ссылок.

3. Поместить указатель на созданный (или имеющийся) объект, в котором реализован интерфейс, в нетипизированную переменную Р.

4. Возвратить результат S_OK.

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

 

Типичный пример реализации метода QueryInterface приведен ниже:

Function TMyClassFactory.QueryInterface(const iid:TIID; var Р): HResult;

begin

if IsEqualIID(iid, IID_IClassFactory) or

IsEqualIID( iid,IID_IUnknown) then begin

Pointer(P) := Self;

_AddRef;

Result := S_OK;

end else begin

Pointer(P) := nil;

Result := E_NOINTERFACE;

end;

end;

 

В модуле System.pas объявлен класс TInterfacedObject, реализующий интерфейс IUnknown и методы этого интерфейса.

Кроме этого, поддержка интерфейсов реализована в базовом классе TObject. Он имеет следующий метод:

function TObject.GetInterface(const IID: TGUID; out Obj): Boolean;

Если класс реализует запрошенный интерфейс, то эта функция:

· возвращает ссылку на этот интерфейс в параметре Obj;

· вызывает метод _AddRef полученного интерфейса;

· возвращает True.

В противном случае функция возвращает False.

Таким образом, имеется возможность запросить у любого класса Delphi реализуемый им интерфейс.