- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
216
выполняется стандартный маршалинг интерфейса. Самое замечательное в CoCreateFreeThreadedMarshaler то, что Вам не нужно знать, кто является клиентом, — все чудеса происходят автоматически. Эта оптимизация работает и в сочетании с CoMarshalInterThreadInterfaceInStream и CoGetInterfaceAndReleaseStream. Это позволяет Вам выполнять явный маршалинг своих интерфейсов и предоставить заботу об оптимизации СОМ. Ниже приведен код, создающий маршалер свободных потоков. Также показана реализация QueryInterface, которая делегирует запросы на IMarshal маршалеру свободных потоков.
HRESULT CA::Init()
{
HRESULT hr = CUnknown::Init(); if (FAILED(hr))
{
return hr;
}
// Создать мьютекс для защиты счетчика m_hCountMutex = CreateMutex(0, FALSE, 0); if (m_hCountMutex == NULL)
{
return E_FAIL;
}
//Создать мьютекс для защиты индикатора стороны m_hHandMutex = CreateMutex(0, FALSE, 0);
if (m_hHandMutex == NULL)
{
return E_FAIL;
}
//Агрегировать маршален свободных потоков
hr = ::CoCreateFreeThreadedMarshaler( GetOuterUnknown(), &m_pIUnknownFreeThreadedMarshaler);
if (FAILED(hr))
{
return E_FAIL;
}
return S_OK;
}
HRESULT __stdcall CA::NondelegatingQueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IX)
{
return FinishQI(static_cast<IX*>(this), ppv);
}
else if (iid == IID_IMarshal)
{
return m_pIUnknownFreeThreadedMarshaler->QueryInterface(iid, ppv);
}
else
{
return CUnknown::NondelegatingQueryInterface(iid, ppv);
}
}
Замечание о терминологии
Как я говорил в начале главы, терминология СОМ, связанная с потоками, существенно различается в разных документах. Авторы Win32 SDK используют слово «apartment» («подразделение») не совсем так, как это делаю я. То, что я называю подразделением, они называют «многопоточное подразделение» для обозначения всей совокупности свободных потоков. В их терминах, в процессе может быть произвольное число «однопоточных подразделений», но только одно «многопоточное подразделение». Я надеюсь, что это разъяснение поможет Вам избежать путаницы при чтении документации Win32 SDK.
Информация о потоковой модели в Реестре
СОМ необходимо знать, какую потоковую модель поддерживают компоненты внутри процесса, чтобы обеспечить правильный маршалинг их интерфейсов и синхронизацию при вызовах между потоками. Чтобы
217
зарегистрировать потоковую модель своего компонента внутри процесса, добавьте в раздел компонента
InprocServer32 параметр с именем ThreadingModel. (ThreadingModel — это именованный параметр, а не подраздел!) Для ThreadingModel допускается одно из трех значений: Apartment, Free или Both.
Должно быть очевидно, что компоненты, которые можно использовать в разделенных потоках, устанавливают этот параметр в значение Apartment. Компоненты, которые можно использовать в свободных потоках, задают значение Free. Компоненты, которые могут использоваться как разделенными, так и свободными потоками, используют значение Both. Если компонент ничего не знает о потоках, то параметр не задан вообще. Если параметр не существует, то подразумевается, что компонент не поддерживает многопоточность. Все компоненты, обслуживаемые данным сервером внутри процесса, должны иметь одну и ту же потоковую модель.
Резюме
В одной главе мы не только научились реализовывать разделенные и свободные потоки, но также узнали, что такое подразделение. Подразделение — это концептуальный конгломерат, состоящий из потока и цикла выборки сообщений. Поток подразделения похож на типичный процесс Win32 в том смысле, что оба имеют один поток и цикл выборки сообщений. В одном процессе может быть любое число подразделений и свободных потоков.
Разделенные потоки должны инициализировать СОМ, иметь цикл выборки сообщений и выполнять маршалинг указателей на интерфейсы в другие потоки. Компонент, созданный в разделенном потоке, должен вызываться только создавшим его потоком. Аналогичное правило существует и для оконной процедуры. Серверы внутри процесса должны иметь «потокобезопасные» точки входа, но компоненты могут не быть «потокобезопасными», так как синхронизацию обеспечивает СОМ.
Свободные потоки должны инициализировать СОМ при помощи CoInitializeEx. Они не обязаны иметь цикл выборки сообщений, но по-прежнему обязаны выполнять маршалинг интерфейсов в разделенные потоки и в другие процессы. Им не нужно выполнять маршалинг интерфейсов в другие свободные потоки в том же самом процессе.
Встает вопрос, потоки какого типа следует использовать Вам? Код пользовательского интерфейса обязан использовать разделенные потоки. Это гарантирует, что сообщения будут обработаны и у пользователя не возникнет впечатления, что программа «зависла». Если Вы хотите выполнять в фоновом режиме только простейшие операции, следует использовать разделенные потоки. Их гораздо легче реализовывать, так как не нужно заботиться о «потокобезопасности» используемых в них компонентах.
Однако в любом случае для всех вызовов разделенного потока необходим маршалинг. Это может существенно снизить производительность. Поэтому, если Вам необходим интенсивный обмен информацией между разными потоками, либо соберите весь код в один поток, либо используйте свободные потоки. Вызовы между свободными потоками внутри одного процесса не требуют маршалинга и могут выполняться значительно быстрее, в зависимости от того, как реализована синхронизация внутри компонента.
Итак, какие же потоки нужно использовать в программе моделирования нашего вертолета? Я оставлю Вам этот вопрос в качестве домашнего задания.
13 глава
Сложим все вместе
Известная более двухсот лет китайская головоломка танграм состоит из семи фрагментов, из которых нужно складывать разные фигуры (рис. 13-1). Я люблю танграм за то, что из простых фрагментов можно сложить бесчисленное множество сложных форм.
Фрагменты танграма включают пять равнобедренных треугольников: два маленьких, один средний и два больших. Еще два кусочка — квадрат и параллелепипед. Все семь фрагментов показаны на рис. 13-1. Из них можно сложить разнообразные фигуры, от геометрических форм до фигур людей, животных, деревьев, машин и даже всех букв алфавита (см. примеры на рис. 13-2). Многие из таких фигур запоминаются и весьма выразительны. Например, слегка поворачивая квадратик, изображающий голову танграмного гребца в танграмной лодке, Вы видимо увеличиваете прилагаемое им усилие.
Рис. 13-1 Семь фрагментов танграма — простые геометрические фигуры
Рис. 13-2 Из фрагментов танграма можно создавать самые разные фигурки. Кролик, вертолет и кошка показаны на рисунке