- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
43
}
}
// Удалить компонент delete pIUnknown;
return 0;
}
Листинг 3-1 Использование QueryInterface
Эта программа выдает на экран следующее:
Клиент: Получить указатель на IUnknown Клиент: Получить интерфейс IX QueryInterface: Вернуть указатель на IX Клиент: IX получен успешно
Fx
Клиент: Получить интерфейс IY QueryInterface: Вернуть указатель на IY Клиент: IY получен успешно
Fy
Клиент: Запросить неподдерживаемый интерфейс QueryInterface: Интерфейс не поддерживается Клиент: Не могу получить интерфейс IZ Клиент: Получить интерфейс IY через IX QueryInterface: Вернуть указатель на IY Клиент: IY получен успешно
Fy
Клиент: Получить интерфейс IUnknown через IY QueryInterface: Вернуть указатель на IUnknown
Совпадают ли указатели на IUnknown? Да, pIUnknownFromIY == pIUnknown
Клиент начинает с создания компонента при помощи CreateInstance. CreateInstance возвращает указатель на интерфейс IUnknown компонента. Клиент при помощи QueryInterface запрашивает через интерфейс IUnknown указатель на интерфейс IX компонента. Для проверки успешного окончания используется макрос SUCCEEDED. Если указатель на IX получен успешно, то клиент с его помощью вызывает функцию этого интерфейса Fx.
Затем клиент использует указатель на IUnknown, чтобы получить указатель на интерфейс IY. В случае успеха клиент пользуется этим указателем. Поскольку класс CA реализует как IX, так и IY, QueryInterface успешно обрабатывает запросы на эти интерфейсы. Однако CA не реализует интерфейс IZ. Поэтому — когда клиент запрашивает этот интерфейс, QueryInterface возвращается код ошибки E_NOINTERFACE. Макрос SUCCEEDED возвращает FALSE, и pIZ не используется (для доступа к функциям-членам IZ).
Теперь мы дошли до по-настоящему интересных вещей. Клиент запрашивает указатель на интерфейс IY через указатель на интерфейс IX, pIX. Поскольку компонент поддерживает IY, этот запрос будет успешным, и клиент сможет использовать возвращенный указатель на интерфейс IY так же, как он использовал первый указатель.
Наконец, клиент запрашивает интерфейс IUnknown через указатель на IY. Поскольку все интерфейсы СОМ наследуют IUnknown, этот запрос должен быть успешным. Однако самое интересное, что возвращенный указатель на IUnknown, pIUnknownFromIY, совпадает с первым указателем на IUnknown, pIUnknown. Как мы увидим далее, это одно из требований СОМ: QueryInterface должна возвращать один и тот же указатель на все запросы к IUnknown.
Пример показывает, что при помощи QueryInterface можно получить любой из интерфейсов CA через любой другой. Это одно из важных правил реализации QueryInterface. Давайте более подробно рассмотрим его и другие правила.
Правила и соглашения QueryInterface
В этом разделе представлены некоторые правила, которым должны следовать все реализации QueryInterface. Если их выполнять, клиент сможет узнать о компоненте достаточно, чтобы (надеяться) управлять им и использовать его в своих целях. Без этих правил поведение QueryInterface было бы неопределенным, и писать программы было бы невозможно.
!" Вы всегда получаете один и тот же IUnknown.
!" Вы можете получить интерфейс снова, если смогли получить его раньше. !" Вы можете снова получить интерфейс, который у Вас уже есть.
44
!" Вы всегда можете вернуться туда, откуда начали.
!" Если Вы смогли попасть куда-то хоть откуда-нибудь, Вы можете попасть туда откуда угодно. Теперь рассмотрим эти правила подробно.
Вы всегда получаете один и тот же IUnknown
У данного экземпляра компонента есть только один интерфейс IUnknown. Всегда, когда Вы запрашиваете у компонента IUnknown (не важно, через какой интерфейс), в ответ вы получите одно и то же значение указателя. Вы можете определить, указывают ли два интерфейса на один компонент, запросив у каждого из них IUnknown и сравнив результаты. Приведенная ниже функция SameComponents определяет, указывают ли pIX и pIY на интерфейсы одного компонента:
BOOL SameComponents(IX* pIX, IY* pIY)
{
IUnknown* pI1 = NULL;
IUnknown* pI2 = NULL;
// Получить указатель на IUnknown через pIX pIX->QueryInterface(IID_IUnknown, (void**)&pI1);
// Получить указатель на IUnknown через pIY pIY->QueryInterface(IID_IUnknown, (void**)&pI2);
// Сравнить полученные указатели return pI1 == pI2;
}
Это важное правило. Без него нельзя было бы определить, указывают ли два интерфейса на один и тот же компонент.
Вы можете получить интерфейс снова, если смогли получить его раньше
Если QueryInterface однажды успешно обработала запрос на некоторый интерфейс, то все последующие запросы для того же компонента будут успешными. Если же запрос был неудачным, то для этого интерфейса QueryInterface всегда будет возвращать ошибку. Это правило относится только к конкретному экземпляру компонента. Ко вновь созданному экземпляру оно неприменимо.
Представьте себе, что произошло бы, если бы набор поддерживаемых интерфейсов мог изменяться со временем. Писать код клиента было бы крайне сложно. Когда клиент должен запрашивать интерфейсы у компонента? Как часто это делать? Что произойдет, если клиент не сможет получить интерфейс, который только что использовал? Без фиксированного набора интерфейсов клиент не мог бы сколько-нибудь уверенно определить возможности компонента.
Вы можете снова получить интерфейс, который у Вас уже есть
Если у Вас есть интерфейс IX, то Вы можете запросить через него интерфейс IX и получите в ответ указатель на IX. Код выглядит так:
void f(IX* pIX)
{
IX* pIX2 = NULL;
// Запросить IX через IX
HRESULT hr = pIX->QueryInterface(IID_IX, (void**)&pIX2); assert(SUCCEEDED(hr)); // Запрос должен быть успешным
}
Это правило звучит несколько странно. Зачем Вам интерфейс, который у Вас уже есть? Вспомните, однако, что все интерфейсы полиморфны относительно IUnknown и многим функциям передается указатель на IUnknown. У этих функций должна быть возможность использовать любой указатель на IUnknown и получить по нему любой другой интерфейс. Это иллюстрирует приведенный ниже пример:
void f(IUnknown* pI)
{
HRESULT hr; IX* pIX = NULL;
// Запросить IX через pI
45
hr = pI->QueryInterface(IID_IX, (void**)&pIX);
// Что-нибудь содержательное
}
void main()
{
//Получаем откуда-то указатель на IX IX* pIX = GetIX();
//Передаем его в функцию
f(pIX);
}
Функция f сможет получить указатель на IX по переданному ей указателю, хотя последний и так указывает на IX.
Вы всегда можете вернуться туда, откуда начали
Если у Вас есть указатель на интерфейс IX и с его помощью Вы успешно получаете интерфейс IY, то можно получить «обратно» интерфейс IX через указатель на IY. Иными словами, независимо от того, какой интерфейс у Вас есть сейчас, можно снова получить интерфейс, с которого Вы начали. Это иллюстрирует следующий код:
void f(IX* pIX)
{
HRESULT hr;
IX* pIX2 = NULL; IY* pIY = NULL;
// Получить IY через IX
hr = pIX->QueryInterface(IID_IY, (void**)&pIY); if (SUCCEEDED(hr))
{
// Получить IX через IY
hr = pIY->QueryInterface(IID_IX, (void**)&pIX2); // QueryInterface должна отработать успешно
assert(SUCCEEDED(hr));
}
}
Если Вы смогли попасть куда-то хоть откуда-нибудь, Вы можете попасть туда откуда угодно
Если Вы можете получить у компонента некоторый интерфейс, то его можно получить с помощью любого из интерфейсов, поддерживаемых компонентом. Если можно получить интерфейс IY через IX, а IZ — через IY, то IZ можно получить и через IX. В программе это выглядит так:
void f(IX* pIX)
{
HRESULT hr;
IY* pIY = NULL;
// Запросить IY у IX
hr = pIX->QueryInterface(IID_IY, (void**)&pIY); if (SUCCEEDED(hr))
{
IZ* pIZ = NULL;
// Запросить IZ и IY
hr = pIY->QueryInterface(IID_IZ, (void**)&pIZ); if (SUCCEEDED(hr))
{
// Запросить IZ у IX
hr = pIX->QueryInterface(IID_IZ, (void**)&pIZ);
// Это должно работать assert(SUCCEEDED(hr));
}
}
}
Это правило делает QueryInterface пригодной для использования. Вообразите, что стало бы, если бы получение указателя на интерфейс зависело от того, через какой интерфейс Вы делаете запрос. Вы редактируете код своего