- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
131
return CLASS_E_CLASSNOTAVAILABLE;
} |
|
// Создать фабрику класса |
// В конструкторе нет AddRef |
CFactory* pFactory = new CFactory; |
|
if (pFactory == NULL) |
|
{ |
|
return E_OUTOFMEMORY; |
|
} |
|
// Получить запрошенный интерфейс
HRESULT hr = pFactory->QueryInterface(iid, ppv); pFactory->Release();
return hr;
}
//
// Регистрация сервера
//
STDAPI DllRegisterServer()
{
return RegisterServer(g_hModule, CLSID_Component2, g_szFriendlyName, g_szVerIndProgID, g_szProgID);
}
STDAPI DllUnregisterServer()
{
return UnregisterServer(CLSID_Component2, g_szVerIndProgID, g_szProgID);
}
///////////////////////////////////////////////////////////
//
// Информация о модуле DLL
//
BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
g_hModule = hModule;
}
return TRUE;
}
Листинг 8-4 Реализация внутреннего (агрегируемого) компонента
Слепое агрегирование
В предыдущем примере внешний компонент агрегирует только один из реализуемых внутренним компонентом интерфейсов. Единственный интерфейс внутреннего компонента, до которого может добраться клиент, это IY. Если бы внутренний компонент реализовывал IZ, клиент не смог бы получить указатель на IZ, так как внешний компонент возвращал бы E_NOINTERFACE.
А что, если внешний компонент хочет агрегировать несколько интерфейсов внутреннего? Внешний компонент легко модифицировать так, чтобы поддерживать еще один интерфейс внутреннего компонента:
else is ((iid == IID_IY) || (iid == IID_IZ))
{
return m_pUnknownInner->QueryInterface(iid, ppv);
}
Конечно, для изменения кода внешнего компонента нам потребуется доступ к этому коду и компилятор. Но как быть, если мы хотим, чтобы клиент имел доступ ко всем интерфейсам внутреннего компонента, включая то, что будут добавлены после того, как внешний компонент будет написан, скомпилирован и поставлен заказчику? Все
132
очень просто — удалите условие. Вместо проверки идентификатора интерфейса можно просто слепо передавать его внутреннему компоненту:
...
else if (iid == IID_IX)
{
*ppv = static_cast<IX*>(this);
}
else // Нет условия
{
return m_pUnknownInner->QueryInterface(iid, ppv);
}
...
Данная процедура называется слепым агрегированием (blind aggregation), так как внешний компонент слепо передает идентификаторы интерфейсов внутреннему. При слепом агрегировании внешний компонент не контролирует, какие из интерфейсов внутреннего компонента он предоставляет клиенту. В большинстве случаев лучше не прибегать к слепому агрегированию. Одна из причин этого в том, что внутренний компонент может поддерживать интерфейсы, не совместимые с интерфейсами, которые поддерживаются внешним компонентом. Например, внешний компонент для сохранения файлом может поддерживать интерфейс ISlowFile, тогда как внутренний для этой же цели поддерживает интерфейс IFastFile. Предположим, что клиент всегда запрашивает IFastFile прежде, чем запрашивать ISlowFile. Если внешний компонент слепо агрегирует внутренний, клиент получит указатель на IFastFile внутреннего компонента. Внутренний компонент ничего не знает о внешнем и поэтому не будет правильно сохранять информацию, связанную с последним. Проще всего избежать таких конфликтов, не используя слепое агрегирование.
Если полное воздержание кажется излишне сильным условием, для устранения подобных конфликтов можно использовать два менее радикальных способа. Во-первых, при реализации внешнего компонента не реализуйте интерфейсы, которые могут дублировать функциональность интерфейсов внутреннего компонента. Во-вторых, можно создавать внешний компонент и клиент либо внешний и внутренний компонент как взаимосвязанные пары.
Метаинтерфейсы
Интерфейсы, вызывающие конфликты внутреннего и внешнего компонента, — это обычно интерфейсы с перекрывающейся функциональностью. Если Вы можете гарантировать, что функциональность интерфейсов не перекрывается, вероятность конфликтов уменьшается. Добиться этого непросто, так как внутренний компонент может быть модернизирован для поддержки новых интерфейсов, которых внешний компонент не ожидает. Вероятность конфликта с существующими интерфейсами компонента минимальна в случае метаинтерфейсов (metainterfaces), или интерфейсов класса. Метаинтерфейсы манипулируют самим компонентом, а не абстракцией, которую он представляет.
Предположим, что у нас есть программа преобразования растровых изображений. Пользователь может модифицировать растровое изображение с помощью различных алгоритмов. Последние реализованы как внутренний компонент, который пользователь может добавлять в систему во время работы. Каждый внутренний компонент может считывать и сохранять растровые образы, а также преобразовывать их в соответствии с некоторым особым алгоритмом. У внешнего компонента есть интерфейс ISetColors, устанавливающий цвета, с которыми работает внутренний компонент. Внешний компонент также имеет интерфейс IToolInfo, который отображает значки разных алгоритмов преобразования на панели инструментов и создает внутренний компонент, когда пользователь выбирает соответствующий значок (рис. 8-7).
ISetColors — это пример обычного интерфейса, который расширяет абстракцию алгоритма преобразования. Компонент преобразования, вероятно, уже поддерживает интерфейс, например, IColors, для манипуляции набором цветов. Второй интерфейс, IToolInfo — пример метаинтерфейса. Все его операции служат для того, чтобы предоставить приложению способ работать с алгоритмами преобразования как с инструментами. Он не имеет никакого отношения к собственно преобразованию растровых образов. Этот интерфейс не расширяет абстракцию компонента преобразования растровых образов, но предоставляет клиенту информацию о самом этом компоненте.
Метаинтерфейсы наиболее полезны, когда они реализованы для набора разных компонентов в системе компонентов. Такие метаинтерфейсы предоставляют системе унифицированный способ работы с компонентами, повышая, таким образом, степень повторной применимости кода посредством полиморфизма. Добавление метаинтерфейсов к существующим компонентам чаще всего выполняют разработчики клиентов. Используя метаинтерфейсы, такой клиент может получать компоненты из самых разных источников и работать со всеми ними единообразно.