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

224

библиотеки типа нам необходимы операторы coclass и library. Операторы coclass, описывающие компоненты, помещены в отдельные файлы IDL, каждый из которых помечен суффиксом _C. Эти файлы импортируют файлы _I для используемых ими интерфейсов.

Этот подход отличает большая гибкость. Однако из каждого файла IDL получается несколько других файлов, так что их размножение сбивает с толку. Следующее соображение поможет Вам не путаться. Считайте, что _C означает CLSID, а _I — IID. Если Ваш код использует IID, необходимо включить соответствующий заголовочный файл _I. Например, IID_ITangramModel определен в MODEL_I.IDL. Если я запрашиваю IID_ITangramModel, то должен включить MODEL_I.H и скомпоновать с MODEL_I.C.

Если я создаю компонент TangramModel, мне нужен CLSID_TangramModel. Этот компонент описан в MODEL_C.IDL. Следовательно, нужно включить MODEL_C.H и скомпоновать с MODEL_C.C. Если файл IDL импортирует другой файл IDL, то в код на С++ необходимо включить заголовок для импортированного файла. Например, MODEL_I.IDL импортирует EVENTS_I.IDL. Поэтому, если Вы включаете MODEL_I.H, то нужно также включить и EVENTS_I.H.

Замечу, что суффиксы _I и _C — это мое личное соглашение. Вы можете называть эти файлы как угодно. Без суффиксов я всегда путал, что где находится. Теперь, если компилятор говорит, что не может найти CLSID, я уже знаю, что мне нужно включить и скомпоновать с файлом _C.

Файл DLLDATA.C

Компилятор MIDL не всегда генерирует новую версию DLLDATA.C. Во многих случаях Вам может потребоваться одна DLL заместителя/заглушки, которая поддерживает несколько интерфейсов. Однако эти интерфейсы определены в разных файлах IDL. Если компилятор MIDL находит существующий файл DLLDATA.C, он добавляет новые интерфейсы, а не создает новый файл. Поэтому следует периодически проверять DLLDATA.C, чтобы убедиться, что там присутствуют только те интерфейсы, которые Вам нужны.

Циклический подсчет ссылок

Когда компонент TangramGdiWorld создает компонент TangramGdiVisual, первый передает последнему указатель на интерфейс ITangramGdiWorld. TangramGdiVisual использует этот интерфейс для преобразования в экранные координаты. К сожалению, при этом создается циклическая ссылка (рис. 13-4). TangramGdiWorld указывает на

TangramGdiVisual, который указывает обратно на TangramGdiWorld.

Циклические ссылки не очень подходят для подсчета ссылок, так как результатом циклических ссылок могут быть компоненты, которые никогда не освобождаются из памяти. Например, TangramGdiWorld создает TangramGdiVisual и получает интерфейс ITangramGdiVisual, для которого вызывает AddRef. Кроме того,

TangramGdiWorld передает указатель на свой интерфейс ITangramGdiWorld компоненту TangramGdiVisual,

который также вызывает для этого указателя AddRef. Теперь счетчик ссылок как компонента TangramGdiVisual, так и компонента TangramGdiWorld равен как миниму единице.

TangramGdiWorld

TangramGdiVisual

Список указателей на образы

ITangramGdiVisual

ITangramGdiWorld

m_pGdiWorld

Рис. 13-4 Циклические ссылки в программе Tangram

Далее TangramGdiWorld освобождает ITangramGdiVisual в своем деструкторе, который вызывается, когда счетчик ссылок станет равным 0. Но TangramGdiVisual имеет указатель на интерфейс ITangramGdiWorld компонента TangramGdiWorld, и не освобождает этот интерфейс, пока его счетчик ссылок не станет равным 0. Результатом является взаимный захват, или «клинч». TangramGdiWorld не освободит TangramGdiVisual до тех пор, пока TangramGdiVisual не освободит TangramGdiWorld. TangramGdiVisual не менее упрям и не желает освобождать указатель, пока это не сделает TangramGdiWorld. Это очень похоже на двух баранов на мосту, ни один из которых не желает посторониться и пропустить другого.

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

