Вторник, 30.04.2024, 11:29
Распределенная обработка данных
Здравствуйте Гость | RSS
Главная страница Методы IUnknown – AddRef и Release Регистрация Вход
Меню сайта

Считалка

Онлайн всего: 1
Гостей: 1
Пользователей: 0

Форма входа

§ 10. Методы  IUnknown – AddRef ( ) и Release ( ).

 

         За управление временем жизни компонентов отвечает два метода интерфейса IUnknown: AddRef ( ) и Release ( ). Обычно СОМ-компонент имеет несколько интерфейсов, каждый из которых может быть связан со многими внешними компонентами. В нашем примере компонент в действительности является классом С++, и мы будем обсуждать управление временем жизни определенного экземпляра класса. AddRef  и Release реализуют технику управления памятью, известную, как подсчет ссылок.

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

§ 10.1. Правила подсчета ссылок.

 

         Используют 3 правила:

         1) Вызывать AddRef перед возвратом функции, возвращающие интерфейсы, перед возвратом всегда должны вызывать AddRef для соответствующего указателя. Это также относится к Query Interface и функции CreateInstance. Таким образом, не нужно вызывать AddRef в своей программе после получения (от функции) указателя на интерфейс.

         2) По завершении работы вызывать Release. После окончания работы с интерфейсом следует вызывать для него Release.

         3) Вызывать AddRef после присваивания. Если один указатель на интерфейс присваивается другому, вызывать AddRef. Иными словами: следует увеличивать счетчик ссылок всякий раз, когда создается новая ссылка на данный интерфейс.

         Пример использования правила 3.

 

// Получить указатель на IUnknown

IUnknown*pIUnknown =  CreateInctance ( );

 

// Получить интерфейс IX

IX*pIX = NULL;

HRESULT hs = pIUnknown → Query Interface (IDD_IX, (void**)&pIX);

pIUnknown  Release ( ); // Завершить работу с IUnknown

         if (SUCCEEDED(h2))

         {

         pIXFX ( )                    // Использовать интерфейс IX

                   IX*pIX2 → pIX;                 // Создать копию pIX

                   pIX2 → FX ( );       // Что-то сделать при помощи pIX2

                   pIX2 → Release ( );     // Завершить работу с pIX2

                   pIXRelease ( );       // Завершить работу с pIX

}

        

Подсчет ссылок на отдельные интерфейсы.

 

         С точки зрения клиента, подсчет ссылок ведется на уровне интерфейсов, а не на уровне компонентов. Таким образом клиент считает, что у каждого интерфейса – свой счетчик ссылок. Компонент может поддерживать отдельные счетчики для каждого из интерфейсов, а может иметь один общий счетчик.



Рис. 10. Программист компонента может использовать один счетчик ссылок для всего компонента либо отдельные счетчики ссылок для интерфейсов.

        

Когда перекрывание времен жизни указателей на интерфейсы. Здесь надо подсчитывать ссылки на оба интерфейса.

 

§ 10.2. Реализация AddRef ( ) и Release ( ).

 

         Для реализации функций AddRef ( ) и Release ( ) унаследованных от класса IUnknown введем переменную m _ cRef, которая отвечает за подсчет текущих обращений к объекту или ожидающих обработку указателей интерфейса. Хотя функции AddRef ( ) и Release ( ) могут изменять значение счетчика обращений к СОМ-интерфейсу, сам интерфейс не является экземпляром объекта. Объект в каждый момент времени может иметь любое количество пользователей своих интерфейсов и должен отслеживать значение внутреннего счетчика активных интерфейсов.

        

ULONG _ stdcall  AddRef ( )

{

   return   ++m _ cRef;

}

 

ULONG _ stdcall  Release ( )

{

   if (--m _ cRef = = 0)

   {

       delete this;

       return 0;

   }

   return m _ cRef;

}

        

         AddRef увеличивает значение переменной m _ cRef, счетчика ссылок. Release уменьшает m _ cRef и удаляет компонент, если значение переменной становится равным нюлю. Во многих случаях можно встретить реализацию AddRef  и Release при помощи функций Win32 Interlocked Increment и Interlocked Decrement. Эти функции гарантируют, что значение переменной изменяет в каждый момент времени только один поток управления и это обеспечивает в свою очередь, возможность синхронизировать доступа к внутренним счетчикам активных интерфейсов.

 

         ULONG _ stdcall AddRef ( )

    {

        return Interlocked Increment (& m _ cRef)

}

   

ULONG _ stdcall  Release ( )

{

if (Interlocked Decrement (& m _ cRef) = = 0)

    {

        delete this;

        return 0;

}

}

