- •Оглавление
 - •От автора
 - •Введение
 - •Преимущества использования компонентов
 - •Адаптация приложений
 - •Библиотеки компонентов
 - •Распределенные компоненты
 - •Требования к компонентам
 - •Динамическая компоновка
 - •Инкапсуляция
 - •Заключительные замечания о компонентах
 - •Повторное использование архитектур приложений
 - •Соглашения о кодировании
 - •Законченный пример
 - •Взаимодействие в обход интерфейсов
 - •Детали реализации
 - •Теория интерфейсов, часть II
 - •Интерфейсы не изменяются
 - •Полиморфизм
 - •Что за интерфейсом
 - •Таблица виртуальных функций
 - •Указатели vtbl и данные экземпляра
 - •Множественные экземпляры
 - •Разные классы, одинаковые vtbl
 - •Запрос интерфейса
 - •IUnknown
 - •Получение указателя на IUnknown
 - •Знакомство с QueryInterface
 - •Использование QueryInterface
 - •Реализация QueryInterface
 - •А теперь все вместе
 - •Правила и соглашения QueryInterface
 - •Вы всегда получаете один и тот же IUnknown
 - •Вы можете получить интерфейс снова, если смогли получить его раньше
 - •Вы можете снова получить интерфейс, который у Вас уже есть
 - •Вы всегда можете вернуться туда, откуда начали
 - •Если Вы смогли попасть куда-то хоть откуда-нибудь, Вы можете попасть туда откуда угодно
 - •QueryInterface определяет компонент
 - •Вы не можете воспользоваться всеми знаниями сразу
 - •Работа с новыми версиями компонентов
 - •Когда нужно создавать новую версию
 - •Имена версий интерфейсов
 - •Неявные соглашения
 - •Управление временем жизни
 - •Подсчет ссылок
 - •Подсчет ссылок на отдельные интерфейсы
 - •Реализация AddRef и Release
 - •Когда подсчитывать ссылки
 - •Оптимизация подсчета ссылок
 - •Правила подсчета ссылок
 - •Амуниция пожарного, резюме
 - •Создание компонента
 - •Экспорт функции из DLL
 - •Загрузка DLL
 - •Разбиваем монолит
 - •Тексты программ
 - •Связки объектов
 - •Негибкое связывание, резюме
 - •HRESULT
 - •Поиск HRESULT
 - •Использование HRESULT
 - •Определение собственных кодов ошибки
 - •GUID
 - •Зачем нужен GUID?
 - •Объявление и определение GUID
 - •Сравнение GUID
 - •Передача GUID по ссылке
 - •Реестр Windows
 - •Организация Реестра
 - •Редактор Реестра
 - •Необходимый минимум
 - •Другие детали Реестра
 - •ProgID
 - •Саморегистрация
 - •Категории компонентов
 - •OleView
 - •Некоторые функции библиотеки COM
 - •Инициализация библиотеки COM
 - •Управление памятью
 - •Преобразование строк в GUID
 - •Резюме
 - •CoCreateInstance
 - •Прототип CoCreateInstance
 - •Использование CoCreateInstance
 - •Контекст класса
 - •Листинг кода клиента
 - •Но CoCreateInstance недостаточно гибка
 - •Фабрики класса
 - •Использование CoGetClassObject
 - •IClassFactory
 - •CoCreateInstance vs. CoGetClassObject
 - •Фабрики класса инкапсулируют создание компонентов
 - •Реализация фабрики класса
 - •Использование DllGetClassObject
 - •Общая картина
 - •Листинг кода компонента
 - •Последовательность выполнения
 - •Регистрация компонента
 - •Несколько компонентов в одной DLL
 - •Повторное применение реализации фабрики класса
 - •Выгрузка DLL
 - •Использование DllCanUnloadNow
 - •LockServer
 - •Резюме
 - •Включение и агрегирование
 - •Включение
 - •Агрегирование
 - •Сравнение включения и агрегирования
 - •Реализация включения
 - •Расширение интерфейсов
 - •Реализация агрегирования
 - •Магия QueryInterface
 - •Неверный IUnknown
 - •Интерфейсы IUnknown для агрегирования
 - •Создание внутреннего компонента
 - •Законченный пример
 - •Слепое агрегирование
 - •Агрегирование и включение в реальном мире
 - •Предоставление информации о внутреннем состоянии
 - •Моделирование виртуальных функций
 - •Резюме
 - •Упрощения на клиентской стороне
 - •Smart-указатели на интерфейсы
 - •Классы-оболочки C++
 - •Упрощения на серверной стороне
 - •Базовый класс CUnknown
 - •Базовый класс CFactory
 - •Использование CUnknown и CFactory
 - •Резюме
 - •Разные процессы
 - •Локальный вызов процедуры
 - •Маршалинг
 - •DLL заместителя/заглушки
 - •Введение в IDL/MIDL
 - •Примеры описаний интерфейсов на IDL
 - •Компилятор MIDL
 - •Реализация локального сервера
 - •Работа примера программы
 - •Нет точек входа
 - •Запуск фабрик класса
 - •Изменения в LockServer
 - •Удаленный сервер
 - •Что делает DCOMCNFG.EXE?
 - •Но как это работает?
 - •Другая информация DCOM
 - •Резюме
 - •Новый способ общения
 - •Старый способ общения
 - •Использование IDispatch
 - •Параметры Invoke
 - •Примеры
 - •Тип VARIANT
 - •Тип данных BSTR
 - •Тип данных SAFEARRAY
 - •Библиотеки типа
 - •Создание библиотеки типа
 - •Библиотеки типа в Реестре
 - •Реализация IDispatch
 - •Генерация исключений
 - •Маршалинг
 - •Что Вы хотите сделать сегодня?
 - •Потоковые модели COM
 - •Потоки Win32
 - •Подразделение
 - •Разделенные потоки
 - •Свободные потоки
 - •Маршалинг и синхронизация
 - •Реализация модели разделенных потоков
 - •Автоматический маршалинг
 - •Ручной маршалинг
 - •Настало время написать программу
 - •Пример с разделенным потоком
 - •Реализация модели свободных потоков
 - •Пример со свободным потоком
 - •Оптимизация маршалинга для свободных потоков
 - •Информация о потоковой модели в Реестре
 - •Резюме
 - •Программа Tangram
 - •Tangram в работе
 - •Детали и составные части
 - •Клиентский EXE-модуль
 - •Компонент TangramModel
 - •Компоненты TangramGdiVisual и TangramGLVisual
 - •Компоненты TangramGdiWorld и TangramGLWorld
 - •Что демонстрирует пример
 - •Файлы IDL
 - •Файл DLLDATA.C
 - •Циклический подсчет ссылок
 - •Не вызывайте AddRef
 - •Используйте явное удаление
 - •Используйте отдельный компонент
 - •События и точки подключения
 - •IEnumXXX
 
 4 глава