Не вызывайте AddRef

Первое решение — самое простое. Не увеличивайте счетчик ссылок одного из интерфейсов в ссылочном цикле. Так поступает компонент TangramGdiVisual. Он не вызывает AddRef для указателя на интерфейс

ITangramGdiWorld, полученного от TangramGdiWorld. TangramGdiVisual известно, что его время существования

225

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

Эта техника используется достаточно часто, чтобы получить собственное имя. Ссылка на интерфейс, счетчик ссылок которого не был увеличен, называется слабой ссылкой (weak reference). Наличие слабой ссылки не удерживает компонент в памяти. Сильная ссылка (string reference) — это ссылка, вызывающая увеличение счетчика ссылок. Такая ссылка удерживает компонент в памяти (рис. 13.5).

Хотя этот метод проще всего, не всегда его можно использовать. Компоненту, имеющему слабую ссылку на другой компонент, необходимо знать, когда такая ссылка становится недействительной. TangramGdiVisual это не волнует, так как его время существования вложено во время существования TangramGdiWorld. Но если времена существования компонентов не вложены, необходим другой способ определять, что ссылка стала недействительной.

TangramGdiWorld

TangramGdiVisual

Список указателей на образы

ITangramGdiVisual

ITangramGdiWorld

m_pGdiWorld

Рис. 13-5 TangramGdiVisual поддерживает слабую ссылку на TangramGdiWorld. Слабая ссылка изображена пунктирной линией.

Используйте явное удаление

Другой способ избежать захвата — предоставить одному из компонентов (или обоим) способ явно удалять другой компонент. Вместо того, чтобы ждать пока счетчик компонента станет равным 0, один из компонентов должен уметь приказать другому освободить все имеющиеся у того указатели на интерфейсы. Для этого нужно просто создать новый интерфейс с функцией, которая удаляет компонент (рис. 13-6).

TangramGdiWorld

 

TangramGdiVisual

Список указателей на образы

 

ITangramGdiVisual

Явный разрыв ссылки

Ликвидировать

ILifeTime

на этот компонент

 

 

 

ITangramGdiWorld

 

m_pGdiWorld

Рис. 13-6 Ссылочный цикл можно разорвать с помощью отдельной функции, которая заставляет компонент освободить имеющиеся у него указатели, прежде чем его собственный счетчик ссылок достигнет 0

Но здесь следует быть осторожным. В реальной программе компонент, который Вы явно удаляете, может быть все еще кому-то нужен. Поэтому хорошей идеей будет реализовать еще один счетчик ссылок для истинно сильных ссылок, помимо традиционного. Пример счетчика истинно сильных ссылок — IClassFactory::LockServer. Подробнее об этом см. гл. 7 и 10. Другими примерами истинно сильных ссылок являются

IOleContainer::LockContainer и IExternalConnection::AddConnection. Эти функции предоставляют клиентам способ явно управлять временем существования компонентов.

Используйте отдельный компонент

Другой способ разорвать ссылочный цикл — использовать отдельный объект или подкомпонент, на который указывает один из компонентов в цикле. Этот подкомпонент поддерживает слабую ссылку на свой внешний объект. Схематически это показано на рис. 13-7. Здесь TangramGdiWorld управляет временем жизни TangramGdiVisual, который управляет временем жизни подкомпонента TangramGdiWorld.

Подкомпоненты — самый гибкий способ избежать циклического подсчета ссылок. Вам не нужен доступ к исходному тексту или дополнительные сведения о компонентах; не нужно ничего, кроме поддерживаемого Вами интерфейса, чтобы реализовать подкомпоненты. Вставка компонента со слабой ссылкой может устранить ссылочный цикл.

TangramGdiWorld и TangramGdiVisual не используют подкомпонент для устранения ссылочного цикла. TangramGdiVisual сам выступает в качестве подкомпонента и поддерживает на TangramGdiWorld слабую ссылку. В то же время TangramGdiVisual и TangramModel используют подкомпонент во избежание циклических ссылок при реализации точек подключения. Подробнее мы поговорим об этом в следующем разделе.

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