Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Роджерсон Д. - Основы COM - 2000.pdf
Скачиваний:
412
Добавлен:
13.08.2013
Размер:
2.4 Mб
Скачать

117

Клиенты агрегируемого компонента никогда не получают указатели на неделегирующий IUnknown внутреннего компонента. Всякий раз, когда клиент запрашивает указатель на IUnknown, он получает IUnknown внешнего компонента. Указатель на неделегирующий IUnknown внутреннего компонента передается только внешнему компоненту. Теперь рассмотрим, как реализовать делегирующий IUnknown.

Реализация делегирующего IUnknown

К счастью, реализация делегирующего IUnknown проста — она передает вызовы либо внешнему, либо неделегирующему IUnknown. Далее приводится объявление компонента, поддерживающего агрегирование. Компонент содержит указатель m_pUnknownOuter. Если компонент агрегирован, указатель ссылается на внешний IUnknown. Если компонент не агрегирован, этот указатель ссылается на неделегирующий IUnknown. Всякий раз при обращении к делегирующему IUnknown вызов переадресуется интерфейсу, на который указывает m_pUnknownOuter. Делегирующий IUnknown реализован функциями, подставляемыми в строку (inline):

class CB : public IY, INondelegatingUnknown

{

public:

// Делегирующий IUnknown

virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)

{

// Делегировать QueryInterface

return m_pUnknownOuter->QueryInterface(iid, ppv);

}

virtual ULONG __stdcall AddRef()

{

// Делегировать AddRef

return m_pUnknownOuter->AddRef();

}

virtual ULONG __stdcall Release()

{

// Делегировать Release

return m_pUnknownOuter->Release();

}

//Неделегирующий IUnknown virtual HRESULT __stdcall

NondelegatingQueryInterface(const IID& iid, void** ppv); virtual ULONG __stdcall NondelegatingAddRef();

virtual ULONG __stdcall NondelegatingRelease();

//Интерфейс IY

virtual void Fy() { cout << “Fy” << endl; }

// Конструктор

CB(IUnknown* pUnknownOuter);

// Деструктор

~CB();

private:

long m_cRef;

IUnknown* m_pUnknownOuter;

};

Создание внутреннего компонента

Теперь, когда мы знаем, как реализовать внутренний компонент, обсудим, как он создается внешним компонентом. Чтобы пройти весь процесс создания, от начала до конца, рассмотрим код трех функций: функция Init внешнего компонента начинает процесс; затем вступает функция фабрики класса CreateInstance и конструктор внутреннего компонента.

Функция Init внешнего компонента

Первое, что делает внешний компонент при агрегировании — создает внутренний компонент. Основное различие между включением и агрегированием состоит в том, что во втором случае внешний компонент передает внутреннему внешний IUnknown. Приведенный ниже фрагмент кода показывает, как внешний компонент создает

118

внутренний. Обратите внимание, что второй параметр CoCreateInstance — это указатель на интерфейс IUnknown внешнего компонента.

Кроме того, отметьте себе, что пятый параметр запрашивает у внутреннего компонента указатель на IUnknown. Фабрика класса будет возвращать указатель на неделегирующий IUnknown внутреннего компонента. Как мы уже видели, это указатель необходим внешнему компоненту для передачи вызовов QueryInstance внутреннему компоненту. Здесь внешний компонент обязан запрашивать указатель на IUnknown; в противном случае он никогда не сможет получить его. Внешний компонент должен сохранить неделегирующий IUnknown внутреннего компонента для последующего использования.

В данном примере нет необходимости явно приводить указатель this к указателю на IUnknown, так как CA наследует только IX, и, следовательно, неявное преобразование не будет неоднозначным.

HRESULT CA::Init()

{

IUnknown* pUnknownOuter = this;

HRESULT hr = CoCreateInstance(CLSID_Component2, pUnknownOuter, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&m_pUnknownOuter);

if (FAILED(hr))

{

return E_FAIL;

}

return S_OK;

}

Реализация IClassFactory::CreateInstance внешнего компонента вызывает CA::Init. В других отношениях реализация IClassFactory внешнего компонента остается неизменной. Фабрика же класса внутреннего компонента подверглась некоторым изменениям, которые мы теперь и рассмотрим.

Функция IClassFactory::CreateInstance внутреннего компонента

