Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Материалы к лекциям COM.doc
Скачиваний:
4
Добавлен:
18.04.2019
Размер:
880.64 Кб
Скачать

8.3.3. Управление временем жизни объекта – подсчет ссылок

Для каждой реализации интерфейса ведется подсчет ссылок (Reference counting). Любой код, который хочет хранить указатель (ссылку) на интерфейс, должен увеличить счетчик ссылок этого интерфейса. Сделать это можно, вызвав у интерфейса метод AddRef (Каждый COM-интерфейс унаследован от IUnknown.). Пока у объекта счетчик ссылок не равен нулю, этот объект не будет уничтожен. Чтобы уменьшить счетчик, надо вызвать метод Release того же интерфейса. COM-объект жив, пока хотя бы у одного интерфейса счетчик ссылок больше нуля. Обычно сразу после создания объекта имеется только один указатель на его интерфейс со счетчиком ссылок, установленным в единицу. Так что, если в этот момент вызвать у его интерфейса метод Release, объект автоматически уничтожится.

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

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

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

Первая – это то, что если следить за ссылками вручную, довольно-таки просто ошибиться и забыть сделать AddRef или Release. Лишний AddRef приведет к тому, что объект не освободится до закрытия клиентского приложения. Лишний Release приведет к появлению неверных указателей в клиентском приложении, что, в свою очередь, приводит к его краху. Как уже говорилось, сетевым подсчетом ссылок занимается сам DCOM, и проблема со ссылками в клиентском приложении не может повлиять на работоспособность сервера. В некоторых языках проблема подсчета ссылок в клиентском приложении решена путем встраивания механизма автоматического подсчета ссылок в сам язык. Так сделано в VB и Delphi. А в C++ эта проблема может быть решена средствами самого языка. Для ее решения используются три свойства языка.

  1. Автоматический вызов деструктора при уничтожении переменных.

  2. Автоматическое уничтожение стековых переменных.

  3. Поддержка шаблонов. Используя две предыдущие возможности, можно создать классы-обертки для интерфейсов, которые автоматически вызывают Release при своем уничтожении. Но такую обертку пришлось бы писать для каждого интерфейса. Шаблоны позволяют написать одну реализацию такого класса-обертки, а компилятор сам создаст реализацию для каждого интерфейса.

В сочетании с перегружаемыми операторами классы-обертки практически полностью снимают проблему подсчета ссылок в C++. Лучшей реализацией такого класса-обертки является шаблон CComPtr<*> из библиотеки ATL. Переменная, созданная на базе такого класса-обертки, называется Smart Pointer, то есть «умный указатель». Объявив вместо простого указателя на интерфейс (например, IStak * pIStak) Smart Pointer, (CComPtr<IStak> * spIStak), можно не заботиться о правильности подсчета ссылок.

Вторая проблема – это то, что если два объекта делают ссылки друг на друга, то они попадают в deadlock, взаимно блокируя друг друга. Эта ситуация возникает нечасто, но если уж возникает, ее решение отнимает много нервов и времени. Есть два пути решения этой проблемы. Один – нарушить соглашения COM, и не делать увеличения счетчика ссылок одному из объектов. При этом время жизни объекта, которому не делается увеличение счетчика, должно быть заведомо больше, чем время, на которое требуется указатель на его интерфейс. Этот путь опасен и не всегда работает в сетевом окружении. Второй путь – создать промежуточный (третий) объект, который и подсунуть объекту, приводящему к взаимоблокировке. При этом на третий объект делается две ссылки, а на первый ссылок не делается вообще. В момент, когда первый объект уничтожается, он отпускает первый и третий. Далее уничтожается второй, и, в свою очередь, отпускает третий, что приводит к уничтожению третьего объекта. Второй способ избегания взаимоблокировок является более предпочтительным, но требует дополнительного кода. Очень плохо, что COM не взял на себя решение проблем с взаимоблокировкой.