Подсчет ссылок
В детстве я хотел стать пожарным. Романтика приключений и опасности привлекала меня, как и большинство мальчиков. Однако по-настоящему мне хотелось быть пожарным не потому. Дело в том, что, во-первых, пожарные ездили, повиснув сзади на пожарной машине (из-за этого я еще хотел стать мусорщиком, но это другая история). Во-вторых, у пожарных была по-настоящему крутая экипировка: металлические каски, высокие ботинки, большие плащи и кислородные баллоны. Я тоже хотел носить все эти замечательные вещи. Пожарные, казалось, никогда не расставались с ними. Даже если они всего лишь снимали кошку с дерева, то все равно делали это в касках, плащах и высоких ботинках. Пожарного было видно издалека.
Класс С++ кое в чем напоминает пожарного. Заголовок сообщает всему миру, какие сервисы и функции предоставляет класс, — точно так же, как амуниция пожарных говорит об их профессии. Однако компоненты СОМ ведут себя совершенно иначе. Компонент СОМ гораздо более скрытен, чем пожарный или класс С++. Клиент не может посмотреть на компонент и сразу увидеть, что тот реализует пожарного. Вместо этого он должен выспрашивать: «Есть ли у Вас кислородный баллон? А топор? Носите ли Вы водонепроницаемую одежду?»
На самом деле клиенту неважно, имеет ли он дело с настоящим компонентом-пожарным. Ему важно, что у компонента за «амуниция». Например, если клиент задает вопрос «Носите ли Вы водонепроницаемую одежду?», то его удовлетворят ответы не только пожарного, но и байдарочника, аквалангиста и лесоруба в непромокаемом плаще. На вопрос «Есть ли у Вас кислородный баллон?» утвердительно ответит пожарный и аквалангист. На следующий вопрос про топор положительный ответ даст уже только пожарный (или аквалангист-лесоруб, если такой найдется).
Изоляция клиента от подлинной сущности компонента делает его менее восприимчивым к изменениям последнего. Однако, поскольку клиент знает компонент только через интерфейсы, он не может прямо управлять временем жизни компонента как такового. В этой главе мы рассмотрим косвенное управление — реализованное как явное управление временем жизни отдельных интерфейсов.
Управление временем жизни
Давайте разберем, почему клиент не должен управлять временем жизни компонента напрямую. Предположим, что наш клиент обращается к тому же компоненту-пожарному. В разных местах кода клиента могут быть разбросаны вызовы этого компонента через различные интерфейсы. Одна часть клиента может дышать через кислородный баллон при посредстве интерфейса IUseOxygen, а другая — крушить дом топором при помощи IUseAxe. Клиент может закончить пользоваться IUseAxe раньше, чем IUseOxygen. Однако Вы вряд ли захотите удалить компонент из памяти, когда с одним интерфейсом уже закончена, а с другим еще продолжается. Определить момент, когда компонент можно безопасно удалить, сложно еще и потому, что мы не знаем, указывают ли два указателя на интерфейсы одного и того же компонента. Единственный способ узнать это — запросить IUnknown через оба интерфейса и сравнить результаты. По мере того, как программа усложняется, становится все труднее определить, когда можно «отпускать» компонент. Проще всего загрузить его и не выгружать до завершения приложения. Но такое решение не слишком эффективно.
Итак, наша стратегия будет такова: вместо того, чтобы удалять компоненты напрямую, мы будем сообщать компоненту, что нам нужен интерфейс или что мы закончили с ним работать. Мы точно знаем, когда начинаем использовать интерфейс, и знаем (обычно), когда перестаем его использовать. Однако, как уже ясно, мы можем не знать, что закончили использовать компонент вообще. Поэтому имеет смысл ограничиться сообщением об окончании работы с данным интерфейсом — и пусть компонент сам отслеживает, когда мы перестаем пользоваться всеми интерфейсами.
Именно для реализации этой стратегии и предназначены еще две функции-члена IUnknown — AddRef и Release. В прошлой главе было дано определение интерфейса IUnknown, которое я любезно повторю:
interface IUnknown
{
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) = 0;