Реализация IClassFactory::CreateInstance внутреннего компонента изменена, чтобы использовать InondelegatingUnknown вместо IUnknown. Код этой функции приведен ниже, отличия от предыдущих вариантов CreateInstance не возвращает автоматически ошибку, если pUnknownOuter не равен NULL (т.е. когда внешний компонент желает агрегировать внутренний). Однако CreateInstance обязана возвратить ошибку, если при этом iid отличен от IID_IUnknown. Когда компонент создается как внутренний в агрегате, он может возвратить только интерфейс IUnknown, иначе внешний компонент никогда не получил бы неделегирующего IUnknown (поскольку вызовы QueryInterface будут делегированы внешнему IUnknown).

HRESULT __stdcall Cfactory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid,

void** ppv)

{

// При агрегировании iid должен быть IID_IUnknown

if ((pUnknownOuter != NULL) && (iid != IID_IUnknown))

{

return CLASS_E_NOAGGREGATION;

}

// Создать компонент

CB* pB = new CB(pUnknownOuter); if (pB == NULL)

{

return E_OUTOFMEMORY;

}

// Получить запрошенный интерфейс

HRESULT hr = pB->NondelegatingQueryInterface(iid, ppv); PB->NondelegatingRelease();

return hr;

}

Для получения запрашиваемого интерфейса вновь созданного внутреннего компонента показанная выше функция CreateInstance вызывает не QueryInterface, а NondelegatingQueryInterface. Если внутренний компонент агрегируется, вызовы QueryInterface он будет делегировать внешнему IUnknown. Фабрика класса должна возвратить указатель на неделегирующий QueryInterface, поэтому он вызывает NondelegatingQueryInterface.

119

Конструктор внутреннего компонента

В приведенном выше коде CreateInstance указатель на внешний IUnknown передается конструктору внутреннего компонента. Конструктор инициализирует m_pUnknownOuter, которая используется делегирующим IUnknown для передачи вызовов либо неделегирующему, либо внешнему IUnknown. Если компонент не агрегируется (pUnknownOuter есть NULL), конструктор помещает в m_pUnknownOuter указатель на неделегирующий IUnknown. Это показано в приведенном ниже фрагменте:

CB::CB(IUnknown* pUnknownOuter) : m_cRef(1)

{

::InterlockedIncrement)&g_cComponents;

if (pUnknownOuter == NULL)

{

// Не агрегируется: использовать неделегирующий IUnknown m_pUnknownOuter = reinterpret_cast<IUnknown*>(

static_cast<INondelegatingUnknown*>(this)

);

}

else

{

// Агрегируется: использовать внешний IUnknown m_pUnknownOuter = pUnknownOuter;

}

}

Указатели внешнего компонента на интерфейсы внутреннего компонента

Когда я реализовывал CA::Init при создании внутреннего компонента, я запрашивал интерфейс IUnknown, а не IY. Однако наш компонент в действительности агрегирует IY. Поэтому неплохо было бы в самом начале проверить, поддерживается ли интерфейс IY внутренний компонент. Но, как указывалось выше, при агрегировании компонента внешний компонент может запрашивать только интерфейс IUnknown. Cfactory::CreateInstance возвращает CLASS_E_NOAGGREGATION, если ей передано что-либо, отличное от IID_IUnknown. Следовательно, нам необходимо запросить у внутреннего компонента интерфейс IY после его (компонента) создания.

Но здесь необходимо быть аккуратным и не запутаться. Когда Вы вызываете QueryInterface, чтобы получить по m_pUnknownInner указатель на интерфейс IID_IY, эта функция, как примерный школьник, вызывает для возвращаемого указателя AddRef. Поскольку внутренний компонент агрегирован, он делегирует вызов AddRef внешнему IUnknown. В результате увеличивается счетчик ссылок внешнего компонента, а не внутреннего. Я хочу еще раз это подчеркнуть. Когда внешний компонент запрашивает интерфейс через указатель на неделегирующий IUnknown или какой-либо еще интерфейс внутреннего компонента, счетчик ссылок внешнего компонента увеличивается. Это именно то, что требуется, когда интерфейс через указатель на интерфейс внутреннего компонента запрашивает клиент. Но в данном случае указатель на интерфейс IY запрашивает внешний компонент, и счетчик ссылок для этого указателя является счетчиком ссылок внешнего компонента. Таким образом, внешний компонент удерживает одну ссылку сам на себя! Если допустить такое, счетчик ссылок внешнего компонента никогда не станет нулем, и компонент никогда не будет удален из памяти.

