- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
106
сервер в памяти до завершения работы. Клиент просто вызывает LockServer(TRUE) для блокирования сервера и
LockServer(FALSE) для деблокирования.
Реализовать LockServer теперь можно простым увеличением и уменьшением счетчика g_cComponents. Многие, и я в их числе, предпочитаю использовать отдельные счетчики для компонентов и блокировок. В этом случае DllCanUploadNow должна проверять на равенство нулю оба счетчика.
Резюме
В большинстве случаев для создания компонентов пользуются CoCreateInstance. Однако иногда эта функция не дает достаточной гибкости. Тогда можно воспользоваться CoGetClassObject, которая дает возможность прямо управлять фабрикой класса и интерфейсом, используемым для создания компонентов. Стандартный для создания компонентов интерфейс — IClassFactory, который используется и CoCreateInstance.
Независимо от того, использует ли клиент CoCreateInstance или CoGetClassObject, у компонента всегда имеется отдельная фабрика класса. Фабрика класса — это компонент, создающий компонент. Фабрика класса компонента обычно реализует IClassFactory. Однако некоторые компоненты предъявляют особые требования к процессу создания. Такие компоненты вместо (или в дополнение) к IClassFactory будут реализовывать и другие интерфейсы.
Если бы мы не могли собирать из деталей Lego конструкции, играть с ними было бы не слишком весело, даже при наличии Домашней Фабрики Пластиковых Деталей. Изготовление деталей — только часть процесса, который описывает эта книга. По-настоящему интересные вещи начинаются тогда, когда мы собираем детали вместе. Так и компоненты становятся по-настоящему интересными, только когда Вы соединяете их вместе, в новую цельную структуру. В следующей главе мы увидим, как построить новые компоненты из уже имеющихся.
8 глава
Повторная применимость компонентов: включение и агрегирование
Авторы статей в компьютерных журналах любят сравнивать СОМ с самыми разными вещами — например, миллионерами, горохом, С++ и компонентными архитектурами других фирм. Обычно в подобных статьях приводится какая-нибудь таблица вроде этой:
Таблица 8-1 Гипотетическая таблица из компьютерного журнала |
|
|||
Свойство |
Миллионеры |
Горох |
С++ |
СОМ |
|
|
|
|
|
Съедобны |
# |
# |
$ |
$ |
Поддерживают наследование |
# |
# |
# |
% |
Могут стать президентом |
# |
$ |
$ |
$ |
|
|
|
|
|
Вы можете не обратить внимания на то, что миллионеры съедобны. Однако, читая эти статьи, Вы не сможете упустить из виду, что СОМ не поддерживает наследования. Авторы, кажется, не обращают внимания на то, что СОМ поддерживает полиморфизм — самую важную концепцию объектно-ориентированного программирования, или что СОМ — небольшая, элегантная и быстро работающая модель, или что компоненты СОМ могут прозрачно работать по сети. Их не интересует и то, что СОМ не зависит от языка программирования; что компонентов СОМ написано больше, чем компонентов какого-либо другого типа. Их интересует только одно модное словцо — наследование!
Поддерживает ли СОМ наследование? И да, и нет. То, что подразумевается в журнальных статьях — это наследование реализации, которое имеет место, когда класс наследует свой код или реализацию от базового класса. Этот вид наследования СОМ не поддерживается. Однако СОМ поддерживает наследование интерфейса, т.е. наследование классом типа или интерфейса базового класса.
Многие необоснованно утверждают, что СОМ — плохая технология, так как она не поддерживает наследование реализации. Их аргументация напоминает мне о бурных «войнах» в Интернет — OS/2 против Windows, vi против Emacs, Java против Python и т.д. Я не участвую в таких спорах, ибо это пустая трата времени.
СОМ не поддерживает наследование реализации, потому что наследование реализации слишком тесно привязывает один объект к реализации другого. Если изменится реализация базового объекта, то производные объекты не будут работать, и их тоже нужно будет изменять. Для программы среднего размера на С++ это не очень трудно — у Вас есть доступ ко всем исходным текстам, и Вы можете изменить производные классы. Однако для программ большего размера на изменение всех зависимых классов может уйти недопустимо много времени. Хуже того, у Вас может даже не быть доступа к исходным текстам. Именно поэтому эксперты по созданию больших программ на С++ настоятельно рекомендуют строить приложения на фундаменте абстрактных базовых классов.
Не случайно базовые классы, т.е. род наследования интерфейсов в чистом виде, оказались также и способом реализации интерфейсов СОМ. Компоненты СОМ могут быть написаны кем угодно, где угодно и на каком угодно языке. Следовательно, необходимо очень тщательно защищать клиентов компонента от изменений. Наследование реализации не обеспечивает такой защиты.
Поэтому, чтобы гарантировать продолжение работы существующих приложений в случае изменений в компонентах, СОМ не поддерживает наследование реализации. Но из-за этого не теряются никакие функциональные возможности — ведь наследование реализации можно целиком смоделировать путем включения компонентов. Конечно, наследование реализации удобнее. Однако я полагаю, что большинство разработчиков предпочтут более устойчивую систему немного более удобной.