§ 10.3.  Множественные интерфейсы.

 

         Одним из наиболее мощных свойств СОМ является то, что каждый компонент предоставляет несколько интерфейсов для одного объекта. При разработке С++ - класса создается всего один интерфейс. Обычно при построении СОМ-компонента необходимо предоставить несколько интерфейсов для одного экземпляра класса С++.

         Объявление нескольких для одного класса С++ на самом деле является непростым делом. Во-первых, поскольку СОМ-интерфейсы фактически являются указателями на виртуальную таблицу функций С++, класс с множеством интерфейсов требует создания множества виртуальных таблиц. Другой причиной взаимосвязи компонентных объектов и их классов реализации интерфейса является необходимость подсчета количества обращений. Все интерфейсы СОМ – объекта, должны взаимодействовать между собой в обеспечение подсчета обращений. Для каждого объекта существует только один счетчик обращений. Для каждого объекта существует только один счетчик обращений, и интерфейсы должны использовать его совместно. Эта взаимосвязь осуществляется с помощью множества интерфейсов IUnknow. Каждый СОМ-компонент должен обладать собственной реализацией интерфейса IUnknown.

         В языке программирования С++  существует три основных способа обеспечения множества интерфейсов для СОМ-компонента:

1) множественное наследование;

2) реализации интерфейсов;

3) вложенность классов С++.

Мы будем использовать множественное наследование классов С++, т.к. с его помощью в активной библиотеке шаблонов (ATL) реализованы СОМ-интерфейсы. Вложенность классов С++ применяется и в библиотеках базовых Microsoft (MFC).

 

§ 10.4. Язык С++ и множественное наследование.

 

         Допустим компонент Math предоставляет нам два интерфейса IMath и IExpression. Используя множественное наследование, снабдим класс Math обоими интерфейсами:

        

class Math: public IMath, public  IExpression

{

// Реализация компонента включает и реализацию каждого наследуемого //интерфейса

};

 

         Единственная проблема этого подхода состоит в том, что в таком случае может возникнуть конфликт базовых интерфейсов IUnknown (так как оба класса, и IMath, и  IExpression, наследуется  IUnknown), однако в данном случае конфликта не происходит, поскольку унаследованный класс должен «совместно использовать» реализацию интерфейса IUnknown.

         Важным моментом является то, что СОМ-компоненты должны предоставлять несколько интерфейсов или, другими словами, несколько указателей на виртуальные таблицы. Если обеспечивать это с помощью множественного наследования, то необходимо, чтобы тип указателя данного экземпляра правильно приводился к типу указателя на виртуальную таблицу. Таким образом, метод Query Interface для класса Math будет иметь вид:

 

HRESVLT MATH : : Query Interface (REFIID riid, void ** ppv)

{

  switch (*iid)

  {

     case IID_ IUnknown:

     case IID_ IMath:

// Множественное наследование требует явного приведения типов

          *ppv = (IMath*) this;

     break;

     case IID_ IExpression:

 

// Множественное наследование требует явного приведения типов

          *ppv = (IExpression*) this;

     break;

     default:

         return (E_NOINTERFASE);

  }

 

// Возвращаем указатель нового интерфейса и таким образом

// вызывается  AddRef для нового указателя

 

  AddRef ( );

  return (S_OK);

}

 

Указатель на интерфейс IUnknown может быть возвращен посредством обращения к IMath или IExpression, поскольку оба эти класса содержат такой метод. Допустим это будет обращение к IMath. Для реализации Query Interface был использован оператор switch, хотя на сомом деле нельзя, поскольку идентификатор интерфейса  - структура (IDD), а не константа.

Другой вариант реализации Query Interface:

 

HRESULT _ stdcall CA::Query Interface

(const IDD&iid, void **ppv )

{

if (iid = = IID _ IUnknown)

{

// Клиент запрашивает интерфейс IUnknown

*ppv = static _ cast <IX*>(this);           //(= (IX*)this)

}

else if (iid = = IID _ IX)

{

//Клиент запрашивает интерфейс IX

*ppv = static _ cast <IX*> (this);

}

else if (iid = = IID _ IY)

{

//Клиент запрашивает интерфейс IY

*ppv = static _ cast <IY*> (this);

}

else

{

// Мы не поддерживаем запрашиваемый компонентом интерфейс

//Установить возвращаемый указатель в NULL

*ppv = NULL;              //обязательно

return E _ NOINTERFASE;

}

static _ cast <IUncnown*> → AddRef ( );

return S _ OK;

}

        

         Перед присваиванием указателю, описанному как void, надо всегда явно приводить this к нужному типу. При возвращении указателя на IUncnown возникает неоднозначность:

*ppv = static _ cast < IUncnown*> (this);   // неодназначность

Поскольку IUncnown наследует два интерфейса, IX и IY. Таким образом, следует выбрать, какой из указателей  static _ cast <IUncnown*> (static _ cast <IX*> (this)) или

static _ cast <IUncnown> (static _ cast <IY*> (this)) – возвращать. В данном случае выбор несущественен, поскольку реализации указателей идентичны. Необходимо, чтобы для IUncnown возвращался один и тот же указатель.
Поиск

Друзья сайта

2024