Так как время существования указателя на IY, принадлежащего внешнему компоненту, вложено во время существования самого внешнего компонента, нам нет необходимости увеличивать счетчик ссылок. Но не вызывайте для уменьшения счетчика ссылок Release для IY — мы обязаны работать с интерфейсом IY так, как если бы у него был отдельный счетчик ссылок. (В нашей реализации компонента неважно, для какого указателя вызывать Release. Но в других случаях это может быть не так.) Освобождение интерфейса IY может освободить используемые им ресурсы. Следовательно, общее правило состоит в том, чтобы вызывать Release, используя указатель, переданный в CoCreateInstance. Версия CA::Init, запрашивающая интерфейс IY, приведена ниже:

HRESULT __stdcall CA::Init()

{

//Получить указатель на внешний IUnknown IUnknown* pUnknownOuter = this;

//Создать внутренний компонент

HRESULT hr = CoCreateInstance(CLSID_Component2,

PUnknownOuter,

// IUnknown внешнего компонента

CLSCTX_INPROC_SERVER,

IID_IUnknown,

// При агрегировании только IUnknown

(void**)&m_pUnknownInner);

if (FAILED(hr))

120

{

// Ошибка при создании компонента return E_FAIL;

}

//Этот вызов увеличит счетчик ссылок внешнего компонента

//Получить интерфейс IY внутреннего компонента

hr = m_pUnknownInner->QueryInterface(IID_IY, (void**)&m_pIY); if (FAILED(hr))

{

// Внутренний компонент не поддерживает интерфейс IY m_pUnknownInner->Release();

return E_FAIL;

}

//Нам нужно уменьшить счетчик ссылок на внешний компонент,

//увеличенный предыдущим вызовом

pUnknownOuter->Release(); return S_OK;

}

При реализации QueryInterface у нас есть выбор — либо возвращать m_pIY, либо вызывать QueryInterface внутреннего компонента. Поэтому можно использовать либо

else if (iid == IID_IY)

{

return m_pUnknownOuter->QueryInterface(iid, ppv);

}

как мы это делали, либо

else if (iid == IID_IY)

{

*ppv = m_pIY;

}

Итак, мы уже создали внутренний компонент, запросили у него интерфейс, скорректировали счетчик ссылок и вернули интерфейс клиенту. Чего мы еще не сделали, так это не освободили интерфейс в деструкторе внешнего компонента. Мы не можем просто вызвать m_pIY->Release, так как у нас нет для него подсчитанной ссылки. Мы убрали ее в функции Init внешнего компонента после того, как получили указатель на IY. Теперь необходимо повторить процедуру в обратном порядке, восстановив счетчик ссылок и вызвав Release для указателя на IY. Однако здесь следует быть осторожным, так как в противном случае этот последний вызов Release снова сделает счетчик ссылок внешнего компонента нулевым, и тот попытается удалить себя.

Таким образом, процесс освобождения нашего указателя на интерфейс внутреннего компонента состоит из трех этапов. Во-первых, необходимо гарантировать, что наш компонент не попытается снова удалить себя. Во-вторых, необходимо вызвать AddRef для внешнего компонента, так как любой вызов Release для внутреннего компонента вызовет Release для внешнего. И, наконец, мы можем освободить указатель на IY, хранящийся во внешнем компоненте. Соответствующий код приведен ниже:

//1. Увеличить счетчик ссылок во избежание

//рекурсивного вызова деструктора

m_cRef = 1;

//2. AddRef для внешнего IUnknown IUnknown* pUnknownOuter = this; pUnknownOuter->AddRef();

//3. Освободить интерфейс

m_pIY->Release();

Давайте мысленно проследим работу этого кода внешнего компонента. Первое, что мы делаем, — устанавливаем счетчик ссылок в 1. Далее мы увеличиваем его до двух. Затем вызываем Release для интерфейса IY. Внутренний компонент будет делегировать этот вызов внешнему. Последний уменьшит счетчик ссылок с 2 до 1. Если бы мы не установили ранее счетчик ссылок в 1, то компонент попытался бы во второй раз удалить себя.

В нашей реализации, когда внутренний компонент агрегируется, его функция Release просто делегируется внешнему IUnknown. Однако внешний компонент должен работать с внутренним так, как если бы тот вел отдельные счетчики ссылок для каждого интерфейса, поскольку другие реализации внутреннего компонента могут делать больше, чем просто делегировать Release внешнему компоненту. Внутренний компонент может также освобождать некоторые ресурсы или выполнять другие операции.

Соседние файлы в предмете Программирование на C++