- •Оглавление
- •От автора
- •Введение
- •Преимущества использования компонентов
- •Адаптация приложений
- •Библиотеки компонентов
- •Распределенные компоненты
- •Требования к компонентам
- •Динамическая компоновка
- •Инкапсуляция
- •Заключительные замечания о компонентах
- •Повторное использование архитектур приложений
- •Соглашения о кодировании
- •Законченный пример
- •Взаимодействие в обход интерфейсов
- •Детали реализации
- •Теория интерфейсов, часть 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
166
на которые имеется ссылка. Поэтому не используйте в качестве типа параметра void*. Если Вам нужно передать абстрактный указатель на интерфейс, используйте IUnknown*. Самый гибкий метод — передача клиентом IID, и именно так работает QueryInterface:
HRESULT GetIFace([in] const IID& iid, [out, iid_is(iid)] IUnknown** ppi);
Здесь атрибут iid_is используется для указания MIDL идентификатора интерфейса. Конечно, вместо этого можно было бы использовать:
HRESULT GetMyInterface([out] IMyInterface** pIMy);
Но что произойдет, если будет возвращен IMy2 или IMyNewAndVastlyImproved?
Компилятор MIDL
Теперь, когда у нас есть файл IDL, его можно пропустить через компилятор MIDL, который сгенерирует несколько файлов. Если описания наших интерфейсов находятся в файле FOO.IDL, то скомпилировать этот файл можно следующей командой:
midl foo.idl
В результате будут сгенерированы файлы, перечисленные в табл. 10-1.
Таблица 10-1 Файлы, гененрируемые компилятором MIDL
Имя файла |
Содержимое |
|
|
FOO.H |
Заголовочный файл (для С и С++), содержащий объявления всех интерфейсов, описанных |
|
в файле IDL. Имя заголовочного файла можно изменить с помощью параметра командной |
|
строки /header или /h. |
FOO_I.C |
Файл С, в котором определены все GUID, использованные в файле IDL. Имя файла можно |
|
изменить с помощью параметра командной строки /iid. |
FOO_P.C |
Файл С, реализующий код заместителей и заглушек для всех описанных в файле IDL |
|
интерфейсов. Имя файла можно изменять с помощью параметра командной строки /proxy. |
DLLDATA.C |
Файл С, реализующий DLL, которая содержит код заместителей и заглушек. Имя файла |
|
можно изменить с помощью параметра командной строки /dlldata. |
|
|
Если в файле IDL имеется ключевое слово library, то по приведенной выше команде будет сгенерирована библиотека типа. (Как Вы помните, более подробно библиотеки типа будут рассматриваться в восхитительной следующей главе этой книги.) На рис. 10-4 показаны файлы, генерируемые компилятором MIDL. Здесь также показано, как из этих файлов генерируется DLL заместителя, — процесс, который мы рассмотрим чуть ниже.
FOO.H
Эти файлы генерируются MIDL
|
|
FOO_P.C |
|
|
|
|
FOO_I.C |
|
|
FOO.IDL |
MIDL.EXE |
DLLDATA.C |
Компилятор C |
FOO.DLL |
|
и |
|||
|
|
|||
|
|
|
компоновщик |
|
|
|
make-файл |
|
|
|
|
FOO.DEF |
|
REGSVR32.EXE |
Эти файлы пишете Вы |
|
|
|
|
|
Файл |
|
|
|
|
|
определений |
|
|
|
|
для DLL |
|
|
Рис. 10-4 Получение и использование файлов, генерируемых компилятором MIDL
167
Сборка примера программы
Чтобы наш разговор был более предметным, давайте соберем пример программы для этой главы. Все необходимые файлы есть на прилагающемся к книге компакт-диске. С помощью make-файла примера можно построить две версии сервера компонента: SERVER.DLL и SERVER.EXE. Для того, чтобы построить обе версии сразу, используется команда
nmake –f makefile
MAKEFILE дважды вызывает файл MAKE-ONE для сборки двух разных версий сервера. Промежуточные файлы сервера внутри процесса будут помещены в подкаталог \INPROC. Промежуточные файлы сервера вне процесса будут помещены в подкаталог \OUTPROC.
В make-файлах этого примера для запуска MIDL используется следующая командная строка:
midl /h iface.h /iid guids.c /proxy proxy.c server.idl
Эта команда переименовывает файлы, генерируемые MIDL, чтобы мы могли использовать прежние имена. Вместо того, чтобы писать определения интерфейса и в IFACE.H, и в SERVER.IDL, мы создаем только SERVER.IDL, а компилятор MIDL по нему генерирует IFACE.H автоматически. Точно так же нам более нужны GUID в файле GUID.CPP. Теперь мы просто подключаем GUIDS.C.
Заголовочный файл, генерируемый MIDL, можно использовать в программах как на С, так и на С++. Единственный недостаток этих заголовочных файлов в том, что они практически нечитабельны. Вы поймете, что имеется в виду, если посмотрите на содержимое сгенерированного MIDL файла IFACE.H. Как видите, его нелегко расшифровать. Тем не менее, это гораздо лучше, чем вручную поддерживать одинаковые описания интерфейса в разных местах.
Сборка DLL заместителя
Чтобы получить DLL заместителя/заглушки, нужно откомпилировать и скомпоновать файлы C, сгенерированные MIDL. Компилятор MIDL генерирует для нас код на С, который реализует для наших интерфейсов заместители и заглушки. Однако мы по-прежнему должны сами скомпилировать эти файлы в DLL. Первый шаг — написать для DLL заглушку файла DEF. Это очень просто. Файл DEF, который я использую, приведен ниже.
LIBRARY |
Proxy.dll |
|
DESCRIPTION |
‘Proxy/Stub DLL’ |
|
EXPORTS |
|
|
|
DllGetClassObject |
@1 PRIVATE |
|
DllCanUnloadNow |
@2 PRIVATE |
|
GetProxyDllInfo |
@3 PRIVATE |
|
DllRegisterServer |
@4 PRIVATE |
|
DllUnregisterServer |
@5 PRIVATE |
Теперь осталось все это откомпилировать и скомпоновать. Как это сделать, показывает следующий фрагмент файла MAKE-ONE:
iface.h server.tlb proxy.c guids.c dlldata.c : server.idl midl /h iface.h /iid guids.c /proxy proxy.c server.idl
!IF “$(OUTPROC)” != “” dlldata.obj : dlldata.c
cl /c /DWIN32 /DREGISTER_PROXY_DLL dlldata.c |
|
proxy.obj : proxy.c |
|
cl /c /DWIN32 /DREGISTER_PROXY_DLL proxy.c |
|
PROXYSTUBOBJS = dlldata.obj |
\ |
proxy.obj |
\ |
guids.obj |
|
PROXYSTUBLIBS = kernel.lib |
\ |
rpcndr.lib |
\ |
rpcns4.lib |
\ |
rpcrt4.lib |
\ |
uuid.lib |
|
proxy.dll : $(PROXYSTUBOBJS) proxy.def |
|
link /dll /out:proxy.dll /def:proxy.def |
\ |
$(PROXYSTUBOBJS) $(PROXYSTUBLIBS) |
|
regsvr32 /s proxy.dll |
|