- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
62
Правило для параметров типа вход-выход
Параметр типа вход-выход (in-out parameter) может одновременно быть и входным, и выходным. Функция использует переданное ей значение такого параметра, затем изменяет его и возвращает вызывающей программе.
Функция обязана вызвать Release для указателя на интерфейс, переданного ей как произвольный параметр, прежде чем записать на его место новый указатель. Перед возвратом в вызывающую программу функция также должна вызвать AddRef для нового значения параметра.
void ExchangeForChangedPtr(int i, IX** ppIX)
{
(**ppIX)->Fx(); |
// Делаем |
что-нибудь |
с |
входным параметром |
|
(**ppIX)->Release(); |
// Освобождаем входной |
параметр |
|||
*ppIX = g_Cache[i]; |
// Выбираем |
указатель из кэша |
|||
(**ppIX)->AddRef(); |
// Вызываем |
для него |
AddRef |
||
(**ppIX)->Fx(); |
// Делаем |
что-нибудь |
с |
выходным параметром |
}
Правило для локальных переменных
Локальные копии указателей на интерфейсы, конечно, существуют только во время выполнения функций и не требуют пар AddRef / Release. Это правило непосредственно вытекает из правила для входных параметров. В приведенном далее примере pIX2 гарантированно будет существовать только во время выполнения функции foo. Таким образом, его существование вложено во время жизни указателя pIX, переданного как входной параметр, — так что вызывать AddRef или Release для pIX2 не нужно.
void foo(IX* pIX)
{
IX* pIX2 = pIX; pIX2->Fx();
}
Правило для глобальных переменных
Если указатель на интерфейс сохраняется в глобальной переменной, то прежде чем передавать управление другой функции, необходимо вызвать AddRef. Поскольку переменная является глобальной, любая функция может вызвать Release и закончить жизнь этого указателя. Указатели на интерфейсы, сохраняемые в переменныхчленах, должны обрабатываться аналогично. Любая функция-член класса может изменить состояние такого указателя.
Правило для сомнительных случаев
Всякий раз, когда у Вас возникает сомнение, вставляйте пару AddRef / Release. Ее отсутствие редко дает значительный выигрыш в производительности или экономию памяти, зато легко может привести к созданию компонента, который никогда не удаляется из памяти. Кроме того, Вы можете потратить много времени на поиск ошибки, вызванной неправильным подсчетом ссылок. Как обычно, с помощью профилировщика можно определить, дает ли оптимизация существенный выигрыш. Помимо этого, если Вы все же решили применить оптимизацию, обязательно пометьте указатель, для которого не выполняется подсчет ссылок, соответствующим комментарием. Другому программисту, модифицирующему Ваш код, очень легко запутаться во временах жизни и нарушить правильность оптимизированного подсчета ссылок.
Пропущенный вызов Release труднее обнаружить, чем отсутствие вызова AddRef. Программисты на С++ легко могут забыть вызвать Release или, еще хуже, попытаться использовать delete вместо Release. В гл. 10 показано, как smart-указатели могут полностью инкапсулировать подсчет ссылок.
Амуниция пожарного, резюме
Методы IUnknown дают нам полный контроль над интерфейсами. Как мы видели в предыдущей главе, указатели на интерфейсы, поддерживаемые компонентом, можно получить через QueryInterface. В этой главе мы видели, как AddRef и Release управляют временами жизни полученных интерфейсов. AddRef сообщает компоненту, что мы собираемся использовать интерфейс. Release сообщает, что использование интерфейса закончено. Release также предоставляет компоненту некоторую способность управлять своим временем жизни. Клиент никогда не выгружает компонент напрямую; вместо этого Release сигнализирует, что клиент завершил работу с интерфейсом. Если ни один из интерфейсов никем не используется, компонент может удалить себя сам.
Хотя теперь мы имеем все возможности управления интерфейсами, у нас нет одной важной составляющей компонентной архитектуры — динамической компоновки. Компонент без динамической компоновки — это все
63
равно что пожарный без каски и плаща. В следующей главе мы добавим к нашим компонентам динамическую компоновку.