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

   
На этом шаге мы рассмотрим интерфейс IUnknown.

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

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

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

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

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

   
При затребовании ссылки на интерфейс клиентом в адресном пространстве сервера создается объект,
резервируются ресурсы, и вызывается метод AddRef. Клиент напрямую не вызывает этот метод:
он вызывается при выполнении метода QueryInterface. Если другой клиент затребует
тот же самый интерфейс, то чаще всего увеличивается счетчик ссылок на уже имеющийся объект
и указатель передается второму клиенту. Однако допустима ситуация, когда создается вторая
копия объекта в памяти и счетчик ссылок при этом равен по единице в обеих копиях. Соответственно,
когда клиенту уже не нужен интерфейс, он вызывает метод Release. Этот метод уменьшает
счетчик ссылок на единицу. Одновременно проверяется, равен ли счетчик ссылок нулю, и
если он оказывается равным нулю, то вызывается деструктор объекта.

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

   
Если COM-клиент реализуется на Delphi, то нет необходимости вызывать в явном
виде метод Release. Достаточно присвоить переменной, в которой хранится указатель на
интерфейс, значение nil (или unassigned, если указатель хранится в переменной
типа Variant). Можно вообще ничего не присваивать таким переменным - перед
их разрушением (освобождение стека, удаление экземпляра класса, закрытие приложения) Delphi
генерирует код, который проверяет переменные на наличие ссылки на интерфейс, и если она есть,
то метод Release вызывается автоматически.

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

  • Вызвать конструктор, если не был создан экземпляр класса, в котором реализован интерфейс.
  • Вызвать метод AddRef для затребованного интерфейса, и тем самым увеличить счетчик ссылок на единицу.
    Иногда для группы интерфейсов реализуется общий счётчик ссылок.
  • Поместить указатель на созданный (или имеющийся) объект, в котором реализован интерфейс, в нетипизированную переменную P.
  • Возвратить результат S_OK.
  •    
    Если же интерфейс с данным идентификатором IID не поддерживается, то в нетипизированную переменную P возвращается nil
    и результат вызова метода должен быть равен E_NOINTERFACE. Типичный пример реализации
    метода QueryInterface приведен ниже:

    function TMyClassFactory.QueryInterface(const iid: TIID; var P): 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;
    

       
    На следующем шаге мы рассмотрим интерфейс IClassFactory.



    Вы можете оставить комментарий, или Трекбэк с вашего сайта.

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