Текст
                    ТЕХНОЛОГИЯ
ОСНОВЫ И
ПРОГРАММИРОВАНИЕ
COM+
ПРАКТИЧЕСКОЕ
РУКОВОДСТВО
ПО WINDOWS 2000 DNA
• Изучение основ СОМ+
• Описание передовых
концепций и технологий
программирования СОМ+
• Рассмотрение Windows 2000,
COM, DCOM, СОМ+
• Использование Visual C++,
Visual Basic и SQL Server
Разработка Web-приложений
Роберт Дж. Оберг
Предисловие Эндрю Скоппа (UCI Corporation)
вильямс
MICROSOFT TECHNOLOGIES SERIES


ТЕХНОЛОГИЯ ОСНОВЫ И ПРОГРАММИРОВАНИЕ COM+
MICROSOFr TECHNOLOGIES SERIES Robert J. Oberg Understanding & Programming C0M+ A Practical Guide to Windows 2000 DNA Prentice Hall RTR, Upper Saddle River, Ml 07458 www.phptr.com
MICROSOFT* TECHNOLOGIES SERIES Роберт Дж. Оберг C0M+ ТЕХНОЛОГОМ основы и ПРОГРАММИРОВАНИЕ Практическое руководство по Windows 2000 DNA Издательский дом "Вильяме" Москва ♦ Санкт-Петербург ♦ Киев 2000
ББК 32.973.26-018.2Я75 026 УДК 681.3.07 Издательский дом "Вильяме" По общим вопросам обращайтесь в Издательский дом "Вильяме" по адресу: info@williamspublishing.com, http://www.williamspublishing.com Оберг, Роберт, Дж. 026 Технология СОМ+. Основы и программирование.: Пер. с англ.: Уч. пос. — М.: Издательский дом "Вильяме", 2000. — 480 с.: ил. — Парал. тит. англ. ISBN 5-8459-0084-0 (рус.) Эта книга представляет собой практическое руководство по изучению СОМ+ для построения трехуровневых приложений с использованием архитектуры Microsoft Windows DNA. Материал книги основан на многолетнем опыте автора, имеющего степень доктора наук, в области программирования и преподавания объектноюриентированных технологий и языков программирования Материал книги профессионально подобран и изложен. Читателю предоставляется все необходимое для изучения СОМ+. Множество конкретных примеров облегчают восриятие материала и позволяют глубже понять особенности технологии СОМ+. Книга в первую очередь рекомендована читателям, знакомым с основами объектно-ориентированных технологий и языков программирования Большое внимание в книге уделяется специализированным вопросам, таким как базы данных, безопасность и защита данных, работа в Internet и другим современным технологиям работы распределенных приложений. ББК32.973.26-018.2Я75 Все названия программных продуктов являются зарегистрированными торговыми марками соответствующих фирм. Никакая часть настоящего издания ни в каких целях не может быть воспроизведена в какой бы то ни было форме и какими бы то ни было средствами, будь то электронные или механические, включая фотокопирование и запись на магнитный носитель, если на это нет письменного разрешения издательства Prentice Hall, Inc., a Pearson Education Company. Authorized translation from the English language edition published by PRENTICE HALL, a Pearson Education Company, Copyright © 2000 All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from the Publisher. Russian language edition published by Williams Publishing House according to the Agreement with R&I Enterprises International, Copyright © 2000 ISBN 5-8459-0084-0 (рус.) ISBN 0-13-023114-2 (англ.) © Издательский дом "Вильяме", 2000 © Prentice Hall, Inc., a Pearson Education Company, 2000
Содержание Введение 26 Часть I Введение в СОМ+ и Windows DNA 29 Глава 1. Что такое СОМ+ 30 Основы объектов 31 Объекты 31 Объектно-ориентированные языки 32 Компоненты 33 Путь Microsoft к СОМ+ 34 Динамически компонуемые библиотеки (DLL) 34 Архитектура открытых систем Windows (WOSA) 35 Связывание и внедрение объектов (OLE) 36 OLE 2.0 37 Модель компонентных объектов (СОМ) 37 ActiveX 39 Microsoft Transaction Server (MTS) 39 Microsoft Message Queue (MSMQ) 40 COM+ 40 Модель компонентов СОМ+ 41 Сервисы СОМ+ 42 Транзакции 42 Безопасность 42 Согласованность 43 Очереди сообщений 43 Другие сервисы 43 Мощь СОМ: первый взгляд 43 Построение Web-броузера 44 Инсталляция примеров 44 Демонстрация Web-броузера 45 Программные компоненты 47 Что дальше 48
Глава 2. Трехуровневые приложения и Windows DNA 50 Эволюция распределенных систем 50 Одноуровневые системы 51 Локальные вычислительные сети ПК 52 Двухуровневые (клиент/сервер) системы 53 Серверы баз данных 53 Серверы приложений 55 Трехуровневые системы 57 Серверы приложений на среднем уровне 58 Общая структура Windows DNA 58 Общие сервисы 58 Сетевые сервисы 59 Безопасность 59 Активные каталоги 59 Кластеризация 60 "Склеивающие" технологии 60 СОМ 60 СОМ+ 61 Слои Windows DNA 61 Уровень представления 61 Богатые клиенты 61 Бедные клиенты 62 Сценарии и компоненты 63 Уровень бизнес-логики 63 Internet Information Server 63 Microsoft Transaction Server и СОМ+ 65 Microsoft Message Queue 67 Уровень доступа к данным 68 Резюме 69 Глава 3. Полигон для испытаний Windows DNA 70 Общая конфигурация 70 Путеводитель 71 Компьютер № 1 Windows 2000 72 Компьютер №2 Windows 2000 Domain Controller 72 Компьютер №3 72 График работ 72 Глава 3, "Полигон для испытаний Windows DNA" 73 Глава 10, "Введение в DCOM" 73 Глава 17, "Windows 2000 и безопасность СОМ+" 73 Глава 18, "SQL Server и ADO" 73 Глава 20, "Использование СОМ+ в Web-приложениях" 73 Глава 21, "Microsoft Message Queue" 73 Windows 2000 73 Требования к аппаратному обеспечению 74 Windows 2000 Professional 74
Знакомство с Windows 2000 Professional 74 Windows 2000 Server 76 Сетевые возможности 76 Domain Name System (DNS) 76 Active Directory 77 Установка Active Directory 78 Присоединение к домену 78 Управление пользователями 79 Средства разработки 80 Visual Basic 80 Visual C++ 81 Visual InterDev 82 Platform SDK 82 Краткий обзор СОМ+ 82 Размещение на удаленном компьютере 83 Резюме 85 Часть II Основы СОМ 87 Глава 4. Клиенты СОМ: концепции и программирование 89 Сервер банковского счета 89 Изучение структуры СОМ-сервера 90 Visual Basic Object Browser 90 Классы, методы и свойства 91 OLE/COM Object Viewer (OLE View) 91 IDL для сервера банковского счета 92 Определение библиотеки типа 94 Определение сокласса 95 Интерфейсы 95 Методы 95 Свойства 95 Заключение 95 Терминология и концепции СОМ 96 Интерфейсы 96 Интерфейс IUnknown 96 Классы 97 Объекты 97 Создание экземпляра объекта 97 CoCreatelnstance (C++) 98 New (Visual Basic) 98 CreateObject (Visual Basic) 99 Идентификаторы в COM 99
Глобально уникальный идентификатор (GUID) 99 Идентификатор программы (ProgID) 100 Имя класса 100 "Пользовательские" имена 100 Время жизни объектов 101 Время жизни объекта в Visual Basic 101 Согласование интерфейсов 102 Вызов Querylnterface из Visual C++ 102 Использование Querylnterface в Visual Basic 102 Сервер 103 Библиотека типов 103 Программная модель клиента СОМ 104 Программирование клиента СОМ 104 Клиент СОМ на Visual Basic 104 Консольная программа-клиент на Visual C++ 106 Программа-клиент на Visual C++ с использованием MFC 108 Дополнительные вопросы программирования СОМ-клиентов 108 Unicode 109 Преобразование с использованием Win32 109 Преобразование с использованием макросов 109 BSTR 110 _BSTR_ 110 Программирование библиотек СОМ 111 Системный реестр Windows и СОМ 111 Использование OLE/COM Object Viewer 112 CLSID 112 Bank.Account.l 113 TypeLib 113 Создание экземпляра объекта 113 Registry Editor 113 Саморегистрация сервера 115 Резюме 115 Глава 5. C++ и СОМ 116 Объекты, компоненты и СОМ 117 Компонентные объекты 117 Компонентное программное обеспечение 117 Component Object Model 118 C++ и COM 118 Классы и интерфейсы 119 Идентификация классов 119 Инкапсуляция 119 Создание объектов 120 Объекты класса 120 Время жизни объекта 120 Версии и согласование интерфейсов 120 Повторное использование 121 Распределенные объекты 121
Реализация классов СОМ с использованием C++ 121 Пример объекта Account 122 Интерфейсы СОМ 122 Бинарное представление интерфейсов 122 Представление интерфейсов в C++ 123 Пример виртуальных функций 123 Задача 124 Решение 125 Глобально уникальные идентификаторы 125 GUIDGEN 126 I Unknown и Query Interface 126 Счетчики ссылок 127 Фабрики классов 127 Реализация СОМ-объекта 127 Определение интерфейса 128 Определение интерфейса IAccount 128 Стандартные макросы 128 Соглашения об именах интерфейсов 129 Реализация интерфейса 129 Конкретный класс 129 Реализация Querylnterface 130 Реализация счетчика ссылок 130 Реализация методов IAccount 131 Функция создания объекта 131 Состояние СОМ и сообщение об ошибках 132 Использование объекта СОМ 133 Тестовые программы для СОМ-объектов 133 Программа для работы с объектом Account 133 Дополнительные интерфейсы 134 Резюме 135 Глава 6. СОМ-серверы контекста приложения 136 Концепции СОМ-сервера 136 Локальная/удаленная прозрачность 136 Фабрика классов 137 Начальная загрузка объекта 137 Объект класса 137 Идентификаторы класса и системный реестр 138 Структура компонента 138 Реестр 138 Редактор системного реестра 139 Файлы записей системного реестра 139 Важная информация реестра 139 Интерфейсы в системном реестре 141 Реализация СОМ-сервера контекста приложения с использованием C++ 141 Определение фабрики классов 142 Реализация фабрики классов 142 Экспортируемые функции DLL 144 Файл определения модуля 144
Предоставление фабрики классов СОМ 144 Механизм выгрузки DLL 145 Доступ клиента к фабрике классов 145 CoCreatelnstance 146 Контекст выполнения 146 CoFreeUnusedLibraries 146 Связывание с библиотеками СОМ 146 Работа с DLL 147 Реализация СОМ-сервера контекста приложения с использованием Visual Basic 148 Создание сервера 148 Создание нового проекта ActiveX DLL 148 Код для класса Account 149 Модуль класса для интерфейса IDisplay 149 Реализация IDisplay в классе Account 149 Построение DLL 150 Установка бинарной совместимости версий 150 Тестовая программа-клиент 150 Резюме 151 Глава 7. Active Template Library 152 Active Template Library 152 MFCnATL 152 MFC и ATL 153 Стереотипный код COM 153 Реализация IUnknown 153 Объявление класса 153 Реализация класса 154 Создание экземпляра СОМ-объекта на основе ATL 155 CComObjcct 155 Пример программы 155 Visual C++ и ATL 156 Поддержка COM Visual C++ 156 Демонстрационный СОМ-сервер с применением ATL 157 ATL COM AppWizard 157 ATL Object Wizard 157 Имена класса 159 Атрибуты 159 Построение сервера 160 Определение методов 160 IDL-файл 161 Определение реализуемого класса 162 Реализация методов 163 Тестовая программа-клиент 163 Прогулка по ATL-коду 163 Код, сгенерированный MIDL 163 CComModule и карта объекта 164 DllMain 164
DllCanUnloadNow и DllGetClassObject 164 Саморегистрация 165 REGSVR32 166 Реализация IClassFactory 166 Множественные интерфейсы и IDL 167 Добавление второго интерфейса в IDL 167 Библиотека типов 168 Coclass и Visual Basic 168 Код C++ для второго интерфейса 169 Классы-оболочки ATL 170 CComBSTR 170 Интеллектуальные указатели 171 Резюме 171 Глава 8. Поддержка СОМ в Visual C++ 172 Visual C++ и клиенты СОМ 172 Демонстрационная программа-клиент на Visual C++ 173 Стартовый проект 173 Использование библиотеки типов 173 Использование интеллектуальных указателей 173 Тестирование и обработка ошибок 174 Завершение программы-клиента 175 Пространства имен 175 Устранение неоднозначностей в Visual Basic 175 Классы поддержки СОМ в Visual C++ 176 _bstr_t 176 _com_error 177 Резюме 177 Глава 9. ЕХЕ-серверы 178 Интеграция приложений и OLE 178 Сообщения Windows и DDE 178 OLE 1.0 179 Связывание и внедрение объектов 180 OLE 2 0 180 Демонстрация OLE 180 Интеграция приложений и ЕХЕ-серверы 182 ЕХЕ-серверы и суррогаты 182 Интерфейсы для OLE-сервера 182 Структура ЕХЕ-сервера 183 Маршалинг 184 Локальный сервер Demo 184 Прокси 184 Регистрация фабрики классов 185 Значения REGCLS 186 Аннулирование фабрики классов 186 Улучшенный ЕХЕ-сервер 187
Сокрытие главного окна приложения 187 Выгрузка приложения 188 Создание ЕХЕ-сервера с помощью ATL 189 Демонстрационный ЕХЕ-сервер 189 Саморегистрация ЕХЕ-сервера 190 Прокси и заглушки 191 Пример пользовательского интерфейса 191 Файлы ЕХЕ-сервера 192 Резюме 193 Глава 10. Введение в DCOM 194 Работа существующего СОМ-объекта в удаленном режиме 194 Существующий СОМ-сервер 195 Демонстрация DCOM 195 Вопросы безопасности 196 Записи в системном реестре 198 Записи реестра для прокси/заглушек 198 Записи системного реестра для локального ЕХЕ-сервера 198 Записи системного реестра для DCOM 198 Программирование DCOM 199 Определение сервера клиентом 200 Реализация DCOM 202 Контекст выполнения 202 COSERVERINFO 202 CoCreate Instance Ex 202 MULTI_QI 203 Пример кода клиента 203 DCOM и системный реестр 204 Оптимизация сетевого трафика 205 Оптимизация программистом 205 IMultiQI 207 Оптимизация инфраструктурой DCOM 207 Безопасность 208 Архитектура DCOM 208 Запуск сервера по сети 208 Сетевые операции сервера 209 Пересылка данных между машинами 209 Сетевая архитектура DCOM 209 Вопросы многопоточности 209 Важность многопоточности для DCOM 210 Резюме 211 Глава 11. Автоматизация и программирование СОМ на Visual Basic 212 Автоматизация 213 Свойства и методы 213
Позднее связывание 213 IDispatch 213 Информация о типах 213 Двойные интерфейсы 214 VARIANT 214 Структура tagVARIANT 215 Автоматизация с использованием ATL и VBScript 216 Сервер автоматизации (с использованием ATL) 216 "Тонкий" клиент (с применением VBScript) 217 Автоматизация и VBScript 218 Еще немного об IDispatch 219 Контроллеры автоматизации на Visual C++ 219 Прямой вызов IDispatch 219 Использование драйвера CComDispatchDriver 220 Автоматизация и Visual Basic 221 Свойства 222 Свойства по умолчанию 222 Реализация свойств на Visual Basic 222 Свойства "только для чтения" и "только для записи" 223 IDL для интерфейса диспетчера 223 События 223 События в СОМ-серверах 224 Программа-клиент, обрабатывающая события 224 IDL для интерфейса события 225 Коллекции 227 Коллекции и объектная модель 227 Итераторы 227 IEnumXXX 227 Классы итераторов ATL 228 Реализация коллекций 228 Требования к коллекциям 228 Пример коллекции 228 Резюме 229 Глава 12. Обработка ошибок и отладка 230 Использование HRESULT 230 Коды областей 231 Коды ошибок и соглашения по именованию 231 Просмотр кодов ошибок 232 Вывод описания ошибки 232 Интерфейсы ошибок СОМ 234 IErrorlnfo 234 Получение информации об ошибке 234 Получение информации об ошибке 234 Реализация ISupportErrorlnfo с использованием ATL 235 Возвращение информации об ошибке с помощью ATL 235 Использование ATL Object Wizard 235 Установка объекта ошибки 236
Пример интерфейсов ошибок СОМ 236 Запуск программы 236 Код сервера, обеспечивающий информацию об ошибке 237 Код клиента, получающий информацию об ошибке 238 Использование ISupportErrorlnfo 238 Использование IErrorlnfo 238 Поддержка ошибок интеллектуальных указателей Visual C++ 239 Исключения автоматизации 240 EXCEPINFO 241 Поддержка исключения автоматизации в MFC 241 COleDispatchException 242 Использование COleDispatchException 243 Обработка ошибок СОМ в Visual Basic 243 Обработка ошибок по умолчанию 244 Использование оператора On Error 244 Трассировка и отладка 246 Поддержка трассировки ATL 246 Трассировка вызовов Querylnterface 246 Вывод трассировки 247 Трассировка в SDK 247 Останов выполнения программы 247 Точки останова в сервере контекста приложения 248 Точки останова в ЕХЕ-сервере 248 Использование функции DebugBreak 248 Компонент Logger 249 Работа из Visual C++ 250 Работа из Visual Basic 250 Резюме 251 Глава 13. Многопоточность в СОМ 252 Параллельное программирование 252 Пример условий гонки 253 Упорядочение доступа к разделяемым данным 253 Автоматическое упорядочение обращения к данным 254 Демонстрация очереди сообщений Windows 254 DelayDeposit 254 Тестовая программа 254 Создание условий гонки 255 Скрытое окно 255 Апартаменты и многопоточность в СОМ 256 Апартаменты 256 Однопоточные апартаменты 256 Многопоточный апартамент 257 ЕХЕ- и DLL-серверы 257 Модели потоков 257 Однопоточная модель 258 Апартаментная модель потоков 258
Модель свободных потоков 259 Модель uBoth" 259 Выбор модели потоков 260 Нейтральная модель потоков 260 Пересечение границ апартаментов 260 Маршалинг указателя на интерфейс 261 Реализация многопоточности в СОМ 261 Поддержка многопоточности в ATL 261 Пример DLL-сервера, шаг 1 262 Пример DLL-сервера, шаг 2 262 Использование указателя на интерфейс 263 Пример DLL-сервера, шаг 3 264 Пример DLL-сервера, шаг 4 265 Клиент: инициализация СОМ 266 Клиент: создание объекта 266 Сервер: классы C++ 267 Сервер, реализация взаимоисключений 267 Критические участки кода 268 Резюме 268 Часть III Windows DNA и СОМ+ 269 Глава 14. Основы архитектуры СОМ+ 270 Основания для выбора СОМ+ 271 Масштабируемость 271 Надежность 271 Сложность 272 Транзакции: классический пример 272 Декларативное программирование с использованием атрибутов 273 Апартаменты как модель декларативного программирования 273 Однопоточные апартаменты 273 Требования объявления транзакций 274 Каталог СОМ+ 274 Сервисы компонентов 274 Терминология СОМ+ 276 Терминология СОМ 276 Терминология СОМ+ 277 Приложение 277 Компонент 277 Установка компонентов 278 Типы приложений СОМ+ 278 Архитектура СОМ+ 278
Контекст 279 Объект контекста 280 Контекст вызова 280 Активизация 280 Течение свойств контекста 281 Пример банковского счета 281 Принудительная активизация в контексте вызова 282 Перехват 282 Перехватчики 283 Маршалинг указателей на интерфейсы 283 Активизация по необходимости 284 Ограничения масштабируемости 284 Бит "сделано" 285 Подключение к активизации и деактивизации объекта 285 Состояние компонентов СОМ+ 285 Пул объектов и их конструирование 285 Пул объектов 286 Конструирование объектов 286 Резюме 287 Глава 15. Урок СОМ+ 288 Компонент COM4- на Visual Basic 288 Путеводитель 289 №1 ^сконфигурированный компонент 289 Использование Logger 289 Тестирование компонента банковского счета 290 №2 Сконфигурированный компонент 290 Создание пустого приложения СОМ+ 290 Установка нового компонента 290 Использование технологии "перетащить и отпустить" 292 Инсталляция и регистрация 292 Просмотр компонентов в СОМ+ Explorer 293 Запуск приложения СОМ+ 293 №3 Экскурсия по атрибутам 294 Атрибуты приложения 294 Атрибуты компонента 295 Атрибуты интерфейса 295 Атрибуты метода 296 №4 Активизация и состояние 296 Программирование контекста в Visual Basic 297 Активизация и деактивизация в процессе работы 298 №5. Отмена активизации по необходимости 299 №6. Подключение к активизации и деактивизации на Visual Basic 299 №7. Автоматическая деактивация 300 Компонент COM4- на Visual С+4- 301 Создание программы-примера 302 Инсталляция и запуск программы 302
Интерфейсы IObjectControl и IObjectConstruct 302 Доступ к контексту объекта 303 Конструирование объекта 304 Настройка строки конструктора 305 Запуск программы 305 Административные объекты СОМ+ 306 Удаленное размещение приложений COM4- 307 Резюме 308 Глава 16. Параллельные вычисления в СОМ+ 309 Синхронизация и апартаменты 309 Многопоточное приложение банковского счета 309 Синхронизация посредством апартаментов 310 Нейтральные апартаменты 310 Синхронизация и активность 311 Активность 311 Атрибут синхронизации 311 Взаимоотношения атрибутов 312 Потоковая модель "напрокат" 313 Пример программы 313 Каждый поток в своем STA 313 Все потоки в МТА 313 Резюме 314 Глава 17. Windows 2000 и безопасность СОМ+ 315 Фундаментальные проблемы безопасности 316 Авторизация 316 Аутентификация 316 Урок системного администрирования в Windows 2000 317 Работа с учетными записями в Windows 2000 317 Добавление пользователей 317 Встроенные учетные записи 319 Группы 319 Рабочие группы, домены и активный каталог 320 Рабочие группы 320 Домены в NT 4 0 320 Windows 2000 и активный каталог 320 Безопасность NT 321 Модель безопасности NT 321 Идентификатор безопасности 322 Признаки доступа 322 Объекты NT 322 Дескрипторы безопасности 322 Записи управления доступом 323 Discretionary Access Control List 323 Демонстрация безопасности 323
Система безопасности СОМ 326 Авторизация 326 Пример авторизации 327 Права на запуск и доступ по умолчанию 327 Права на запуск и доступ для приложений 329 Аутентификация 329 Подлинность 330 Работа в качестве запускающего пользователя 330 Работа в качестве интерактивного пользователя 332 Работа в качестве конкретного пользователя 332 Подмена пользователя 332 Система безопасности СОМ+ 333 Electronic Commerce Game™ 333 Специализированная версия Electronic Commerce Game™ 334 Запуск заглушки 334 Экспорт прокси 335 Конфигурирование системы безопасности СОМ+ 336 Настройка безопасности на уровне приложения 336 Система безопасности, основанная на ролях 337 Настройка ролей 338 Настройка доступа на уровне компонента 338 Программная безопасность 339 Работа сервера в СОМ+ 341 Имперсонация 342 Клиент 343 Сервер 343 Резюме 344 Глава 18. SQL Server и ADO 345 Основы SQL Server 7.0 345 Анализатор запросов 346 Enterprise Manager 347 Управление базами данных с использованием SQL Server 7.0 348 Базы данных для Electronic Commerce Game™ 348 History 348 Game 348 Базы данных поставщиков 349 Создание базы данных 349 Создание таблицы 349 Вставка данных в таблицу 350 Создание и использование сценариев SQL 351 Создание сценариев 351 Использование сценариев 352 Настройка баз данных для Electronic Commerce Game™ 352 Создание баз данных 353 Тестирование баз данных 353 Унифицированный доступ к данным 353
ODBC 354 OLE DB 355 ActiveX Data Objects 355 Урок программирования баз данных 357 Создание источника данных ODBC 357 Административная программа для базы данных History 359 Программирование с использованием ADO 360 Объектная модель ADO 360 Connection 361 Recordset 361 Коллекция Errors 365 Использование OLE DB-провайдера SQL Server 367 Трехуровневое приложение COM+ 367 Создание сервера среднего уровня 368 Создание клиента уровня представления 368 Отключенные наборы записей 368 Использование СОМ+ для создания удаленного прокси 368 Удаленный запуск уровня данных 368 Electronic Commerce Game™ 369 Файловое имя источника данных 369 Игра 370 Резюме 370 Глава 19. Транзакции в СОМ+ 371 Принципы технологии транзакций 371 Транзакции 371 Распределенные транзакции 372 Модель DTP 372 Двухфазное принятие транзакции 374 Технология транзакций Microsoft 374 OLE Transactions 375 Координатор распределенных транзакций Microsoft 375 Взаимодействие с ХА 377 Автоматическая обработка транзакций с использованием СОМ+ 377 Транзакционные компоненты 377 Зависимые атрибуты 378 Объекты и границы транзакций 379 Согласованность и бит выполнения 379 Флаг транзакции 380 Жизненный цикл автоматической транзакции 380 Программирование транзакций в СОМ+ 381 Программа Player Administration 381 Компоненты среднего уровня 382 ecUtil.dbPlayer 383 ecUtil.dbHistory 383 ecUtil.bMove 384 Уровень данных 385 Уровень представления 385
Автоматическая деактивизация метода 386 Резюме 387 Глава 20. Использование СОМ+ в Web-приложениях 388 Классическая технология Web 388 Гипертекст и HTML 389 Унифицированные локаторы ресурсов 390 Web-броузеры 390 Формы HTML 391 Серверы Internet 392 HyperText Transfer Protocol (HTTP) 393 Заголовки HTTP 393 Ответ Web-сервера 394 Методы HTTP 394 Common Gateway Interface 394 Динамические Web-страницы 395 Еще немного об HTML-формах 396 Изучение программирования Internet 396 Internet Explorer 5 0 396 Internet Information Services 5 0 397 Размещение материалов 397 Просмотр каталога 399 Запуск сценариев CGI 400 Web-технологии Microsoft 401 Клиентские Web-технологии Microsoft 402 Сценарии 402 VBScript и JavaScript 404 Управляющие элементы ActiveX 404 Настройка безопасности в Internet Explorer 406 Загрузка управляющего элемента ActiveX 407 Серверные Web-технологии Microsoft 408 Internet Server API (ISAPI) 409 Active Server Pages (ASP) 411 ASP и COM+ 413 Объектная модель ASP 413 Запросы и ответы ASP 414 Использование СОМ+ в трехуровневом Web-приложении 415 Приложение — прейскурант СОМ+ 415 Использование объектов СОМ/СОМ+ в ASP 418 Web-версия приложения-прейскуранта 419 Дополнительные вопросы Web-программирования 422 Резюме 423 Глава 21. Microsoft Message Queue 424 Очереди сообщений и Microsoft Message Queue 425 Очереди сообщений 425
Microsoft Message Queue 425 Приложения MSMQ 426 Архитектура MSMQ 426 Хранение очереди сообщений 426 Указание очередей 426 MSMQ API 427 Объектная модель MSMQ 427 MSMQ и транзакции 427 Использование и программирование Microsoft Message Queue 427 Установка и тестирование MSMQ 428 Инсталляция MSMQ 428 Тестовая программа MSMQ API 428 Администрирование MSMQ 429 Демонстрационные программы 430 QueueCreate 430 QSendObj 431 QueueSend 432 QueueReceive 434 Компоненты, работающие с очередями 435 Архитектура QC 436 Использование QC 436 Требования к QC 437 Конфигурирование QC 437 Безопасность 437 Имя очереди 437 Запуск приложения, работающего с очередями 438 Программный пример 439 Конфигурирование компонента, работающего с очередями 440 Конфигурирование приложения как работающего с очередями 440 Конфигурирование интерфейса как работающего с очередями 441 Получение сертификата безопасности MSMQ 441 Добавление запроса в очередь 441 Запуск приложения, работающего с очередью 442 Использование административных объектов 443 Резюме 443 Глава 22. События СОМ+ 444 События и точки подключения в СОМ 444 Пример события 444 Сервер 445 Клиент 446 Архитектура точек подключения 446 Входящие и исходящие интерфейсы 447 Клиент—Объект—Сток 447 IConnectionPoint 448 IConnectionPointContainer 448
Архитектура точек подключения 448 Жестко связанные события 449 Свободно связанные события и модель издатель/подписчик в СОМ+ 449 Архитектура системы событий СОМ+ 450 EventClass 451 Подписка 451 Постоянная подписка 452 Временная подписка 452 Подписчики 452 Издатели 452 Фильтрация 452 Фильтрация издателей 453 Фильтрация параметров 453 Пример события СОМ+ 453 EventClass 454 Подписчик 454 Добавление фильтра 456 Издатель 457 Резюме 458 Глава 23. СОМ+ и масштабируемость 459 Технология кластеризации Microsoft 459 Microsoft Cluster Server 460 Windows Load Balancing Service 461 Component Load Balancing 462 COM+ Component Load Balancing 463 Балансировка загрузки 463 Алгоритм балансировки загрузки 464 Составляющие кластерные технологии 464 Настройка Component Load Balancing 465 Защита от сбоев в Component Load Balancing 466 Вопросы разработки CLB-компонентов 466 Производительность 466 Пул объектов 466 Использование пула объектов 467 IObjectControl 468 Требования к размещаемым в пуле объектам 468 Пул объектов и балансировка загрузки 469 Важность СОМ+ 469 Эффективность СОМ 469 Мощь СОМ+ 470 Повышение уровня абстракции 470 Важность качества 471 Резюме 471 Приложение 472 Предметный указатель 474
Посвящение Памяти Фреда Р. Оберга (Fred R. Obeig), 1910-1999 Предисловие СОМ+, вероятно, самая интересная из новых технологий Microsoft, и я не могу назвать никого другого, кроме Боба Оберга, кто столь квалифицированно смог бы описать ее. Боб сочетает в себе умелого инженера, отличного писателя и одаренного учителя. Он много лет готовит учебные материалы и курсы для UCI Corporation. Бобу всегда хватает энтузиазма и терпения, так необходимых при обучении студентов новым программным технологиям. В этой книге меня больше всего привлекает широта изложения, не ограничивающаяся только СОМ+, а охватывающая и такие вопросы, как технология COM, Windows DNA и ряд других. Первая часть книги посвящена СОМ и DCOM, и лучшего описания этих вопросов мне не приходилось встречать нигде. Впрочем, это меня не удивляет, так как я очень хорошо знаю Боба и то, сколько лет он обучает других технологиям COM, OLE, ActiveX и пр. Им для компании UCl Corporation разработано множество курсов по таким темам, как СОМ и OLE, COM и DCOM, программирование Internet с использованием ActiveX. Боб всегда с большим энтузиазмом осваивает новые технологии, и эта книга, посвященная СОМ+, — яркое тому свидетельство. СОМ+ довольно сложна для изучения, поскольку профессиональные приложения включают множество смежных технологий, таких как транзакции, базы данных, безопасность, программирование Web и т.п. В своей книге Боб ухитрился кратко, но с потрясающей полнотой осветить все эти вопросы, снабдив теоретическое изложение множеством примеров. Чтение этой книги доставило мне искреннее удовольствие, которое, надеюсь, получите и вы. Эндрю Скоппа (Andrew Scoppa), президент UCI
Введение Эта книга представляет собой практический курс по СОМ+ и ее применению для построения трехуровневых приложений, использующих архитектуру Microsoft Windows DNA. Книга представляет собой результат многолетнего опыта программирования и обучения СОМ. В этой книге внимание сосредоточено в основном на вопросах, важных для разработчика-практика, но, тем не менее, она представляет немалый интерес и для других— например, архитекторов или менеджеров. Изучать новую технологию увлекательно и интересно, но при этом достаточно сложно, поскольку она включает множество новых концепций и инструментов. Одна из ставившихся при написании книги задач — сделать ее по возможности самодостаточной, т.е. обеспечить даже неподготовленного читателя базовой информацией по всем необходимым вопросам. Так, данная книга включает большое количество сведений, касающихся СОМ, так что материал книги будет доступен даже в том случае, если прежде вы не имели дела с этой технологией. Книга состоит из трех частей. Первая часть представляет собой введение в СОМ+ и архитектуру Microsoft Windows DNA, которая является основой для построения мощных трехуровневых распределенных приложений, предоставляя ядро инфраструктуры этой архитектуры. Это введение рассчитано на читателя-новичка, а потому в нем описана история возникновения и развития СОМ+, рассказано, какое программное и аппаратное обеспечение следует иметь для полноценного чтения этой книги, т.е. чтения, сопровождающегося выполнением всех демонстрационных примеров. Вторая часть книги посвящена основам СОМ, представляющей собой фундамент, на котором построено здание СОМ. В третьей части речь пойдет о СОМ+. Там вы узнаете, каким образом создаются многоуровневые приложения в рамках модели Windows DNA. В качестве языков разработки в этой книге используются как C++, так и Visual Basic. Одной из сильных сторон СОМ является ее нейтральность по отношению к языку разработки. Различные части приложения могут быть разработаны с использованием разных языков программирования, наиболее подходящих для решения той или иной задачи. Такой подход полезен как при разработке программного обеспечения, так и при изучении. В части И, "Основы СОМ" мы используем как C++, так и Visual Basic, но несколько большее ударение делается на C++, так как этот язык способствует лучшему пониманию концепций СОМ, которые тщательно скрываются от программиста в Visual Basic. В части HI, "Windows DNA и СОМ+" мы в большей степени прибегаем к Visual Basic, поскольку здесь нас интересуют только сервисы СОМ+, и в методических целях мы старались избежать отвлекающих от обсуждаемой темы сложностей работы с C++. Например, Visual Basic используется для работы с базами данных с применением ADO. Компоненты для доступа к базам данных, разработанные на Visual Basic, могут легко быть вызваны бизнес-объектами, реализованными на C++ (в то же время, компоненты для работы с базами данных могут быть разработаны на C++ с использованием OLE DB, а бизнес-логика может быть реализованной на Visual Basic).
Почему мы не используем Java? На самом деле Java вполне подходит для реализации компонентов СОМ (по крайней мере, Microsoft Java, Visual J++). Способность Java поддерживать множественные интерфейсы делает этот язык программирования весьма пригодным для разработки СОМ. Но дело в том, что стандарт Java не предусматривает поддержку СОМ, а желание программистов использовать Java, по всей вероятности, объясняется способностью Java работать на разных платформах. Кроме того, будущее Visual J++ не совсем ясно. Использование языков C++ и Visual Basic оправданно и эффективно, но, конечно же, у вас могут быть собственные симпатии и антипатии. Не повлияет ли это на эффективность вашей работы с книгой? Автор пытался сделать книгу полезной как программистам на C++, так и программистам на Visual Basic. Если вы — программист на C++, простые примеры на Visual Basic не должны представлять для вас никакой трудности. Если вы — программист на Visual Basic, полностью прочтите часть I, "Введение в СОМ+ и Windows DNA", главу 4, "Клиенты СОМ: концепции и программирование", главу 6, "СОМ-серверы контекста приложения", а также все главы, начиная с 9, "ЕХЕ-серверы" и заканчивая 13, "Многопоточность в СОМ", обратив особое внимание на главу И, "Автоматизация и программирование СОМ на Visual Basic". При чтении делайте упор на концепции. Все это должно основательно подготовить вас к восприятию материала части III, "Windows DNA и СОМ+". Вторым важным вопросом, рассматриваемым в книге, является программирование баз данных, по части которого у вас также может быть (а может и не быть) богатый опыт. Если у вас имеется опыт работы с настольными базами данных типа Microsoft Access, но вы не знаете, как функционирует SQL Server, читайте главу 18, "SQL Server и ADO". Здесь вы найдете массу информации о работе с SQL Server — мощной базой данных, которая достаточно проста в обращении. Тут же вы узнаете об OLE DB и ADO. Базы данных используются в главах, посвященных транзакциям и разработке Web-приложений. Многие компании переходят к Web-приложениям, отличающимся простотой их размещения. Все, что требуется для работы клиента — это Web-броузер и подключение к Internet. Сервер при этом может использовать все возможности СОМ+. В главе 20, "Использование СОМ+ в Web-приложениях" вы познакомитесь с основами Web-программирования и о том, как используется СОМ+ на среднем уровне. В силу важности рассматриваемого вопроса это самая большая глава книги. В Windows NT 4.0 реализованы две важные технологии — Microsoft Message Queue (MSMQ) и Microsoft Transaction Server (MTS). Введение в Microsoft Message Queue — технологию, играющую очень важную роль в СОМ+, — приведено в главе 21, "Microsoft Message Queue" настоящей книги. Что касается MTS, то здесь история несколько иная — в Windows 2000 MTS не существует как отдельный элемент, а встроен в СОМ+ как его неотъемлемая часть. Таким образом, отдельно MTS в книге не рассматривается, а лишь только как краткий обзор в части I, "Введение в СОМ+ и Windows DNA". Очень важны при изучении новых технологий практические примеры, и в книге вы встретите немалое их количество. Многие из них — не просто демонстрации, а требуют большой работы по созданию полноценно действующего, пусть и предельно простого, приложения. Однако маленькие примеры не отражают всего, что должно быть учтено и включено в реальное трехуровневое приложение, а потому в книге представлено исследование игры Electronic Commerce Game™, которое, как я надеюсь, будет очень поучительно. Все примеры работы с СОМ+ к этой книге создавались с использованием бета- версии Windows 2000 (Windows 2000 Beta 3 и Release Candidate 1), а потому я вынужден сделать обычное предупреждение о возможном несоответствии бета-версии око н- чательному продукту. В частности, Microsoft удалила из окончательной версии 1п- Memory Database (IMDB), а сервис Component Load Balancing (CLB) стал самостоя-
тельным продуктом. Соответственно, я удалил из книги часть, касающуюся IMDB, но оставил информацию о CLB. Последние изменения и дополнения к этой книге, над которыми я продолжаю работу, вы можете найти, обратившись по адресу: www.Objectlnnovations.com. Примечания о примерах к этой книге Все примеры к этой книге вы можете найти на Web-узле www.williamspublishing.com. Примеры содержатся в самораспаковывающемся архиве install.exe, объемом немногим более одного мегабайта, который установит их на ваш жесткий диск. По умолчанию программные примеры устанавливаются на жесткий диск — в каталог с:\ComPlus, — но вы можете изменить эту установку. Однако, если вы используете другой каталог, вам следует внести соответствующие изменения в .reg-файлы в примерах к главе 6, "СОМ-серверы контекста приложения" и главе 9, "ЕХЕ-серверы". Я старался избежать привязок к конкретному каталогу, однако особо тщательно тесты в этом направлении мною не производились. Если вы — программист, то работа с примерами будет для вас не только поучительной, но и доставит немалое удовольствие. Но даже если вы — архитектор или менеджер, я очень рекомендую вам поработать с практическими примерами "из жизни СОМ+", чтобы лучше и полнее представлять эту технологию и эффективно использовать ее в своей работе. Благодарности Список благодарностей обычно бывает или очень краток, или очень велик. Список людей — преподавателей, коллег, авторов, студентов, друзей и близких, — бесчисленное множество раз помогавших мне в работе над этой книгой, слишком велик, чтобы разместить его в книге. Поэтому я приведу только краткий список тех, чья помощь на этапе планирования и написания этой книги была неоценимой. Я благодарен Эндрю Скоппа (Andrew Scoppa) из UCI Corporation, который предложил написать эту книгу, и Майку Мигану (Mike Meehan) из Prentis Hall, который помогал в осуществлении данного проекта. Над историей развития и основами СОМ со мной работал Майкл Стифель (Michael Stiefel), критика которого на ранней стадии написания книги была хотя и неприятной для меня, но в высшей степени полезной для книги. Рон Ривз (Ron Reeves) сумел выкроить время в своем напряженном графике для того, чтобы прочесть эту книгу. Я благодарен ему за полезные предложения. Мой отец поддерживал меня при написании этой книги своим опытом автора. Моя жена Марианна (Marianne) помогала не только в работе над этой книгой, но и во всех моих делах. Сообщите нам ваше мнение Вы, читатель этой книги, и есть главный ее критик и комментатор. Мы ценим ваше мнение и хотим знать, что было сделано нами правильно, что можно было сделать лучше и что еще вы хотели бы увидеть изданным нами. Нам интересно услышать и любые другие замечания, которые вам хотелось бы высказать в наш адрес. Мы ждем ваших комментариев и надеемся на них. Вы можете прислать электронное письмо или просто посетить наш Web-узел, оставив свои замечания — одним словом, любым удобным для вас способом дайте нам знать, нравится или нет вам эта книга, а также выскажите свое мнение о том, как сделать наши книги более подходящими для вас. Посылая письмо или сообщение, не забудьте указать название книги и ее авторов, а также ваш факс или номер телефона. Мы внимательно ознакомимся с вашим мнением и обязательно учтем его при отборе и подготовке к изданию последующих книг. E-mail: info@williamspublishing. com WWW http://www.williamspublishing.com .
ЧАСТЬ I Введение в СОМ+ и Windows DNA 1 Что такое СОМ+ 30 2 Трехуровневые приложения и Windows DNA 50 3 Полигон для испытании Windows DNA 70 Первая часть этой книги представляет собой введение в СОМ+ и архитектуру Windows Distributed internet Application (Распределенные приложения Internet). Эта всеобъемлющая архитектура представляет собой проект Microsoft для построения мощных трехуровневых распределенных приложений. СОМ+ обеспечивает ядро инфраструктуры для всей архитектуры. Это введение позволит вам сориентироваться в предмете в целом. Вы познакомитесь с историей возникновения и развития СОМ+, эволюцией объектов и систем компонентов. Windows DNA рассматривается как принцип организации для понимания работы программного обеспечения фирмы Microsoft, которое позволяет создавать как традиционные распределенные системы, так и Web-приложения. Одна из глав поможет вам настроить полигон, который будет использоваться для работы с остальной частью книги. Возможно, имеет смысл начать работу именно с этой темы, для того чтобы заранее устранить все препятствия на пути изучения СОМ+ и Windows DNA.
Глава 1 Что такое СОМ+ Итак, мы начинаем наше путешествие по всеобъемлющей архитектуре программных компонентов Microsoft, именуемой СОМ+, которая представляет собой следующий шаг в эволюции модели компонентных объектов (Component Object Model — СОМ). Это путешествие не всегда будет легким, так как предмет изучения очень большой, а местами — достаточно сложный. Вам придется не только читать, но и, закатив рукава, поработать. Как сказал одному желающему быстро и без труда выучить геометрию Евклид: "Королевской дороги в Геометрию нет ". На нашем пути мы не будем срезать углы и искать более короткую дорогу. Для облегчения разработки приложений Microsoft создала немало специализированного инструментария, но ведь необходимо понимать, что и как вы делаете, а это невозможно без всестороннего знания как архитектуры СОМ+, так и специфичных методов программирования. Эта глава должна помочь вам сделать первый шаг, с которого, как известно, начинается дорога любой протяженности. Итак, первый вопрос: "Что такое СОМ+?" Мы увидим, что СОМ+ — всего лишь одна из частей большей структуры, называемой Windows DNA. Мы обсудим эту большую структуру в следующей главе, а затем перейдем к детальному изучению СОМ+. Но вначале надо рассмотреть предмет на более высоком уровне, и я попытаюсь помочь вам в этом, построив свое изложение в форме рассказа. Рассказ гораздо интереснее формального руководства и проще для понимания, чем технический трактат. Здесь я проведу небольшой экскурс в историю задач, которые приходилось решать программистам, а также расскажу о вкладе Microsoft в их решение. Последнее мне представляется особенно интересным, так как Microsoft является компанией, которая достигла успеха, во многом копируя других, но делая это с большим упорством и коммерческим талантом. Однако СОМ (а теперь и СОМ+) представляет собой нечто новое в этом подлунном мире, созданном Microsoft. Предполагаемое путешествие по миру СОМ в той же мере ваше, как и мое, а потому вы вольны в выборе собственного пути. Если я рассказываю об истории и этот материал представляется вам неинтересным, — можете пропустить его. Если какой-то материал вас заинтересовал, — вернитесь к нему еще раз. Не бойтесь возвращаться к уже пройденному материалу всякий раз, когда вы ощущаете в этом потребность. Если какой-то раздел кажется вам слишком сложным и непонятным, — возможно, стоит пропустить его и вернуться к нему позже, когда вам понадобится изложенный в нем материал. (Не беспокойтесь — мы начинаем с очень простых вещей.) Итак, приступим!
Основы объектов СОМ+ представляет собой наивысшую (на сегодняшний день) точку длинного пути, который прошла Microsoft при создании мощных технологий разработки приложений и систем. Мы увидим, что СОМ+ включает некоторые оригинальные разработки Microsoft, но в основе этой технологии лежат классические идеи программирования, начинающиеся с объектно-ориентированного программирования. Объекты Ключевое понятие, которое помогает нам решать задачи программирования (и, безусловно, придает смысл окружающему миру) — это абстракция. Решить проблему проще, если не рассматривать ненужные детали и сконцентрироваться на том, что нас интересует. Допустим, я планирую поездку из Бостона в Филадельфию. При этом имеется всего несколько существенных вопросов, на которые следует обратить особое внимание и подумать о них в первую очередь. Как я планирую добраться до Филадельфии — самолетом, поездом или автомобилем? Сколько времени займет у меня поездка на каждом из видов транспорта? Сколько времени на путешествие у меня есть? (Если это деловая командировка, то у меня нет лишнего времени и мне придется лететь самолетом.) Имеется огромное множество характеристик каждого вида транспорта, но для моей задачи первостепенной является скорость. Меня не интересует цвет транспортного средства, но может интересовать авиакомпания — в связи с тем, что я часто летаю, я могу накопить определенные скидки. Таким образом, в нашем маленьком примере у нас есть абстракция — транспортное средство (Vehicle). Каждое транспортное средство имеет атрибут, или характеристику, именуемую скоростью (Speed). Имеются различные транспортные средства, такие как самолет (Plane), поезд (Train) и автомобиль (Саг), иерархические взаимоотношения которых могут быть представлены диаграммой (рис. 1.1). Транспортное средство Самолет Поезд Автомобиль Рис. 1.1. Простая абстрактная иерархия Обычно мы говорим о такой абстракции как о классе, который определяет различные характеристики и способности к чему-либо. Один тип характеристик может представлять данные, такие как скорость того или иного транспортного средства либо количество двигателей у самолета. Второй аспект класса заключен в его поведении. Например, самолет может выполнять такие действия, как взлет или посадка. Действия класса известны также как его методы. Ключевое свойство класса состоит в том, что
его данные и методы сгруппированы в единую сущность, которая определяет некоторую частную абстракцию. Объект представляет собой экземпляр конкретного класса. Каждый объект имеет собственные уникальные значения данных. Группирование данных и методов облегчает инкапсуляция. Обычно данные скрыты; можно сказать, что они отделены от остальной системы стеной. Мы говорим, что данные закрыты {private). Методы для работы с данными являются открытыми (public), и могут быть вызваны из любого места программы. Инкапсуляция данных таким путем предлагает два вида защиты. Данные сами по себе защищены от повреждения другой частью программы, поскольку непосредственное обращение к данным невозможно. Таким образом, защита данных от повреждения сводится к корректной работе методов. Второй вид защиты заключается в том, что остальная часть программы защищена от изменения представления данных. Пока интерфейсы открытых методов остаются неизменными, программа будет продолжать корректно работать. Класс чтит контракты. К сожалению, примером последствий некапсулированности служит проблема 2000 года. Многие программы работают только с двумя цифрами, обозначающими год. Тем самым экономится некоторое пространство в памяти и на диске; программа при этом работает корректно — но только в пределах одного столетия. Когда мы пересекаем границу между столетиями, такой метод представления даты становится неработоспособным. Если к данным о дате обращаются многие части программы, то исправления для перехода к представлению года с помощью четырех цифр потребуется вносить в огромное количество кода. Объектно-ориентированные языки Каким образом пишутся программы, которые используют объекты? Так называемое "сокрытие данных" для достижения инкапсуляции может быть достаточно легко реализовано во многих языках программирования. Например, в С можно объявить данные как static и тем самым ограничить их область видимости файлом. Тогда в этом исходном файле реализуются те функции, которые должны иметь доступ к указанным данным. Функции могут вызываться из других функций (в том числе из других файлов) — эти функции являются открытыми (public), но данные, с которыми они работают, остаются закрытыми (private) в пределах файла, в котором они объявлены. Не так легко реализовать на С поведение в стиле классов, при котором можно создавать объекты как экземпляры класса. Это может быть сделано с использованием концепции непрозрачных дескрипторов, используемых для представления объекта данных. Для создания различных типов объектов разрабатываются специализированные функции CreateXXXX, которые возвращают дескриптор вновь созданного объекта. Внутренняя реализация поддерживает таблицу дескрипторов, связывающую дескриптор созданного объекта с реальными данными. Извне доступ к этим данным невозможен, можно только обратиться к указателю. Все функции, работающие с такого рода "объектами", принимают в качестве параметра дескриптор объекта. Такой тип архитектуры широко используется в интерфейсе Windows С API, включая такие расширенные системы, как ODBC. Первое использование классов в языке программирования относится к 1967 году, когда в Норвегии появился один из потомков языка Algol — язык программирования Simula. Бьерн Страуструп (Bjarne Stroustrup) — изобретатель C++ — использовал язык программирования Simula в своей диссертации для программы, моделирующей компьютерные системы. Он нашел, что язык Simula очень выразительный и позволяет работать с высоким уровнем абстракции. Однако, когда дело дошло до реальных запусков программы, выяснилось, что производительность Simula весьма низка, и выполнить работу вовремя не удастся. Поэтому программу пришлось переписать на С. Однако Страуструп
поступил достаточно нетривиально. Вместо кодирования вручную он написал транслирующую программу, которая получала на входе С-подобную программу с расширениями для использования классов (смоделированную после Simula) и переводила ее на чистый С. Таково происхождение языка "С с классами" (С with classes), который впоследствии стал C++, а программа-транслятор стала компилятором "cfront" фирмы AT&T, который транслировал программу C++ в чистый С (таким образом облегчалось создание компилятора C++ для различных типов машин и систем, так как компилятор С можно было найти везде). Позже появились компиляторы C++, переводящие программу непосредственно в машинный код той или иной вычислительной платформы. Выполняется ли трансляция исходного текста с использованием промежуточного кода на С или без него — конечным результатом всегда оказывается машинный код для той или иной платформы. Кроме того, предоставляя скомпилированный код, разработка с использованием C++ (вспомните Страуструпа и его диссертацию!) всегда стремится к высокой эффективности работы программного обеспечения. Другим важным объектно-ориентированным языком является Smalltalk. В отличие от C++, который произошел от С, Smalltalk (разработанный в исследовательском центре Xerox Pare), создавался как "чистый" объектно-ориентированный язык. В Smalltalk все, даже обычные числа, реализовано как объекты, а в состав языка входит большая библиотека стандартных классов. Кроме того, имелся весьма богатый набор инструментария, включающий программу просмотра классов, отладчики и другие не менее полезные инструменты, дающие в результате неплохую среду разработки программ. Производимый Smalltalk код не так эффективен, как код C++, и сам язык сильно отличается от других языков, используемых большинством программистов. Smalltalk остается очень специализированным языком, как и некоторые другие (еще более специализированные), например, такие как Objective С и Eiffel. Еще одним языком, популярность которого в последнее время быстро растет, является Java. Java, как и Smalltalk, изначально проектировался как сугубо объектно- ориентированный язык, но в отличие от последнего имеет более прагматичный подход к встроенным типам данных — реализует их непосредственно, не в виде объектов. Java компилирует исходный текст в переносимый промежуточный язык, называемый "байт-кодом" (bytecode), который выполняется на Виртуальной Машине Java (Java Virtual Machine — JVM), интерпретирующей байт-код для конкретной платформы. В результате скомпилированная программа Java без внесения каких-либо изменений способна работать на множестве различных компьютеров — свойство, очень полезное для приложений Internet. В состав Java входит большая и быстро растущая библиотека стандартных классов. Недостатком Java является ее низкая производительность. Visual Basic, вообще говоря, сложно считать объектно-ориентированным языком, но в действительности современные версии этого языка имеют "объектную ориентацию". Как мы увидим позже, объектная модель Visual Basic тесно связана с СОМ и хорошо работает в ее среде, как при использовании СОМ-объектов, так и при их создания (что такое СОМ-объект, мы узнаем немного позже). Изначально Visual Basic представлял собой интерпретируемый язык, но последние версии в состоянии давать скомпилированный код. Visual Basic исключительно легок и прост в использовании и кое в чем похож на языки четвертого поколения. Самый эффективный код среди всех объектно-ориентированных языков программирования дает C++. Компоненты Несколько лет назад в журнале BYTE была опубликована статья, озаглавленная "Объектно-ориентированное программирование умерло?". В ней указывалось, что во многом объекты не оправдали возложенных на них надежд. Говорилось о том, что
объекты облегчают повторное использование кода. На самом деле объекты более уместно сравнивать с электронными платами, собрать компьютер из которых гораздо проще, чем из отдельных деталей. Но, как правило, достичь повторного использования кода в той же степени, в которой платы облегчают сборку компьютеров, простым применением программных объектов не удается. Тем временем Microsoft создала язык программирования Visual Basic, который был призван упростить программирование приложений Windows. Главным в подходе Visual Basic к созданию графического интерфейса пользователя была вставка различных управляющих элементов (controls) в форму. К каждому такому объекту можно присоединить небольшие куски кода, которые обрабатывают связанные с элементами события. Microsoft пришлось признать, что встроенных управляющих элементов, предоставляемых Windows, не достаточно для удовлетворения всех нужд программистов на Visual Basic, и пойти на создание спецификации пользовательских управляющих элементов VBX (Visual Basic Extension). Элементы VBX вставлялись в среду разработки Visual Basic и вели себя в точности так же, как и встроенные элементы управления, выполняя при этом специальные функции, для которых они и создавались. Подобно обычным управляющим элементами, VBX имеют свойства, которые можно устанавливать, и события, которые могут быть обработаны кодом Visual Basic. Вскоре после этого независимыми разработчиками программного обеспечения б ы- ли созданы и проданы сотни, а затем и тысячи управляющих элементов VBX. Эти управляющие элементы VBX могут быть как простыми "украшениями" программ разного вида, так и содержать сложную функциональность — например, при использовании Windows Sockets. Любой управляющий элемент VBX может быть легко вставлен в программу Visual Basic. Статья в журнале BYTE описала управляющие элементы VBX как более успешную реализацию мечты о повторном использовании кода. Статья довела объектно- ориентированное сообщество до белого каления. Управляющие элементы VBX не являются объектно-ориентированными, в них нет даже концепции метода, не говоря уже о том, что они не поддерживают такие возможности, как наследование и полиморфизм, хотя и облегчают повторное использование кода (не говоря уж о коммерческом успехе проекта). Управляющие элементы VBX могут рассматриваться как пример (хотя и очень предварительный) программного компонента. Программный компонент может рассматриваться как часть бинарного кода, который может быть легко вставлен в различные приложения. Путь Microsoft к СОМ+ Управляющие элементы VBX, скорее, представляют собой весьма любопытных зверюшек на пути эволюции, чьим завершающим звеном на сегодня оказалась СОМ+. На самом деле они представляют собой тупиковую ветвь эволюции, которая, тем не менее, сыграла свою роль, придя из динамически компонуемых библиотек (DLL), представляющих собой одну из основ Windows. DLL представляют собой первые шаги на пути, ведущем непосредственно к СОМ+. Динамически компонуемые библиотеки (DLL) DLL представляет собой специальный вид программного обеспечения, которое может быть скомпоновано с приложением во время работы, что более гибко и эффективно, чем использование статически компонуемых библиотек. Статические библиотеки возвращают нас в ранние дни программирования, когда программы набивались на перфокартах, а код библиотеки представлял собой просто колоду перфокарт, встав-
ляемую в пачку программы для обеспечения необходимой ее функциональности. Языки программирования поставлялись со стандартными библиотеками подпрограмм, такими как, например, стандартная библиотека С. Статические библиотеки компонуются в выполняемый файл приложения. Таким образом, если каждое из трех приложений использует один и тот же код из статической библиотеки, каждый выполняемый файл будет содержать копию этого кода. При применении DLL в память загружается только одна копия этого кода, и нуждающиеся в нем приложения могут совместно использовать этот код. Другое свойство DLL — компоновка в процессе выполнения программы. Это означает, что новая версия DLL может распространяться без перестройки всего приложения, использующего эту DLL. При сохранении строгой совместимости приложение может стать более производительным, если новая версия DLL обладает повышенной эффективностью работы. К сожалению, если новая версия DLL не вполне совместима, то может оказаться, что установка нового приложения с новой DLL приведет к неработоспособности приложений, использовавших старую версию DLL. Возврат же старой версии DLL приведет к неработоспособности нового приложения. Архитектура открытых систем Windows (WOSA) Динамически компонуемые библиотеки представляют собой первую попытку Microsoft реализовать компоненты на платформе Windows. Они обладают рядом преимуществ перед традиционными стандартными статическими библиотеками и предлагают механизм повторного использования кода. Однако и при их использовании не обойтись без проблем, одна из которых — проблема несовместимости версий. Другое ограничение применения DLL заключается в том, что они не поддерживают полиморфизма. Полиморфизм представляет собой еще одну возможность объектно- ориентированного программирования. Применение полиморфизма позволяет клиенту вызывать один и тот же метод из различных объектов и при этом получать разное поведение для каждого типа объектов. Рассмотрим, например, метод Show. Текстовый объект может вывести текст с помощью стандартного шрифта. Объект векторной графики может создать графическое представление с помощью соответствующих графических операций. Графический растровый объект может представить свое изображение, назначив цвета отдельным пикселям, а видеообъект — запустить воспроизведение видеоклипа. В качестве примера случая, когда полиморфизм крайне желателен, можно рассмотреть доступ к данным в конечном приложении. Программа электронных таблиц типа Excel должна быть способна считывать данные из базы данных и корректно заполнять ячейки таблицы. Конечному приложению заранее не известен тип источника данных. Один пользователь может работать с базами данных Access, другой — с базами данных Oracle. Вместо того чтобы создать различные версии Excel для работы с различными базами данных, Microsoft пошла другим путем, создав промежуточный программный слой, известный как Open Database Connectivity (ODBC), который определяет общий программный интерфейс приложений (API) баз данных. ODBC обеспечивает стандартный конечный интерфейс для приложений. Интерфейс уровня вызовов использует SQL — синтаксис, основанный на спецификациях Х/Open и SQL Access Group (SAG) SQL CAE (1992 год), подмножестве стандарта SQL-92. Реализация взаимодействия с базами данных обеспечивается инсталлируемыми драйверами (представляющих собой динамически компонуемые библиотеки). На рис. 1.2 изображена базовая архитектура ODBC.
Приложение Менеджер драйверов Драйвер Драйвер Интерфейс ODBC Источник данных Источник данных Рис. 1.2. Архитектура Open Database Connectivity (ODBC) Используя ODBC, приложение может полиморфно обмениваться информацией с разными базами данных. С помощью административной процедуры (аплета ODBC Control Panel) определенный драйвер (например, Access или Oracle) ассоциируется с источником данных, и во время работы Driver Manager загружает соответствующую DLL драйвера. Такой подход представляет собой классическое промежуточное программное обе с- печение (middleware). ODBC располагается между базой данных и приложением. Связывание и внедрение объектов (OLE) Технология OLE 1.0 появилась в июне 1991 года. Это была первая попытка Microsoft обеспечить объектно-ориентированный механизм интеграции приложений. Microsoft ввела концепцию составного документа, который может содержать объекты из других приложений. OLE стала результатом работы программистов, трудившихся над созданием PowerPoint, которые хотели иметь возможность внедрения элементов Microsoft Graph в свои документы и преодолеть некоторые ограничения ранее испол ь- зовавшихся технологий. Используя буфер обмена (Clipboard), пользователи могли вставлять статические снимки данных из других приложений в свои документы. Процесс редактирования таких данных был очень тяжелым, так как требовалось отредактировать исходные данные и вставить их в документ заново. Не имелось никакого способа заставить буфер обмена поддерживать связи (link), для того чтобы изменение исходных данных автоматически находило свое отражение в составном документе. Другим механизмом интеграции служил динамический обмен данными (Dynamic Data Exchange — DDE), который мог использоваться для поддержки связей с исходными данными, но для представления данных в подходящем формате требовал наличия соответствующего кода у контейнера.
OLE 1.0 позволило пользователю внедрять объект в контейнер. Объект содержит статический рисунок, а также исходные данные, необходимые для редактирования объекта с помощью соответствующего приложения. Для редактирования объекта, как правило, используется двойной щелчок мышью; при этом вызывается оригинальное приложение в отдельном окне. По окончании работы вы можете сохранить данные — при этом будут обновлены данные в документе-контейнере. Объект также может храниться как отдельный файл; в документе-контейнере находится только ссылка на этот файл. При этом гарантируется, что документ-контейнер всегда содержит последнюю копию связанного объекта — он автоматически получает все обновления и изменения при редактировании данных в оригинальном приложении. Технология OLE 1.0, реализованная поверх DDE, имеет ряд ограничений. Динамический обмен данными по сути своей асинхронен — при вызове функции возврат из нее происходит немедленно, и вы должны ожидать результата в цикле сообщений, постоянно проверяя состояние флага статуса. Кроме того, соединения DDE достаточно хрупкие. OLE 1.0 основывается на разделяемой глобальной памяти для передачи данных между приложениями. Большие блоки данных должны копироваться в память перед передачей (и при этом могут тут же быть сброшены обратно на диск системой подкачки). Связи OLE 1.0 легко разбивались при перемещении файлов. Редактирование внедренных и связанных объектов представляет собой достаточно навязчивый сервис, поскольку при этом запускается другое приложение в отдельном окне. OLE 2.0 OLE 2.0 появилась в мае 1993 года. Основная цель, которая ставилась перед разработчиками OLE 2.0, — улучшение OLE 1.0. В процессе создания OLE 2.0 разработчики перешагнули рамки первоначальной задачи поддержки составных документов. В OLE 2.0 (по сравнению с OLE 1.0) были внесены значительные изменения и улучшения. Так, DDE был заменен более мощным протоколом облегченного удаленного в ы- зова процедур (Lightweight remote procedure call — LRPC). Альтернативой разделяемой памяти в OLE 2.0, обеспечивающей эффективную передачу данных, стал механизм унифицированной передачи данных (uniform data transfer — UDT). Новый механизм имен улучшил отслеживание связей. Визуальное редактирование, или активизация на месте, позволяет редактировать внедренный объект в контексте основного приложения-контейнера. Основное в технологии OLE 2.0 состоит не столько в улучшениях (по сравнению с OLE 1.0), сколько в инфраструктуре, созданной для ее поддержки. OLE 2.0 основана на точно определенной модели — COM (Component Object Model — Модель компонентных объектов). Эта модель по сути своей расширяема, а потому добавление нового компонентного сервиса не требует никаких фундаментальных изменений. След о- вательно, номер версии может быть опущен, и описываемая технология стала называться просто OLE. Модель компонентных объектов (СОМ) Модель компонентных объектов, созданная в качестве базиса для OLE 2.0, может рассматриваться как третье поколение архитектуры компонентов Microsoft. Первым поколением компонентов были динамически компонуемые библиотеки, обеспечивающие интерфейс вызовами функций С. Второе поколение, WOSA, предоставляет большое количество важных сервисов, например, таких как ODBC или Windows Sockets. Интерфейс взаимодействия при этом все еще представляет собой вызов функций С. Третье поколение вводит "компонентные объекты". Как мы видели, объекты инкапсулируют данные и поведение в единые сущности. Без объектов необходимо поддерживать множество переменных и передавать их отдельным обособленным функц и-
ям. Группировкой связанных данных и функций в один объект создается абстракция, которая упрощает программирование и позволяет расширить модель программирования для включения новых типов данных. СОМ расширяет эти возможности структурирования объектов. Объекты могут иметь множественные интерфейсы, каждый из которых поддерживает свои свойства и возможности через связанные группы функций. Объект может поддерживать множественные интерфейсы и, если клиент имеет указатель на один интерфейс, получать указатель на другой интерфейс с помощью функции Querylnterface. Эта простая концепция множественных интерфейсов и механизма запросов чрезвычайно полезна, поскольку она поддерживает эволюцию программных компонентов через введение дополнительных интерфейсов без нарушения работоспособности существующих клиентов. Например, сервер составного документа OLE может как поддерживать, так и не поддерживать активизацию "на месте". Исходно сервер OLE этого не делает, так как такая активизация не является свойством OLE 1.0. Чтобы сервер поддерживал такую активизацию, он должен обеспечивать дополнительные интерфейсы, с которыми может работать новая версия серверного приложения. Это не приведет к нарушению работы старых клиентских приложений, так как старые клиенты не запрашивают новые интерфейсы. Новые клиенты смогут работать с новым сервером, поскольку их запросы к соответствующим интерфейсам будут удовлетворены, и сервер будет активизироваться в контексте приложения-контейнера. Новые клиенты смогут работать и со старыми серверами, так как при запросе новых интерфейсов они не смогут их найти и запустят сервер как отдельное приложение в собственном окне. Другая очень важная характеристика СОМ заключается в том, что она представляет собой бинарный стандарт, позволяющий взаимодействовать программам, созданным с применением различных языков программирования. Таким образом, различные компоненты СОМ могут быть созданы на С, C++, Visual Basic и Java (версия Microsoft — Visual J++) различными производителями программного обеспечения. Каждый их этих компонентов может использоваться любым языком программирования, поддерживающим СОМ. Этот бинарный стандарт представляет собой главное усовершенствование по сравнению с объектно-ориентированными языками программирования, которые определяют объекты на уровне исходных текстов, ограничивая потенциал их широкомасштабного взаимодействия. Вместе с Windows NT 4.0 Microsoft выпустила в свет DCOM, или распределенную (distributed) COM, расширяющую СОМ для использования в сети. Изначально СОМ использовала механизм удаленного вызова процедур (RPC) для выхода за рамки процесса. Применение DCOM расширяет возможности RPC. Базовая архитектура остается при этом неизменной. Независимо от того, находится ли этот объект в том же процессе, в другом процессе или на другой машине, клиентская программа обращается к нему одинаково. Эта особенность СОМ известна как прозрачность размещения {location transparency). Основное свойство СОМ заключается в том, что она не является промежуточным программным обеспечением (middleware). COM не располагается между клиентом и сервером, как ORB (Object Request Broker) в CORBA или ODBC в WOSA. Время выполнения СОМ включается в установку соединения между клиентом и сервером. В случае, если компонент СОМ запускается в том же процессе, что и клиент, накладные расходы отсутствуют — они в точности те же, что и при вызове виртуальной функции в C++. Если компонент запускается вне процесса или на другой машине, накладные расходы — это только удаленный вызов процедуры, пересекающий границы процессов или сети.
ActiveX Маркетологи Microsoft "намутили воду" (и теперь неплохо ловят в ней рыбку), придумав термин "ActiveX" для обозначения технологии Internet, разработанной Microsoft для создания "активных" приложений Internet. Классическая Internet представляет собой в основном огромное хранилище информации. С помощью программы-броузера вы можете путешествовать по Web-узлам всего мира и загружать на свой компьютер HTML-страницы, которые хотите просмотреть. Эти страницы содержат ссылки на другие страницы, благодаря чему вы можете перемещаться по всей сети. Постепенно добавлялась новая функциональность. Например, создаваемые на HTML-страницах формы позволяли вводить данные (скажем, для покупки продукта). Затем вы могли передать введенную информацию, а программа на сервере — обработать ее. Но формы HTML — всего лишь бледное отражение яркого графического интерфейса классических GUI-программ. Одна из целей, которых хотела добиться Microsoft, состояла в том, чтобы обеспечить в Internet богатый пользовательский интерфейс, сравнимый с интерфейсом Windows. Для этого была создана технология, обеспечившая возможность работы OCX (пользовательских управляющих элементов OLE, OLE Custom Control — наследника VBX) на страницах Web. Затем эти управляющие элементы были переименованы в "управляющие элементы ActiveX". Это имя им так понравилось, что все, что раньше было OLE, стало перекрещиваться в ActiveX. Появилось Active To и Active Это... Не забивайте себе голову этими названиями — это не более чем уловка маркетологов! Microsoft Transaction Server (MTS) Технологии Microsoft вначале ориентировались на рабочее место, а затем расширялись на уровень рабочей группы, а теперь — и всего предприятия в целом. Создание приложений уровня предприятия на сервере включает множество сложных програм м- ных решений, не видимых на рабочем месте пользователя. Серверные приложения многопоточны и могут обслуживать множество пользователей одновременно. Одним из основных вопросов при их создании была масштабируемость, так что приложение продолжает корректно работать при все большем и большем количестве подключенных пользователей. Это свойство становится особенно важным для Web-приложений, пользователи которых разбросаны по всему миру. Следующим важным вопросом становится безопасность — ведь большинство серверных приложений работает с базами данных, и одному приложению может понадобиться управление транзакциями, что повлияет на многие базы данных. Решение Microsoft в этой области называется сервером транзакций Microsoft (Microsoft Transaction Server — MTS). MTS сильно упрощает создание серверных приложений с помощью СОМ. Программист может создавать компонент СОМ как DLL, разработанную для одного пользовательского приложения, не беспокоясь о таких вопросах, как многопоточность или безопасность. Используя инструмент, называемый MTS Explorer, компонент СОМ может быть импортирован в пакет MTS. Затем пользовательская программа обращается не непосредственно к компоненту СОМ, а к процессу MTS, управляющему всеми вопросами многопользовательности, такими, например, как создание новых потоков для новых клиентов. Кроме того, с использованием MTS Explorer пакет может быть специальным образом сконфигурирован для обеспечения безопасной работы в многопользовательском окружении. Компонентам в пакете могут быть назначены определенные роли (например, Manager, Auditor, Employee) и обеспечен выборочный доступ к ним в пределах пакета на основе ролей, назначенных конкретным пользователям. MTS также управляет взаимодействием с базами данных и может объединять такие скудные ресурсы, как, например, подключения к базам данных. MTS обеспечивает простую модель управления распределенными транзакциями.
Microsoft Message Queue (MSMQ) Очередность сообщений представляет собой концептуально простую модель построения распределенных систем. Приложение создает сообщение и отправляет его в очередь. Другое приложение может считать сообщение из очереди и затем может послать другое сообщение в другую очередь. Эта другая очередь может считываться приложением, исходно пославшим сообщение, или каким-то третьим приложением. Очередь сообщений асинхронна. Как только сообщение отправлено в очередь, приложение, пославшее сообщение, возвращается к "своим делам", не дожидаясь обработки сообщения. Очередь сообщений позволяет приложениям работать в автономном режиме — сообщение может быть получено в тот момент, когда приложение не подключено к сети, а затем, при подключении, переслано в его очередь. Очередь сообщений может быть очень мощным средством — например, она может быть организована в виде сохраняемого объекта, т.е. восстанавливаться после сбоев и даже полной перезагрузки системы. Очередь сообщений и удаленный вызов процедур представляют собой высокоуро в- невые модели обмена информацией между приложениями, и каждое из них имеет свою область использования. DCOM представляет собой объектно-ориентированную модель коммуникации, построенную на базе RPC. Для синхронных операций, когда вызывающая программа для дальнейшей работы должна получить результат обработки запроса сервером, обычно используется именно этот механизм взаимодействия. Если же посылающее и принимающее приложения могут работать независимо, в разное время, то следует использовать очередь сообщений. Этот же механизм должен использоваться и в том случае, если приложению не известно, каким приложением будет обработан посылаемый запрос. В случае использования очереди посылаемое сообщение могут прочесть различные серверы, в то время как RPC обращается, как правило, к конкретному серверу. Для многих распределенных систем главным вопросом является вопрос взаимоде й- ствия различных систем. Данные могут быть доступны из многих систем при использовании стандартных технологий доступа к данным, таких как ODBC. Но что, если бизнес-правила работают на удаленной системе? Очередь сообщений обеспечивает простую модель, реализуемую на многих системах. Имеется большая категория промежуточного программного обеспечения, называемого "ориентированное на сообщения промежуточное программное обеспечение" (message-oriented middleware — MOM) и созданного специально для объединенной работы различных систем. Очередь сообщений Microsoft (Microsoft Message Queue — MSMQ) представляет собой реализацию очередей сообщений на платформе Windows компании Microsoft. Имеется три основных компонента MSMQ: ■ API для отправки и получения сообщений; ■ сообщения, созданные приложением и посланные другим приложениям; ■ очереди, в которые посылаются и из которых считываются сообщения. Очередями управляет менеджер очередей. Имеется два вида MSMQ API — классический интерфейс С и СОМ-интерфейс. С помощью последнего сервисы MSMQ могут быть вызваны любой клиентской программой, которая может использовать СОМ, включая Visual Basic и Visual C++. СОМ+ И, наконец, перейдем к СОМ+, которую можно рассматривать как следующее поколение компонентной архитектуры, разработанной Microsoft. COM+ интегрирует MTS в СОМ и обеспечивает альтернативу вызовам СОМ, используя механизм сооб-
щений, основанных на MSMQ. Результат представляет собой цельную систему, в которой упрощается создание как серверных, так и клиентских приложений. Кроме того, имеется множество сервисов, предоставление которых позволяет создавать высо- комасштабируемые приложения. Общее название связанных с СОМ+ технологий в Windows 2000 — Сервисы Компонентов {Component Services). Я полагаю, что вы получите четкое представление о том, что же такое СОМ+, научившись отделять "компоненты" от "сервисов". Я считаю, что множества недоразумений, связанных с системной архитектурой Microsoft, можно избежать, если отделить инфраструктуру ядра от множества обеспечиваемых ими сервисов. В следующей главе мы приступим к изучению Windows DNA и получим более четкое представление об этих вопросах, а пока я попытаюсь разделить на компоненты и сервисы уже рассмотренные здесь технологии. Это разделение в определенной мере условно и не может служить определением компонентов или сервисов. Цель такого разделения — помочь в понимании вопроса. Компоненты СОМ Сервисы ODBC OLE MSMQ Я считаю, что это разделение поможет нам при рассмотрении СОМ и OLE. После появления OLE было издано множество книг, на обложках которых красовалась аббревиатура "OLE", — и так продолжалось довольно длительное время, и только недавно стали появляться книги, посвященные СОМ. Таким образом, сложилась тенденция считать ключевой технологией именно OLE. OLE была и остается невероятно трудной технологией. Крейг Брокшмидт (Kraig Brockschmidt) героически попытался разоблачить то, что он называл мифом о трудностях OLE. Проблема заключается в том, что в действительности OLE — весьма сложная технология. Геометрическое расположение компонентов в составном документе, активизация приложений в контексте приложения-контейнера, слияние меню — вот только небольшой перечень проблем, которые следует решать при создании составного документа. Главным достоинством OLE была инфраструктура компонентов, использовавшаяся для поддержки всего множества сервисов. Эта инфраструктура и представляет собой СОМ. При рассмотрении СОМ+ постараемся не забывать о различии между инфраструктурой и сервисами. Модель компонентов СОМ+ Что всегда представлялось мне наиболее элегантным в СОМ, так это то, что СОМ не является промежуточным программным обеспечением (middleware). COM обеспечивает механизм для связи клиента и сервера. Результатом является исключительно гибкая архитектура компонентов, которая может использоваться как для очень быстрых компонентов, представляющих собой усовершенствование DLL, так и для серверов DCOM в сети (в обоих случаях применяется одна и та же модель). Однако для сложных приложений уровня предприятия имеются мириады случаев, когда особую ценность представляет именно промежуточное программное обеспечение. Превосходным примером может служить ODBC, и это — только вершина айс-
берга. Сложная обработка данных, которая должна выполняться для реализации распределенных приложений, включает обработку транзакций, вопросы согласованности, безопасности, очередей сообщений, уведомления о событиях и многое другое. Сли ш- ком дорого реализовывать все это для каждого приложения отдельно. Следовательно, эту роль должны сыграть системные сервисы. Гениальность СОМ+ состоит в том, что она предоставляет архитектуру, называемую перехватом, позволяющую ей вмешиваться в работу приложений только при необходимости, а не постоянно. Компоненты СОМ+ работают в том, что известно под названием контекст. Контекст можно рассматривать как набор ограничений времени выполнения. Если компонент и его клиент запускаются в одном и том же контексте, вызов метода происходит непосредственно, без вмешательства СОМ+ и без каких бы то ни было накладных расходов. Если же они запущены в разных контекстах, вызов пройдет через "перехватчик", который сделает все необходимое для удовлетворения ограничениям времени выполнения. Точно так же и возврат будет осуществлен с пр и- влечением перехватчика для выполнения необходимых действий. В результате мы получаем промежуточное программное обеспечение, которое вступает в игру тогда и только тогда, когда это необходимо. Другое ключевое свойство компонентной модели СОМ+ включает способ определения ограничений времени выполнения. Это делается не с помощью программного интерфейса, а путем объявления значений некоторых атрибутов. Эти значения атрибутов хранятся в конфигурационной базе данных, называемой каталогом. Во время работы СОМ+ на основе этих конфигурационных параметров определяется необходимый перехватчик. Сервисы СОМ+ Только что описанная модель очень элегантна, но она не представляет ничего сама по себе без, как минимум, некоторого понимания сервисов, предоставляемых описанными перехватчиками. Поэтому вкратце ознакомимся с некоторыми из основных сервисов СОМ+. Транзакции Транзакции дали имя серверу транзакций Microsoft (Microsoft Transaction Server) и остаются ключевым сервисом его наследника — СОМ+. Транзакции представляют собой единицы работы приложения. Они атомарны и либо полностью успешны, либо полностью неудачны. Множество различных видов хранилищ данных (не только традиционные базы данных) являются кандидатами для участия в транзакциях. В распределенной среде данные могут находиться во многих различных хранилищах, которые м о- гут объединяться с помощью механизма транзакций. Каким образом программировать транзакции? Эти хранилища данных имеют свои собственные различные интерфейсы, поэтому остается большой простор для деятельности промежуточного программного обеспечения, призванного сгладить отличия и скрыть их за унифицированным API для участия в транзакциях. Однако такой подход сохраняется на уровне программирования. MTS, а теперь СОМ+, предоставляют другой подход. Компоненты объявляют атрибут, означающий "требуется транзакция". Тогда во время работы с помощью перехватчика СОМ+ вызывает необходимые сервисы для участия компонента в транзакции. Программирование на уровне приложения при этом не требуется. Безопасность Вторым жизненно важным вопросом в приложениях уровня предприятия является безопасность — защита различных ресурсов приложения от несанкционированного доступа. И, опять-таки, есть два подхода: программирование систем безо-
пасности с использованием API и объявление атрибутов безопасности. СОМ+, как наследник MTS, обеспечивает безопасность высокого уровня на основе модели ролей. Каждому данному ресурсу вы можете определить различные роли — такие, как Manager, Clerk, Auditor и другие, — для каждой из которых обеспечивается определенный доступ к ресурсу, а приложение соответствующим образом трактует роли, назначенные пользователям. Членство пользователя в тех или иных ролях определяется администратором. Тем самым получается гибкая, мощная и простая в использовании система безопасности. Согласованность Приложения уровня предприятия работают со многими пользователями, которые неизбежно будут обращаться одновременно к одному и тому же ресурсу. Поэтому особое внимание при разработке приложения следует обращать на защиту потока от других потоков приложения. Это звучит устрашающе, если требуется решить проблему на уровне приложения, но все выглядит вовсе не так страшно при использовании СОМ+, которая предоставляет непрограммное решение в виде атрибутов типа "требуется синхронизация". При этом в процесс может вмешаться перехватчик и применить блокировки для обеспечения синхронизации, тем самым значительно упростив работу программиста. Очереди сообщений Мы уже видели, что MSMQ предоставляет сервис очередей сообщений, который обеспечивает большую гибкость запросов клиента к сервисам сервера. Например, клиент может быть временно отключен от сервера и, тем не менее помещать сообщения в очередь, где система гарантирует их обработку, пусть и с вынужденной задержкой. Программистский подход к использованию этого сервиса предусматривает изучение API MSMQ и непосредственные его вызовы при необходимости. Подход СОМ+ вновь использует атрибуты. При объявлении соответствующего атрибута запрос в очередь ставит не приложение, а СОМ+ (через свой перехватчик). Результатом такого подхода вновь становится упрощение работы программиста, который создает прикладное приложение. Другие сервисы Мы можем продолжать еще долго, но пора уловить и основную идею. В части III, "Windows DNA и СОМ+" этой книги вы познакомитесь с сервисами более детально. Общим для всех сервисов является то, что все они важны для создания мощного, масштабируемого приложения уровня предприятия, но их трудно реализовывать на уровне приложения. СОМ+ облегчает задачу программистов, предлагая модель объявления атрибутов. Мощь СОМ: первый взгляд Надеюсь, к этому моменту у вас уже сложилось определенное представление о том, что такое СОМ+. Путь к детальному пониманию включает в себя изучение основ СОМ, поскольку каждый компонент COM+ является компонентом COM. СОМ+ добавляет конфигурационные атрибуты и очень мощные сервисы времени выполнения, но для участия в этих сервисах вам следует реализовать компонент СОМ. В части И, "Основы СОМ" этой книги детально рассматриваются фундаментальные концепции и технологии программирования СОМ. Это изучение займет немало времени, если вы
намерены получить глубокие знания, необходимые для создания собственных мощных приложений. А пока может оказаться весьма полезным беглый взгляд на то, чего можно достичь, проделав всю эту работу. Построение Web-броузера Вся прелесть СОМ+ заключается и в том, что она строится на базе СОМ, что обеспечивает построение сложных приложений из повторно используемых компоне н- тов. Я думаю, что эта вершина системной архитектуры Windows усилиями Microsoft быстро распространится во все области, включая технологии Internet. В качестве демонстрации возможностей СОМ я приглашаю вас сесть за компьютер и создать собственный несложный Web-броузер. Мы используем Visual Basic 6.0. Если вы, как и я, фанатик C++ и никогда не программировали на Visual Basic, потратьте немного времени на знакомство с ним. Электронной справочной системы Programmers Guide более чем достаточно для того, чтобы начать работать. Среда Visual Basic достаточно проста и вы вполне сможете справиться с ней самостоятельно, в крайнем случае, воспользуйтесь небольшими подсказками и советами, которые я буду давать при работе с демонстрационными программами (например, взгляните на демонстрационную программу, представленную в главе 3, "Полигон для испытаний Windows DNA"). В книге для иллюстрации различных возможностей СОМ и СОМ+ мы будем использовать как C++, так и Visual Basic. Поскольку Visual Basic работает на более высоком уровне абстракции, чем C++, то он больше подходит для того, чтобы продемонстрировать ту или иную особенность СОМ или СОМ+ без использования излишнего кода. Построение Web-броузера является простой задачей, если воспользоваться средствами Microsoft, имеющимися в архитектуре Internet Explorer (начиная с версии 3.0). Ключевым компонентом является управляющий элемент ActiveX, называющийся Web Browser Control, который мы будем использовать в своей программе. Инсталляция примеров Все примеры к этой книге вы можете найти на Web-узле www.williamspublishing.com. Если вы все еще не посетили этот узел и не загрузили на свой компьютер самораспаковывающийся файл install.exe, то сделайте это прямо сейчас (или как только у вас появится такая возможность). Примеры несут не только иллюстративную нагрузку — они обеспечивают практические навыки работы с СОМ и СОМ+, дают более глубокое понимание предмета изучения и, право слово, стоят того, чтобы активно с ними поработать и узнать, что такое СОМ+, не понаслышке. Лучше один раз скомпилировать, чем сто раз прочесть! Если вы загрузили примеры, но еще не инсталлировали их — сейчас самое время сделать это. Самораспаковывающийся архив install.exe содержит все необходимые каталоги и файлы в упакованном виде и может установить их в соответствующий каталог на вашем жестком диске (по умолчанию это каталог C:\ComPlus). Если вы назначите для распаковки другой каталог, то вам придется немного отредактировать .reg-файлы в главах 6, "СОМ-серверы контекста приложения" и 9, "ЕХЕ-серверы". Не думаю, что зависимость от конкретного пути встретится где-либо еще, хотя специальные тесты мною и не проводились. Я предлагаю вам прямо сейчас запустить на выполнение этот install.exe и распаковать содержимое на свой жесткий диск. Оно займет немного места, а взамен вы сможете получить практику работы с СОМ и СОМ+. Ваш файл всегда остается с вами, а значит, вы можете не стесняться и экспериментировать с исходными текстами на вашем винчестере.
Структура установленных примеров очень проста. В каталоге верхнего уровня ComPlus находятся подкаталоги — по одному для каждой главы, в которой встречаются примеры кода. Каталоги глав в свою очередь имеют подкаталоги для каждой демонстрационной программы в главе (некоторые из этих программ имеют несколько этапов создания). В каталоге CaseStudy находится Electronic Commerce Game™. Демонстрация Web-броузера Вам предстоит работа в каталоге Chapl\Demos\MyBrowser. Исходный код находится в каталоге Chapl\MyBrowser. 1. Запустите Visual Basic 6. Создайте новый проект Standard EXE. Измените имя проекта на MyBrowser. Замените свойство Caption формы Forml на My Web Browser. Сохраните вашу программу в каталоге Chapl\Demos\MyBrowser (еще раз вам потребуется сохранить ее по завершении всей работы). 2. Вызовите диалоговое окно Components, выбрав в меню Projects«=>Components. Отметьте Microsoft Internet Controls (рис. 1.3) и щелкните на кнопке ОК. Теперь управляющий элемент WebBrowser добавлен в вашу палитру инструментов (в виде маленького глобуса). Рис. 1.3. Добавление управляющего элемента ActiveX в проект 3. Теперь изобразите в форме управляющий элемент для метки URL, который представляет собой текстовое поле, в которое пользователь сможет ввести интересующий его URL, кнопку GO и управляющий элемент WebBrowser. Вы можете также захотеть немного расширить форму (рис. 1.4). 4. Дайте имена txtllrl и cmdGo соответственно текстовому полю и кнопке и согласитесь с предложенным по умолчанию именем WebBrowserl для управляющего элемента WebBrowser.
5. Вызовите Object Browser для получения информации о методах нового управляющего элемента (команда меню View^Object Browser). В выпадающем списке библиотек выберите SHDocVwCtl, в списке классов Classes найдите "WebBrowser" и прокрутите список методов в поисках метода Navigate, как показано на рис. 1.5. Рис. 1.4. Добавление управляющего элемента в форму Рис. 1.5. Использование Object Browser 6. Добавьте обработчик команды для кнопки GO и введите следующий очень простой код: Private Sub cmdGo_Click() WebBrowserl.Navigate(txtURL) End Sub Вы обратили внимание на диалоговое окно Auto completion, которое появилось после того, как вы ввели WebBrowserl и добавили точку? Список в этом диалоговом окне содержит возможные методы, и, вместо того чтобы набирать название метода вручную, вы можете просто выбрать его из списка.
7. Соединитесь с Internet и запустите программу. Введите в поле URL ваш любимый Web-узел, щелкните на кнопке GO и радуйтесь результату (рис. 1.6). Рис. 1.6. Использование нового приложения для просмотра Web Программные компоненты Я надеюсь, вам понравилась эта маленькая демонстрация. В действительности она очень много говорит об архитектуре компонентов Microsoft. Среда разработки приложений Visual Basic тесно интегрирована с СОМ. Диалоговое окно Components выводит список всех управляющих элементов ActiveX, зарегистрированных в вашей системе. Управляющий элемент ActiveX представляет собой специальный тип компонентов СОМ с очень богатым набором свойств. Он может быть вставлен в среду разработки и использоваться как встроенный управляющий элемент. Когда в диалоговом окне Components вы выбирали "Microsoft Internet Controls", в ваш проект добавлялись все управляющие элементы ActiveX, содержащиеся в динамически компонуемой библиотеке shdosvw.dll (показанной в поле Location), включая управляющий элемент WebBrowser. Новые управляющие элементы отображаются в палитре Tools, как и встроенные управляющие элементы. Object Viewer показывает все доступные вам объекты. Встроенные элементы, подобно текстовым полям и кнопкам, являются такими же объектами, как и добавленные объекты СОМ. Программами просмотра объектов (типа Object Viewer) для получения информации об объектах в системе используется свойство СОМ, известное под названием "библиотека типов". Имеется ряд свойств программных компонентов, которые делают их чем-то большим, нежели просто объекты из объектно-ориентированных языков программиров а- ния. Одно из них — то, что объекты не зависят от используемого языка программирования. Они могут использоваться множеством различных языков, и распространяются в виде бинарных выполняемых файлов и представляют собой полностью закрытые черные ящики. (Библиотеки классов типа Microsoft Foundation Classes (MFC) привязаны к языку разработки (например, к C++) и обычно распространяются вместе с исходными текстами. Библиотеки шаблонов типа Active Template Library (ATL) должны распространяться в виде исходных текстов, поскольку того требует механизм использования шаблонов C++.) Исходные тексты практически представляют собой полную документацию библиотеки классов, и наследование может внести некоторые нюансы в поведение объектов. Предоставление исходных текстов также нарушает права на интеллектуальную собственность производителя библиотеки классов. Напротив, концепция распространения компонентов в виде черного ящика защищает производителя и делает функциональность компонента обособленной и более ясной для конечного пользователя. Функциональность компонентов может быть очень богатой, как мы уже
видели на примере только что созданного Web-броузера. Другим свойством компонентов является то, что они в действительности встраиваются в среду разработки, как в случае управляющих элементов ActiveX. Интеграция компонентной архитектуры с инструментарием исключительно важна, поскольку реализация и использование компонентов "вручную" представляет собой весьма сложную задачу. Здесь можно провести аналогию с написанием программы в машинных кодах и на языке ассемблера. С использованием компиляторов и различного инструментария разработки (например, интегрированной среды разработки) написание и отладка компьютерных программ очень сильно упрощается, и при этом не имеет значения, насколько сложен лежащий в основе машинный язык. В своем обозрении объектно-ориентированных языков программирования я должен был упомянуть язык Ada. Хотя язык Ada изначально поддерживает инкапсуляцию, он не был объектно-ориентированным, поскольку не имел в своем составе классов, которые позволяли бы создавать экземпляры объектов. Однако исправленные версии Ada 9x поддерживают объекты, и теперь Ada действительно представляет собой очень мощный объектно-ориентированный язык программирования. Ada используется в некоторых больших военных и правительственных системах, однако этот язык программирования так и не стал популярным коммерческим языком. Я считаю, что широкому использованию Ada мешает недостаток инструментов поддержки. При разработке Ada больше внимания уделялось спецификациям языка, и куда меньше — спецификации инструментария ASPE (Ada Programming and Support Environment), в связи с чем унифицированный инструментарий языка Ada отсутствует. Напротив, Microsoft уделяет огромное внимание инструментарию поддержки СОМ, кроме того, она опубликовала полную спецификацию COM. Microsoft поддерживает других производителей инструментария, использующих СОМ в своих средах. Таким образом, Delphi (основанный на еще одном объектно-ориентированном языке — Object Pascal (мой обзор, оказывается, очень неполон!)), PowerBuilder, Progress, SAP R/3 и другие — все они, вероятно, поддерживают СОМ (до тех пор, пока они работают на платформе Windows). СОМ представляет собой один из основных компонентов архитектуры Windows и находится в процессе переноса на другие платформы. Так, Microsoft перенесла СОМ на платформу Macintosh и объединяет свои усилия с другими разработчиками типа Software AG по переносу СОМ на платформы Unix и MVS. Поскольку до сих пор Microsoft не передала спецификации СОМ+ другим производителям (по крайней мере, на момент написания этой книги), судьба СОМ на других платформах остается неясной. Кроссплатформенная поддержка не является сильной стороной СОМ. Для взаимодействия с другими платформами вы должны рассмотреть различные стратегии, в том числе такие, как работа с СОМ в пределах систем Microsoft и шлюз к CORBA для других систем. Как уже упоминалось ранее, для интеграции с другими системами можно воспользоваться средствами MSMQ. Что же касается нашей книги, то в ней описывается использование СОМ+ на платформах Windows, и вопросы кроссплат- форменного взаимодействия выходят за ее рамки, а потому мы больше не будем к ним возвращаться. Что дальше Теперь вы должны иметь общее представление о том, что такое СОМ и СОМ+, об их месте в эволюции объектно-ориентированных систем и систем компонентов. Вы увидели практическую демонстрацию мощи технологии СОМ, в особенности при ее комбинировании с инструментарием разработки. В этой книге мы детально рассмотрим технологию СОМ+. В следующей главе СОМ+ рассматривается в контексте архитектуры Windows DNA. Мы познакомимся с тем, как Windows DNA применяется
при разработке трехуровневых приложений. После общего знакомства с архитектурой мы приступим к настройке "полигона" для испытания Windows DNA. Этот полигон очень велик и включает в себя Windows 2000, Visual Studio с Visual C++ и Visual Basic, MSMQ, SQL Server 7.0, а также некоторые другие компоненты. Хотя некоторые задачи будут решаться с применением только одного инструмента (например, для рассмотренного в этой главе примера было достаточно Visual Basic), полное изучение предмета требует обширного инструментария. Теперь вы должны иметь общее представление о том, что такое СОМ/СОМ+, об их месте в эволюции объектно-ориентированных систем и систем компонентов. Вы увидели практическую демонстрацию мощи технологии СОМ, в особенности при ее комбинировании с инструментарием разработки. В оставшейся части книги мы приступим к более детальному рассмотрению технологии СОМ+. В следующей главе СОМ+ рассматривается в контексте архитектуры Windows DNA. Мы познакомимся с тем, как Windows DNA применяется для разработки трехуровневых приложений. После общего знакомства с архитектурой мы приступим к настройке "полигона" для испытания Windows DNA. Этот полигон очень велик и включает в себя Windows 2000, Visual Studio с Visual C++ и Visual Basic, MSMQ, SQL Server 7.0 и некоторые другие компоненты. Хотя некоторые задачи будут решаться с применением только одного инструмента (например, для рассмотренного в этой главе примера было достаточно Visual Basic), полное изучение предмета требует обширного инструментария.
Глава 2 Трехуровневые приложения и Windows DNA СОМ представляет собой весьма мощную технологию. С помощью СОМ мы можем инкапсулировать очень сложное программное обеспечение (например, такое как Web-броузер) в простой в использовании программный компонент. Многие из имеющихся в настоящее время приложений технологии СОМ находят применение в клиентских программах, использующих компоненты СОМ, например, управляющие элементы ActiveX. С появлением СОМ+ стало возможным применять эту технологию не только в клиентских приложениях, но и на серверах. В этой главе мы обсудим вопросы, связанные с распределенными приложениями, проследим все этапы их эволюции — от одноуровневых до двухуровневых (клиент/сервер) приложений и далее до трехуровневых приложений. Три уровня — это уровень представления (для пользовательского интерфейса), бизнес-уровень (для логики приложения) и уровень доступа к данным. Одним из наиболее важных отличительных признаков трехуровневых приложений является используемая на уровне представления технология. В традиционных приложениях клиент/сервер упор ставился на богатое клиентское приложение, оснащенное всеми возможностями современного графического интерфейса пользователя, предоставляемого, например, Microsoft Windows. Было выпущено огромное количество богатых клиентских приложений (которые часто называют "толстыми" (fat)), однако в последнее время предпочтение отдают "тонким" (thin) клиентам, в особенности — клиентам, в основе работы которых находится Web- броузер (для представления стандартного пользовательского интерфейса). Такие Web-приложения для связи со средним уровнем используют HTTP-протокол. Остальная часть системы остается той же, что и ранее, т.е. с бизнес-объектами и объектами данных на среднем уровне, работающими с данными на третьем уровне. В данной главе будут рассмотрены эти и другие вопросы, касающиеся трехуровневых приложений, а также Windows DNA как архитектура Microsoft для построения трехуровневых приложений. Мы обсудим все три уровня и познакомимся с технологиями Microsoft для реализации каждого из них. Эволюция распределенных систем Ранние компьютерные системы не были распределенными. Пользователь непосредственно взаимодействовал с компьютером, причем на заре компьютерной эры это взаимодействие происходило на языке бинарных
программ и данных, вводившихся с помощью соответствующих переключателей, расположенных на машине. Прошло время, и было создано различное периферийное оборудование, которое позволило существенно упростить взаимодействие с машиной — с помощью перфокарт и клавиатур. В те времена с машиной одновременно мог работать только один пользователь, и нормой была пакетная работа компьютера. Интересно, что современные персональные компьютеры в какой-то мере отбросили нас назад к такой архитектуре, хотя и на другом аппаратном и программном уровне, обеспечивающем более дружественную среду для работы с машиной (ну как тут не вспомнить "закон отрицания отрицания", который, несомненно, помнят читатели постарше, — прим. перев). В этом разделе мы рассмотрим этапы перехода однопользовательских систем в многопользовательские одноуровневые, а затем — в двухуровневые (клиент/сервер) системы. Одноуровневые системы Простейшим видом многопользовательской системы является одноуровневая система, в которой все данные и их обработка выполняются на центральном компьютере, обычно представляющем собой мейнфрейм или мини-компьютер. Пользователи взаимодействуют с центральным компьютером с помощью терминалов. Поскольку все данные и их обработка сосредоточены в одном месте, управление такой системой существенно упрощается. На рис. 2.1 показана схема одноуровневой системы. Мейнфрейм/Миникомпьютер Данные Терминал Терминал Рис. 2.1. Одноуровневая система Такая система имеет ряд недостатков. Поскольку вся обработка выполняется на центральном компьютере, вычислительные мощности, выделяемые одному пользователю, ограничены. Центральный компьютер, кроме того, отвечает за логику представления, которая в этом случае обычно имеет текстовый вид и потому очень проста. В такой системе имеется единственный возможный источник сбоев — если центральный компьютер идет ко дну, то тонут все пользователи. Центральный компьютер — самый важный компонент одноуровневой системы и очень дорогостоящий; он создан специально для работы с максимальной устойчивостью и надежностью. Такая зависимость всех от работоспособности центрального компьютера является основным недостатком одноуровневой системы, описанной выше. В системе работает одно приложение, установленное на центральном компьютере, что приводит к удорожанию программного обеспечения для подобных систем, так же как и аппаратного — в связи с требованием повышенной надежности.
Локальные вычислительные сети ПК Ответом на отсутствие гибкости систем с мейнфреймами стали персональные компьютеры (ПК). Основная привлекательность персональных компьютеров изначально заключалась в доступности большого количества разнообразного недорогого программного обеспечения. Благодаря низкой цене персональных компьютеров их кол и- чество очень быстро выросло, создав благодатную почву для рынка программного обеспечения, что автоматически повлекло за собой снижение цен и большое разнообразие продуктов. На рынке программного обеспечения для персональных компьютеров можно найти все для того, чтобы решить практически любую задачу, и вам не требуется тратить время на разработку собственных приложений или деньги на покупку дорогостоящих программ для мейнфреймов. Заметим также, что рост популярности персональных компьютеров начался еще до появления графических пользовательских интерфейсов, которые сделали этот рост просто аномальным. Однако изначально персональные компьютеры представляли собой автономные машины, что создавало огромные препятствия на пути их использования в бизнесе. Чтобы обойти это ограничение, стали подключать персональные компьютеры к локальным вычислительным сетям, получая в результате приведенную на рис. 2.2 конфигурацию. Сервер представляет собой файловый сервер или сервер печати; в системе используется сетевая операционная система типа NetWare или LAN Manager с соответствующим клиентским программным обеспечением на каждом локальном персональном компьютере. В такой системе разделяемыми являются только файлы и принтеры, а все приложения выполняются на локальных персональных компьютерах. ПК ПК Сервер Локальные данные Локальные данные Разделяемые данные Рис. 2.2. Локальная вычислительная сеть персональных компьютеров Обратите внимание на то, что в данной конфигурации можно запускать разделяемые приложения, хранящиеся на сервере, — но при этом сервер хранит только выполняемые файлы, а каждый персональный компьютер запускает собственную копию приложения в своей локальной памяти. Приложение базы данных, созданное для работы на одном персональном компьютере, в такой среде способно работать с разделяемой базой данных, но только в разделяемом режиме и при наличии соответствующих блокировок. Такой путь неэффективен, хотя бы потому, что данные при этом находятся на совместно используемых дисках, а обрабатываются на отдельных персональных компьютерах, что приводит к постоянному обмену большими объемами данных между отдельными персональными компьютерами и совместно используемым диском.
Двухуровневые (клиент/сервер) системы Мы оказались на распутье. С одной стороны, у нас есть дорогие централизованные системы, построенные на мейнфреймах, с другой стороны, — недорогие гибкие системы, построенные на локальных сетях персональных компьютеров. Недорогие персональные компьютеры привлекательны, но требуют решения некоторых фундаментальных проблем, таких как неэффективность постоянного обмена данными по сети. Серверы баз данных Следующим шагом на рассматриваемом нами пути было решение проблемы постоянной пересылки данных между сервером и персональным компьютером. Рассмотрим ситуацию на конкретном примере таблицы базы данных с 10000 записей. Поступает запрос на отдельную запись, содержащую данные, отвечающие некоторому критерию отбора. При работе приложения в памяти конкретного персонального компь ю- тера для выполнения запроса по сети должны быть переданы все записи, тогда как требуется только одна запись, отвечающая некоторым критериям запроса. Решение заключается в разбиении приложения на две части. Первая часть представляет собой работающий на сервере механизм базы данных, а вторая — работающий на персональном компьютере интерфейс пользователя (теперь именуемый клиентом), и эти две части приложения общаются друг с другом по сети с помощью некоторого сетевого протокола. В нашем примере с запросом к базе данных и сама база данных, и обслуживающий ее программный механизм расположены на сервере, и клиенту пересылается только результат запроса (т.е. одна запись), который будет предоставлен пользователю клиентским приложением. Эта система клиент/сервер проиллюстрирована на рис. 2.3. Сервер Данные Клиент Клиент Рис. 2.3. Двухуровневая система клиент/сервер Хотя рис. 2.1 и 2.3 очень похожи, между ними имеется принципиальное отличие. Переход к системам клиент/сервер представляет собой огромный концептуальный скачок в эволюции распределенных систем. Все предыдущие системы характеризовались тем, что приложение целиком работало на одной машине, так же как и на самом первом компьютере. В системе клиент/сервер имеется два независимых, тесно сотрудничающих между собой приложения, одно из которых работает на сервере, а второе — на компьютере-клиенте, и которые общаются между собой посредством сети. Хотя такая архитектура имеет определенные достоинства — как было показано на примере с запросом к базе данных, — по существу, приложения клиент/сервер очень сложны.
К счастью, работа программиста существенно упрощается благодаря соответствующему инструментарию. Для приложений баз данных СУБД могут обеспечить необходимую инфраструктуру целиком. В качестве простого примера рассмотрим базу данных SQL Server под названием Game, установленную на удаленном компьютере. В ней имеется таблица Products со столбцами Item и price. Мы хотим найти цену игры cat carrier (дословно — "носитель гусеничной техники", — прим. перев.), для чего можно использовать инструмент Query Analyzer из поставки SQL Server 7. Если вы хотите заняться этим прямо сейчас — обратитесь к главе 18, "SQL Server и ADO" за инструкциями по установке базы данных Game. Запустите Query Analyzer. Перед вами появится экран входа, где вы можете выбрать, к какой системе SQL Server следует подключиться (рис. 2.4). В выпадающем списке SQL Server выберите имя компьютера, на котором расположена база данных. Далее выберите базу данных Game из выпадающего списка DB и введите SQL- запрос: select * from Products where item = 'cat carrier' В результате вы получите ответ, "гласящий", что искомая цена равна 30,00 (рис. 2.5). Рис. 2.4. Подключение к удаленной СУБД Рис. 2.5. Простой SQL-запрос
Этот простой пример иллюстрирует квинтэссенцию приложений клиент/сервер. Серверная часть приложения представляет собой механизм базы данных, работающий на удаленном компьютере. Клиентская часть приложения представляет собой инструмент запросов, работающий на локальном компьютере. Эти две части приложения общаются друг с другом по сети посредством протокола, предоставленного СУБД. Прикладная программа в данном случае является простым SQL-запросом, а вся инфраструктура предоставлена СУБД. Конечно, обычно клиентская программа представляет собой не инструментарий создания запросов, а некоторое пользовательское приложение. Инструментарий типа Visual Basic или PowerBuilder облегчает построение приложений с графическим интерфейсом пользователя для работы с базами данных. Если работа всегда идет с одним типом СУБД (например, SQL Server или Oracle), то можно положиться на сетевой протокол, предоставляемый СУБД. СУБД также поставляет клиентский API, который можно использовать для работы с этим протоколом, облегчая тем самым работу программиста. Если в организации используется несколько СУБД или если создаваемое приложение не должно привязываться к конкретному типу СУБД и может в будущем быть перенесено на другую СУБД, имеет смысл использовать промежуточное программное обеспечение типа ODBC. В главе 1, "Что такое СОМ+" упоминалось, что ODBC (Open Database Connectivity — Открытое подключение к базе данных) представляет собой часть открытой системной архитектуры Windows фирмы Microsoft (Windows Open System Architecture — WOSA) (на рис. 1.2 показана базовая архитектура). Линии между драйверами и источниками данных в действительности могут пересекать границы сети. Ответственность за сетевое соединение несет СУБД. Может показаться, что система клиент/сервер предоставляет почти полное решение, простое с точки зрения программной реализации благодаря использованию СУБД. Но что, если мы захотим решить дифференциальное уравнение в частных производных? Насколько мне известно, ни одна СУБД не способна на такой подвиг. Это означает, что код для решения уравнения должен находиться у клиента, и мы опять оказываемся в ситуации, когда требуется пересылка больших объемов данных от сервера к клиенту. Мы хотим, чтобы клиент был способен выполнить команду "решить уравнение". Необходимые при этом данные находятся на сервере, и хотелось бы, чтобы соответствующий код выполнялся там же, с пересылкой клиенту только полученного результата. Имеется и другой недостаток простой архитектуры СУБД типа клиент/сервер. Когда вся присущая приложению логика размещена на машине-клиенте, мы получаем слишком "толстый" клиент. У такого подхода несколько изъянов. С одной стороны, интенсивные вычисления на машине-клиенте могут потребовать более дорогого компьютера для решения поставленных задач. Другой недостаток заключается в организации обслуживания — поддержка и обновление программного обеспечения на каждой машине-клиенте с ростом количества клиентов очень быстро превращается в кошмар. В действительности недостатки системы клиент/сервер оказались настолько велики, что слухи о кончине мейнфреймов оказались несколько преувеличенными. Экономия средств на более дешевых персональных компьютерах может легко вылететь в трубу подъема мощности клиентов и их обслуживания. В результате системы, основанные на мейнфреймах, оказались удивительно живучи. Еще одна причина их долголетия состоит в том, что они могут служить отличными серверами. Серверы приложений Если не рассматривать возвращение к одноуровневой системе, нам придется найти путь реализации функциональности приложений на сервере, как для уменьшения требований со стороны клиента, так и для упрощения администрирования. По этому
пути мы придем к серверам приложений, которые способны на большее, чем просто позволять совместно использовать файлы и принтеры (как в случае файл-серверов и серверов печати локальных вычислительных сетей персональных компьютеров) или обеспечивать доступ к базам данных (как только что рассмотренные серверы баз данных). Нам требуется получить возможность кодировать требуемую функциональность и запускать ее на сервере. Специализированные файловые серверы типа NetWare не слишком приспособлены для решения таких задач, и наиболее популярными сервер а- ми приложений стали Unix и NT. При запуске функциональности приложения на сервере мы встретимся с вопросами сетевого взаимодействия, которые в случае серверов баз данных вместо нас решали СУБД. Один из подходов состоит в увеличении возможностей базы данных, для того чтобы оснастить ее языком программирования общего назначения, с помощью которого вы могли бы кодировать алгоритм решения дифференциальных уравнений в частных производных в пределах СУБД. Технически такой подход вполне реализуем, и некоторые фанатики баз данных горячо ратуют за него. Последний стандарт SQL содержит более 800 страниц — сравните это число со 100 первоначальными страницами, охватывающими все требования реляционных баз данных. Некоторая бизнес-логика может быть реализована в базе данных с помощью механизма хранимых процедур, но пытаться сделать из баз данных универсальный вычислитель вряд ли разумно. Кстати, это идет вразрез с современным подходом модульности в программировании. Да и вряд ли покупатели захотят быть "заперты" в одной, пусть и очень сложной СУБД... Таким образом мы приходим к серверу, обеспечивающему функциональность пр и- ложения вне базы данных; приложение должно реализовать сетевое соединение. Для этого могут использоваться как сетевые протоколы типа TCP/IP, SPX/IPX (NetWare) или NetBios/NetBEUI (LAN Manager), так и протоколы более высокого уровня типа сокетов или каналов (pipes). Все это требует высокой степени специализации коммуникационных программ, кроме того, низкоуровневые протоколы зависят от используемого типа сети. Проще программировать сетевые коммуникации с использованием протокола удаленного вызова процедур (Remote Procedure Call — RPC), но даже этот уровень программирования достаточно сложен. Однако протоколы еще более высокого уровня могут сделать коммуникационные задачи вполне решаемыми. Одним из примеров решения может служить модель Microsoft DCOM, которую мы вкратце рассмотрим в главе 3, "Полигон для испытаний Windows DNA", и более подробно — в главе 10, "Введение в DCOM". Другие примеры включают CORBA (Common Object Request Broker Architecture) от Object Management Group или Remote Method Invocation в Java. Еще одним подходом является промежуточное программное обеспечение, ориентированное на сообщения (message- oriented middleware — MOM) типа IBM MQSeries и Microsoft MSMQ. Этот аббревиатурный винегрет технологий показывает сложность архитектуры клиент/сервер и то, что панацеи, которая бы сделала решение проблем простым и легким, не существует. Организация разработки требует выбора соответствующих вашим требованиям технологий и инвестиций для приобретение необходимого инструментария и изучения выбранных технологий. В этой книге мы остановимся на техн о- логиях, предлагаемых Microsoft. В действительности термин "сервер приложений" (application server) означает больше, чем общее имя сервера, который обладает некоторой дополнительной, по сравнению с файл-сервером, сервером печати и сервером баз данных, функциональностью. Сейчас это, скорее, категория программных продуктов, обеспечивающих множество сервисов и существенно упрощающих разработку распределенных приложений. Microsoft предлагает на рынке свой собственный Microsoft Transaction Server (MTS) и множество различных серверов приложений Java. Мы обсудим эти продукты более детально при рассмотрении трехуровневых приложений.
Трехуровневые системы В стороне от задач коммуникаций, которые так или иначе должны решаться в любой распределенной системе, находятся некоторые присущие двухуровневому подходу ограничения. Вернемся к обычным приложениям для работы с базами данных (в которых не решаются дифференциальные уравнения в частных производных). Нам незачем беспокоиться о вопросах коммуникации, так как об этом уже побеспокоились разработчики СУБД. Но кроме этих вопросов есть множество других, не менее важных. Приложения для работы с базами данных имеют общую структуру. В первую очередь, все они имеют собственно данные, находящиеся в базе данных; кроме того, все они имеют пользовательский интерфейс, работающий на клиентском персональном компьютере или рабочей станции. И, наконец, у всех у них есть бизнес-логика. В одноуровневых системах бизнес-логика реализуется на центральном компьютере, в двухуровневых же системах, как правило, бизнес-логика отдана клиенту (некоторая предварительная обработка, само собой разумеется, с помощью хранимых процедур может быть выполнена в базе данных). Мы уже упоминали о проблемах "толстых" клиентов, в частности о повышенных требованиях к клиентским персональным компьютерам и о сложности управления приложениями на множестве машин. Есть еще один важный вопрос, связанный с конструированием, а именно вопрос масштабируемости. В системах клиент/сервер каждый клиент непосредственно подключается к базе данных. Это нормальное решение для приложений уровня отдела, но на уровне предприятия с большим числом пользователей оно может попросту привести сервер в неработоспособное состояние (а также каналы соединения — например, связь с Internet может быть попросту перекрыта из-за большого количества одновременно работающих с сервером пользователей). Проблема заключается в том, что подключение к базе данных — быстро исчерпываемый ресурс. Кроме того, если клиент связывается непосредственно с базой данных, то он ограничен определенным типом базы данных. Определенную гибкость предоставляет ODBC, но клиент остается ограничен типом данных, для которых имеется драйвер ODBC. Более гибкая и масштабируемая архитектура создается при введении третьего уровня, называемого "бизнес-уровнем", или "уровнем приложения", который располагается между уровнем представления и уровнем доступа к данным, как показано на рис. 2.6. Уровень представления Бизнес-логика Доступ к данным Рис. 2.6. Трехуровневая архитектура Бизнес-уровень отделяет клиент от непосредственной зависимости от базы данных. Таким образом, база данных может быть изменена без какого-либо воздействия на код уровня представления. Более того, бизнес-уровень может реализовывать "бизнес-правила", применяемые многими различными приложениями-клиентами, что приводит к увеличению повторного использования кода. При изменении бизнес- правил изменения должны вноситься только на бизнес-уровне. Реализуется бизнес- уровень на сервере, так что клиентская система остается неизменной при изменении бизнес-логики, а значит, поддержка и обновление системы упрощается. И, наконец, оптимизация бизнес-уровня может резко повысить масштабируемость системы. Например, бизнес-уровень может поддерживать пул соединений с базой данных и предоставлять их клиентам при необходимости непосредственного доступа к базе данных.
По завершении работы с базой данных соединение не закрывается, а возвращается в пул бизнес-уровня — тем самым небольшое число постоянно поддерживаемых соединений в состоянии обслужить большое количество клиентов, поскольку не все клие н- ты требуют постоянного одновременного доступа к базе данных. Серверы приложений на среднем уровне Хотя концептуально трехуровневая архитектура очень проста, ее реализация представляет собой сложную задачу. Здесь имеется множество вопросов, которые требуют рассмотрения. Одним из них являются уже обсуждавшиеся сетевые соединения. Другой вопрос связан с только что упоминавшимся пулом соединений с базой данных. Еще один вопрос заключается в многопоточности разрабатываемых приложений: так как к бизнес-уровню одновременно обращается множество пользователей, а этот уровень дополнительный, мы не можем рассчитывать на помощь СУБД в решении такого рода задач. Бизнес-уровню может потребоваться одновременное обращение к нескольким базам данных, а потому может оказаться необходимой система управления распределенными транзакциями. Однако всех возникающих проблем не перечислить... Должно быть, очень трудно реализовать трехуровневое приложение, если нужно разрешать такое количество описанных выше проблем. К счастью, такие задачи уровня системного программирования может решать сама система (аналогично тому, как СУБД решает вопросы подключения, совместной работы и транзакций, на среднем уровне подобные функции выполняет новая категория продуктов — серверы приложений). Предлагаемое Microsoft программное решение этого класса задач для NT 4.0 — Microsoft Transaction Server, или MTS. За MTS следует COM+, представляющая собой основной предмет изучения данной книги. Для реализации трехуровневых приложений требуется очень много составных частей, и Microsoft организовала решение этого вопроса под рубрикой "Архитектура распределенных приложений Internet" (Distributed interNet Applications (DNA) architecture). В последующих разделах этой главы мы познакомимся с основными частями Windows DNA, включая очень важную разновидность трехуровневых приложений для использования в Internet, называемых приложениями Web. Общая структура Windows DNA Windows DNA представляет собой грандиозное предвидение, включающее в себя множество сложных технологий. Это нечто большее, чем просто реализация трехуровневой модели фирмой Microsoft. Будет весьма разумно пройти по всем трем уровням и взглянуть на используемые на этих уровнях технологии. Кроме технологий, "привязанных" к уровням, применяются и технологии, предоставляющие общие сервисы и так называемые "склеивающие" технологии, которые нельзя отнести к какому-то одному конкретному уровню. Перед тем как приступить к прогулке по уровням, вкратце познакомимся с этими сервисами и технологиями. Общая структура Windows DNA показана на рис. 2.7. Общие сервисы Общие сервисы включают базовый Win32 API и множество других сервисов, надстроенных над Win32. Некоторые из важных сервисов являются специфичными для того или иного уровня. В этом разделе мы рассмотрим некоторые из основных серв и- сов, применяемых более чем к одному уровню.
"Клей" Общие сервисы Уровень представления Сетевые сервисы Безопасность СОМ/ СОМ+ Уровень бизнес-логики Активные каталоги Кластеризация Уровень доступа к данным и др. Рис. 2.7. Структура Windows DNA Сетевые сервисы Фундаментальную роль в распределенных процессах, конечно, играют сетевые сервисы, построенные на основе базовых сетевых протоколов, наиболее важным из которых является TCP/IP. Windows DNA связана с двумя протоколами высшего уровня — HTTP (HyperText Transfer Protocol — Протокол передачи гипертекста), который является промышленным стандартом соединения клиентов и серверов Web через Internet, и DCOM, который представляет собой объектно-ориентированный протокол Microsoft, построенный на базе RPC. DCOM является естественным расширением СОМ и рассматривается в главе 10, "Введение в DCOM". Мы увидим, что существует два основных типа приложений DNA: тонкие клиенты с использованием HTTP и толстые — с применением протокола DCOM. Безопасность Безопасность представляет собой очень важный аспект функционирования распределенных систем. Вопросы безопасности включают аутентификацию пользователя (для того чтобы гарантировать, что пользователь именно тот, за кого себя выдает), ограничения доступа к данным и сервисам и защиту данных при их пересылке (например, с помощью шифрования). Ядро безопасности обеспечивается операционной системой. Windows NT/2000 обеспечивает "слоистую" систему безопасности для различных провайдеров систем защиты с использованием специального интерфейса SSPI (security support provider interface — интерфейс провайдера поддержки безопасности). Windows NT основывается на NTLM (NT LAN Manager), a Windows 2000 использует более мощного провайдера безопасности Kerberos. DCOM обеспечивает высокоуровневый механизм безопасности, применимый к объектам. Microsoft Transaction Server и СОМ+ предоставляют модель безопасности еще более высокого уровня и более простую в применении. Более подробно вопросы безопасности рассматриваются в главе 17, "Windows 2000 и безопасность СОМ+". Активные каталоги Очень важен новый сервис, появившийся в Windows 2000, — активные каталоги. Windows начала свой рост как операционная система рабочего места для одного пользователя. Затем она была расширена для работы в локальной сети, объединяющей
множество пользователей в рабочую группу (workgroup). Такая сетевая конфигурация представляет собой объединение равных пользователей, без выделенного центрального хранилища пользователей. В NT Server была реализована концепция доменов, представляющих собой хранилища пользователей. Однако размер такого хранилища ограничен, и большое предприятие было вынуждено иметь несколько доменов, что приводило к определенным трудностям при их администрировании. Активный каталог представляет собой центральное хранилище, которое может управлять всеми пользователями предприятия. Предоставляются возможности репликации этого хранилища для увеличения мощности и производительности системы. Активный каталог является объектно-ориентированным и может хранить различные типы элементов, а не только информацию о пользователях; он стал важных хранилищем системной информации. Рассмотрение активного каталога выходит за рамки настоящей книги, но в главе 3, "Полигон для испытаний Windows DNA" мы увидим, что его требуется установить на один из наших компьютеров, чтобы создать полнофункциональный полигон, в котором мы могли бы управлять множественными записями пользователей. Кроме того, активный каталог необходим для работы Microsoft Message Queue. Кластеризация Еще одним важным вопросом больших систем является их масштабируемость. Каким образом обработать очень большое число одновременно работающих пользователей? Windows DNA предоставляет различные технологии для увеличения масштабируемости, и некоторые из них мы рассмотрим в части III, "Windows DNA и СОМ+". Одной из важных технологий Microsoft является Cluster Server (Сервер кластеров), представляющий собой программное обеспечение, позволяющее многим компьютерам в сети участвовать в обработке данных, а потому называемое "свободно связанная многопроцессность". Кластеризация не требует специального аппаратного обеспечения, в отличие от "тесно связанной многопроцессности", означающей установку нескольких центральных процессоров на одном компьютере. Кластеризация может быть настроена для подцержки высокой степени отказоустойчивости. Технология кластеризации Microsoft рассматривается в главе 23, "СОМ+ и масштабируемость". "Склеивающие " технологии Следующей важной частью Windows DNA являются "склеивающие" технологии, позволяющие различным частям программного обеспечения сотрудничать для достижения цели. В программном обеспечении Microsoft роль "склеивающих" технологий играют СОМ и ее "большая сестра" СОМ+. сом Ядром базовой технологии, используемой во всех трех уровнях Windows DNA, служит СОМ. Детальнее СОМ будет рассмотрена в части И, "Основы СОМ". А пока о СОМ достаточно знать то, что это — технология быстрая, объектно-ориентированная и пакетная. Она быстрая, так как может быть вызвана из процесса непосредственно. СОМ представляет собой результат эволюции объектно-ориентированной технологии, как мы видели в главе 1, "Что такое СОМ+". Это означает, что объекты СОМ обычно являются частью объектной модели и, таким образом, имеют организованную структуру. И, наконец, компоненты СОМ объединяются в симпатичные пакеты типа DLL, которые удобно вставлять в приложения, приобретать у сторонних производителей и пр.
СОМ+ СОМ+ можно рассматривать двояко. С одной стороны, СОМ+ можно рассматривать как коллекцию сервисов, обеспечивающих поддержку сложных распределенных приложений, которые существенно облегчают жизнь прикладного программиста, позволяя ему избежать кодирования многопоточности, пулов подключений и т.п. В этом смысле СОМ+, в первую очередь, адресована среднему уровню и может рассматриваться, как объединение MTS и СОМ. Более фундаментальный путь — рассматривать СОМ+, как расширение модели СОМ для поддержки декларативного программирования, основанного на атрибутах. Объект и его клиент живут каждый в своем собственном программном контексте, со своими собственными представлениями о многопоточности, требованиях транзакций и т.п. СОМ+ позволяет как объекту, так и его клиенту декларировать свои собственные атрибуты. Если при этом обеспечивается полное совпадение атрибутов, то имеет место непосредственный вызов методов. Если же атрибуты соответствуют не полностью, требуется некоторый объем дополнительной работы по согласованию контекстов для их совместной работы. СОМ+ вводит концепцию перехватчика (interceptor), который автоматически вызывается системой при необходимости согласования контекстов. Перехватчики обеспечиваются системой, и прикладному программисту нет необходимости в дополнительном кодировании, направленном на согласование контекстов. СОМ+ детально рассматривается в части HI, "Windows DNA и СОМ+" этой книги, а фундаментальным архитектурным свойствам атрибутов, контекстов и перехватчиков посвящена глава 14, "Основы архитектуры СОМ+". Слои Windows DNA В этом разделе мы опишем некоторые технологии Microsoft и относящийся к ним инструментарий, предназначенный для разработки и реализации трехуровневых пр и- ложений. Диапазон применения этих технологий очень широк, поэтому здесь мы не будем пытаться провести полный обзор, а обратим свое (и ваше) внимание на основные в контексте данной книги технологии. Уровень представления Есть два обширных вида клиентов, которые Microsoft именует "бедным клиентом" (thin client1) и "богатым клиентом" (rich client). Дистанцируясь от "толстого (fat) клиента", "богатый клиент" в большей степени ссылается на используемые при создании пользовательского интерфейса технологии, чем на то, какое количество кода выполняется на стороне клиента. Богатые клиенты Богатые клиенты представляют собой приложения Win32, имеющие доступ ко всем ресурсам компьютера клиента. Они ничем не отличаются от прочих приложений Win32, за исключением того, что одновременно представляют собой и клиентскую часть трехуровневого приложения. Их главное отличительное свойство заключается в развитом графическом интерфейсе пользователя. Microsoft проявляет необычайную агрессивность в навязывании внешнего вида своих приложений Windows со множест- * Английское слово thin имеет основное значение "тонкий, худой", но в данном контексте представляется более уместным использовать такой перевод этого слова, как "бедный, жалкий". — Прим. перев.
вом визуальных элементов, бантиков и оборочек. Современный графический интерфейс пользователя Windows почти ничем не напоминает своего прямого предка — классического WIMP (Windows, Icons, Menus and Pointing device). Стандартный интерфейс сегодня включает панели инструментов, информационные панели, строки состояния, подсказки, контекстные меню и множество специальных элементов типа раскрывающихся деревьев... Сравните современное приложение Windows и типичную HTML-форму, и вы поймете, что я имею в виду (впрочем, стимулируемый Windows интерфейс Web тоже не стоит на месте). Традиционный путь построения богатого клиента пролегает через системы создания приложений высокого уровня типа Visual Basic или Visual C++, оснащенные дополнениями наподобие управляющих элементов ActiveX. Новый подход Microsoft заключается в использовании в качестве конечных пользовательских программ приложений Office. Приложения Office издавна обеспечены богатыми возможностями настройки опытными пользователями с использованием макросов. Изначально у каждого продукта Microsoft Office имелся свой собственный, несовместимый с другими, макроязык; сегодня ситуация изменилась, и все приложения Office используют общий язык VBA (Visual Basic for Applications), который Microsoft к тому же лицензирует другим разработчикам приложений Windows. Достоинство богатых клиентов — в их функциональности, как с точки зрения графического интерфейса пользователя, так и с точки зрения выполнения дополнительной обработки данных на компьютере клиента. Но "богатые тоже плачут", и основным недостатком богатых клиентов является то, что на клиентский компьютер при этом накладываются требования, одолеть которые под силу только богатому клиенту (в данном случае — клиенту с большим счетом в банке). Встают также вопросы, связанные с работой богатых клиентов с приложениями Internet. Если вы содержите магазин, ваша цель — продажа товара как можно большему числу клиентов, а не только тем, кто в состоянии купить очень мощный персональный компьютер, установить на него операционную систему Windows, получить от вас (или загрузить по Internet) большое приложение для Windows, инсталлировать его и поддерживать... Вряд ли при этих условиях у вас найдутся покупатели! Вам следует максимально облегчить жизнь покупателя, а потому для работы им должно хватать простейшего Web-броузера. Бедные клиенты Бедность тоже бывает разная, и бедные клиенты не все одинаково бедны. Примером бедного клиента служит издавна известный терминал — но и здесь произошли кое-какие изменения. Microsoft добралась и сюда, предложив технологию Windows Terminal Server, при которой приложение Windows работает на центральном сервере и передает графический интерфейс пользователя клиенту. Такая конфигурация имеет главное достоинство, заключающееся в простоте администрирования. Но при этом требуется дорогостоящий сервер, широкая полоса пропускания между клиентом и сервером и пр. Однако, чем дальше, тем чаще понятие "бедный клиент" обозначает приложение, работающее на Web-сервере и передающее пользовательский интерфейс с помощью HTML-страниц на Web-броузер. Это и есть воплощение мечты электронных торговцев о приложении, которое работает "везде". Через некоторое время родилась идея оснащения Web-приложений различного типа компонентами, которые могут использоваться броузером, — такими, как Netscape plug-ins, управляющие элементы ActiveX, аплеты Java и др. Проблема только в том, что ни одна из этих технологий не является стандартом, так что получить всеобщий охват с помощью какой-то из них невозможно. То же самое можно сказать и о сценариях, выполняющихся на стороне клиента. Более подробно эти вопросы мы обсудим в главе 20, "Использование СОМ+ в Web-приложениях".
Другой подход заключается в наращивании мощи и богатства самого HTML. Стандарт HTML 4 определяет динамический HTML (он же DHTML). С помощью этой технологии можно создавать Web-страницы с богатым пользовательским интерфейсом, который будет работать на любом совместимом броузере. Однако пока нет полного согласия в вопросах реализации DHTML, и, конечно же, старые броузеры его не поддерживают. Заметим, однако, что Web-сервер в состоянии определить тип используемого клиентом броузера и легко настроить передаваемую Web-страницу соответствующим образом. Технология Microsoft для работы с бедными клиентами была реализована в их собственном Web-броузере Microsoft Internet Explorer (IE), текущая версия которого в настоящее время — 5.0. Основная идея Windows DNA заключается в том, что бедные клиенты для Web-приложений должны работать на любом Web-броузере. Следовательно, для Web-страниц должен использоваться бедный старый HTML 3.2. Идея заключается в том, что бедный клиент должен иметь возможность работать везде, но на платформе Microsoft с использованием IE он должен работать гораздо лучше. Сценарии и компоненты Возможности как богатых, так и бедных клиентов могут быть расширены путем использования подходящей технологии. В частности, они могут использовать компоненты СОМ, которые мы будем изучать в части II, "Основы СОМ". Богатые клиенты могут вызывать компоненты СОМ с помощью стандартных технологий, а бедные клиенты — применять для этого механизм сценариев с использованием языков типа JavaScript и VBScript. Простейший путь размещения некоторой логики на Web-странице заключается в использовании сценариев, которые могут выполняться как на стороне сервера, так и на стороне клиента. Сценарии, выполняющиеся на стороне клиента, представляют собой уровень представления; их легко изучить и реализовать, и они способны резко увеличить общую производительность Web-приложения. В качестве простейшего примера рассмотрим процедуру проверки корректности заполнения полей формы HTML. Стандартная технология использует проверку на стороне Web-сервера, и пользователь не может увидеть ошибки ввода, пока не отправит форму серверу. При использовании сценариев проверка выполняется сразу же по заполнении полей формы. Сценарии обсуждаются в главах И, "Автоматизация и программирование СОМ на Visual Basic" и 20, "Использование СОМ+ в Web-приложениях". А пока запомните, что если вы хотите создать тонкий клиент, который бы работал на любом Web- броузере, то должны избегать выполнения сценариев на стороне клиента. Уровень бизнес-логики Средний уровень, или уровень бизнес-логики, представляет собой самую важную часть трехуровневой архитектуры и отличает ее от предшественниц. Большинство новых сервисов, предоставляемых Microsoft, предназначено для среднего уровня, и именно на его рассмотрении сфокусирована наша книга. Три общих сервиса, предоставляемых Microsoft для среднего уровня, являются сервисами компонентов (СОМ), Microsoft Message Queue (MSMQ) и Internet Information Server (IIS). Начнем рассмотрение с более простого для понимания IIS. Internet Information Server Internet Information Server представляет собой полнофункциональный Web-сервер Microsoft (текущая версия 5.0), интегрированный в Windows 2000 Server. IIS является сервером приложений, поддерживающим бедных клиентов, которые подключаются к
нему с помощью протокола передачи гипертекста (HTTP). Клиенты посылают запросы серверу, используя этот протокол. Запрос может представлять собой простое требование выслать HTML-страницу или содержать данные из заполненной HTML- формы. Запрос может также содержать требование выполнить некоторую программу, возможно, с использованием Common Gateway Interface (CGI). При выполнении программы страница HTML генерируется "на лету" и содержит данные, полученные в результате работы программы, — например, результаты вычислений или обращения к базе данных. HTTP представляет собой протокол без запоминания состояния, с разрывом связи между клиентом и сервером после каждого ответа от сервера2. На рис. 2.8 показана базовая Web-структура, не привязанная к технологиям Microsoft. Web-клиент HTTP Web-сервер Программы Данные Рис. 2.8. Клиенты и серверы Web Базовый протокол HTTP и использование CGI для вызова программ на Web- сервере — это обычная технология Internet, поддерживаемая каждым Web-сервером. Microsoft добавляет к Web-серверу значительное количество расширений. При использовании клиентских расширений Microsoft (ваше приложение может не работать на всех Web-броузерах) вы можете определить, какой сервер запускает ваши приложения, и, таким образом, в случае, если сервер используется на платформе Windows, 2 Последнее, впрочем, не совсем верно, так как HTTP 1.0 позволяет не разрывать соединение с помощью передачи заголовка "Connection: Keep Alive", a HTTP 1.1 по умолчанию поддерживает соединения до явного закрытия его сервером или клиентом. — Прим. перев.
можно получить дополнительные функциональные возможности с помощью серверных технологий Microsoft. Наиболее фундаментальным расширением сервера Microsoft является ISAPI (Internet Services Application Programming Interface), который обеспечивает высокопроизводительную альтернативу CGI. В то время как CGI для обработки каждого запроса запускает новый процесс, ISAPI использует DLL для работы в контексте основного процесса. ISAPI остается самым высокопроизводительным решением Microsoft для Web-приложений. Если в вашем Web-приложении появилось узкое место, стоит рассмотреть вопрос о разработке части кода на уровне ISAPI. ISAPI DLL выполняет непосредственные вызовы Win32 API и может, конечно, использовать компоненты СОМ, так же как и любая программа Windows. Однако для большей части работы, выполняемой на сервере, вы, вероятно, захотите использовать технологию активных страниц сервера (Active Server Pages — ASP), которая реализована с использованием ISAPI для получения большей производительности прикладного программирования Web-сервера. ASP представляет собой сценарное окружение для создания HTML-страниц, возвращаемых Web-клиенту. Страница ASP может представлять собой смесь HTML-кода и команд сценария. Те же языки сценариев (JavaScript и VBScript), которые применяются на стороне клиента, могут быть использованы и на сервере. Таким образом, написание ASP представляет собой очень простую задачу. Главный недостаток использования языков сценариев заключается в низкой производительности, связанной с тем, что сценарии не компилируются, а интерпретируются, и потому они значительно медленнее скомпилированного кода. Следовательно, непосредственно с помощью сценариев можно решать только относительно простые задачи. Когда же на первое место встают вопросы скорости, сценарий должен вызывать скомпилированный код, и сделано это может быть с помощью компонентов СОМ. Microsoft Transaction Server и СОМ+ Microsoft Transaction Server (MTS) является решением NT 4.0, служащим для упрощения разработки компонентов среднего уровня. MTS представляет собой дополнение к NT 4.0, входящее в состав NT 4.0 Options Pack. В Windows 2000 MTS перестает существовать как отдельное программное обеспечение, а его свойства и возможности интегрируются в СОМ, которая с этого момента становится СОМ+. В части III, "Windows DNA и СОМ+" этой книги мы детально рассмотрим СОМ+, а в этом разделе только бегло ознакомимся с MTS. Средний уровень наиболее сложен для программиста. На этом уровне представления программа, по сути, заботится только об одном пользователе, и основная сложность состоит в программировании графического интерфейса пользователя, для чего имеется обширный инструментарий (например, Visual Basic или MFC), упрощающий эту работу. Как и с уровнем представления, с уровнем доступа к данным также возникают определенные сложности, но как и в случае уровня представления, современные системы баз данных очень мощны и предоставляют программисту богатый инструментарий. Основную сложность на уровне доступа к данным представляет обеспечение унифицированного доступа ко многим различным типам данных — вопрос, который будет обсужден нами позже в этой главе. Средний уровень — также не без сложностей, решение которых предоставляет MTS/COM+. Первый вопрос — работа с транзакциями (которые и дали имя MTS). Транзакции являются фундаментальной структурной концепцией, которая обеспечивает разработку сложных многопользовательских приложений для работы с данными. Главное свойство транзакций заключается в их атомарности. Транзакция означает все или ничего. Если вы переводите деньги с одного счета на другой, вряд
ли вам понравится, если они будут сняты с вашего счета, но не поступят на другой счет. СУБД поддерживают транзакции много лет, так что можно подумать, что транзакции представляют собой свойство третьего уровня. Предположим, вы заказываете продукт через Web и заполняете форму за несколько различных шагов, при каждом из которых происходит обращение к различным компьютерным системам. При этом мы оказываемся в царстве распределенных транзакций. В системах клиент/сервер клиент может быть очень сложным — он не только обеспечивает интерфейс пользователя, но и обращается к нескольким базам данных. Посмотрим на то, как такая система может быть построена с использованием трехуровневой архитектуры (рис. 2.9). Серверная часть приложения реализована на среднем уровне с помощью трех бизнес-объектов — заказа (Order), оформления счета (Billing) и доставки товара (Shipping), — которые запускаются в транзакции. Таким образом, если заказ передается объекту Shipping, но объект Billing выясняет, что кредитная карточка покупателя исчерпана, заказ не будет доставлен, и в базу данных доставки товара никакие изменения внесены не будут. Все эти объекты работают под управлением MTS (или СОМ+). Рассмотрим некоторые предоставляемые MTS сервисы, и выясним, почему они так необходимы (или почему они должны быть реализованы программистом при отсутствии MTS). Клиент HTTP MTS IIS Заказ Оформление счета Доставка товара Данные Данные Рис. 2.9. Трехуровневое приложение для заказа товаров
Первой является сама транзакция. Если у нас была бы только одна база данных, то отвечать за работу транзакции могла бы соответствующая СУБД. Однако в приведенном выше примере используются две базы данных, которые к тому же могут быть расположены на разных компьютерах. Транзакции с участием различных баз данных реализуются через координатор распределенных транзакций Microsoft (Microsoft Distributed Transaction Coordinator — MSDTC). Бизнес-объекты могут обращаться к MSDTC непосредственно, но это приведет к некоторому усложнению используемого кода. MTS же делает задачу обращения к MSDTC очень простой. Вы декларируете, что объект Order "требует транзакцию", и передаете работу вспомогательным объектам — Billing и Shipping. Каждый из них декларирован как "поддерживающий транзакции". Это означает, что если они вызваны компонентом в транзакции, то также будут работать в пределах той же транзакции и успешной или неудачной будет вся обработка заказа объектом Order. Это потребует небольшого количества кода, к тому же достаточно тривиального. Если объект успешно выполняет свою работу, он вызывает SetComplete; если выполнение неудачно, вызывается SetAbort. Если все объекты- участники вызвали SetComplete, значит, транзакция выполнена успешно; в противном случае — транзакция неудачна. Следующий вопрос, в решении которого участвует MTS, — управление согласованностью. Может случиться, что множество клиентов одновременно попытаются разместить свои заказы и приложению придется иметь дело с возможными конфли к- тами. В случае приложений клиент/сервер вопросы согласованности многопользовательского обращения к базе данных решаются на уровне СУБД. Однако в нашем случае клиент не обращается к базе данных непосредственно, а делает это через бизнес- объекты. Таким образом, вопросы согласованности должны решаться на уровне бизнес-объектов, что и делается с помощью MTS. Еще один важный вопрос — управление подключениями. Это та область, в которой масштабирование систем клиент/сервер невозможно. Подключения к базе данных представляют собой ограниченный ресурс, который при попытке одновременного подключения множества пользователей быстро исчерпывается. В случае трехуровневого приложения клиенты могут подключаться к компоненту Order, но MTS "активизирует" экземпляр объекта только тогда, когда это необходимо. MTS также управляет пулом подключений к базам данных, которые раздаются активным объектам, нуждающимся в них. При деактивации объекта подключение возвращается в пул. Приложение при этом должно вызывать по окончании работы SetComplete или SetAbort для деактивации объекта. Microsoft Message Queue Последняя ключевая часть технологии Microsoft для среднего уровня — очередь сообщений Microsoft (Microsoft Message Queue, MSMQ). Так же, как и MTS, MSMQ представляет собой часть NT 4.0 Option Pack. В отличие от MTS, MSMQ остается отдельной единицей в Windows 2000, хотя и является ее стандартной частью (подобно IIS). MSMQ обеспечивает асинхронную однонаправленную связь, ориентированную на сообщения. MSMQ можно рассматривать как протокол связи, альтернативный DCOM и HTTP, с весьма отличающимися характеристиками. DCOM представляет собой ориентированный на соединения протокол с сохранением состояния. HTTP является протоколом без сохранения состояния. Как DCOM, так и HTTP — синхронные протоколы, которые могут возвращать результат. Это означает, что работа клиента блокируется до получения ответа от сервера. Это также означает, что для успешной работы требуется, чтобы работали и клиент, и сервер. MSMQ же представляет собой асинхронный протокол, так что вызов сервиса осуществляется помещением сообщения в очередь. При этом происходит немедленный возврат из вызова с указанием того, был
он успешен или нет. "Успешность" в данном случае означает лишь, что сообщение было благополучно помещено в очереди. Клиент при этом не блокируется и может продолжать работу. Заметим, что сервер при этом не обязан быть запущен — позже, когда сервер приступит к работе, он просмотрит соответствующую очередь сообщений. И, наконец, сообщения однонаправлены — они не возвращают результат, поскольку при помещении сообщения в очередь сервер не вызывается. При необходимости возврата результата сервер может отправить сообщение клиенту. Имеется множество бизнес-ситуаций, в которых применима модель асинхронных очередей. Вновь рассмотрим приложение обработки заказов, показанное на рис. 2.9. В нем может потребоваться определенное время для получения результата обработки от приложения Shipping. Поэтому лучшим решением может стать передача запроса через очередь сообщений, а позже, если возникнут проблемы, приложение Shipping сможет переслать сообщение приложению Order. Заметьте, я стал упоминать об Order и Shipping, как о "приложениях". Другое приятное свойство MSMQ заключается в облегчении интеграции приложений. MSMQ способна к взаимодействию с другими системами очередей сообщений, например с такими, как IBM MQSeries. MSMQ можно рассматривать как ориентированное на использование сообщений промежуточное программное сообщение от Microsoft (message-oriented middleware — MOM), являющееся популярным механизмом интеграции сообщений. В СОМ+ реализована технология, называемая "queued components" ("компоненты, которые могут быть поставлены в очередь"), которая упрощает использование MSMQ. Если все методы интерфейса СОМ не возвращают результат, то такому интерфейсу может быть присвоен атрибут "queued". Тогда вызов этого интерфейса клиентом будет автоматически направлен через создаваемую СОМ+ очередь. Помимо устранения необходимости вызовов API к MSMQ, работа с компонентами, которые могут быть поставлены в очередь, позволяет избежать необходимости размещать параметры в блоке сообщения. Уровень доступа к данным Microsoft предоставляет множество разнообразных технологий для поддержки уровня доступа к данным в трехуровневых приложениях. Стратегическим СУБД- продуктом фирмы Microsoft является SQL Server (текущая версия 7.0). В отличие от предыдущих версий SQL Server, работающих только на машинах под управлением NT, SQL Server 7.0 может работать на платформе Windows 95/98. Однако SQL Server работает только на платформах Windows, в отличие от таких СУБД, как, например, Oracle, которые работают на разных платформах. SQL Server оптимизирован для полного использования предоставляемых Win32 возможностей. Например, SQL Server непосредственно использует потоки Win32 и, соответственно, не должен применять специализированный пакет работы с потоками в составе СУБД (как в случае других СУБД, способных к многопоточной работе даже под управлением тех операционных систем, в которых многопоточность на системном уровне не поддерживается). В примерах этой книги в качестве СУБД мы будем использовать SQL Server 7.0 (небольшое руководство по работе с ним вы найдете в главе 18, "SQL Server и ADO"). Однако (что более важно, чем разработка собственной СУБД) Microsoft определила ряд важных интерфейсов доступа к данным, широко используемых в программной индустрии. Первым таким интерфейсом был ODBC, вкратце описанный в главе 1, "Что такое COM4-". ODBC представляет собой интерфейс, написанный на языке программирования С, который несколько сложен для кодирования. Для программистов на C++ Microsoft разработала несколько специализированных классов в составе MFC, которые в некоторых ситуациях упрощают работу. Для программистов на Visual
Basic Microsoft включила возможность доступа к базе данных Jet (используемой Access) с помощью технологии объектов доступа к данным (Data Access Objects — DAO). Кроме того, будучи сложным для программирования без дополнительного уровня, ODBC страдает от ограничений, связанных с моделью реляционных баз данных. Это приводит к тому, что если работа с реляционными базами данных с применением ODBC достаточно проста, то, например, написание ODBC-драйвера для нереляционной базы данных представляет собой очень сложную задачу. Фундаментальной современной технологией доступа к данным, разработанной Microsoft, является OLE DB — очень гибкий низкоуровневый интерфейс СОМ. Хотя первоначально провайдер данных OLE DB работал через ODBC, сейчас имеется ряд провайдеров OLE DB для многих СУБД, включая SQL Server и Oracle. OLE DB- провайдер, разработанный для использования с конкретной СУБД, представляет собой самый быстрый объектно-ориентированный интерфейс базы данных. Над OLE DB находится слой объектов данных ActiveX (ActiveX Data Objects — ADO), обеспечивающий очень простую в использовании объектную модель. ADO может использоваться в программах, разработанных как на Visual Basic, так и на C++. Объектная модель ADO в чем-то схожа с DAO, но более усовершенствованная, чем последняя. Самое важное свойство технологии Microsoft доступа к данным состоит в упрощении доступа к любым источникам данных (независимо от того, реляционная или нет используемая база данных) с помощью одного и того же интерфейса. Microsoft называет это свойство унифицированным доступом к данным (Uniform Data Access — UDA). Резюме В этой главе мы говорили о трехуровневых компьютерных системах и архитектуре Windows DNA, обеспечивающей удобный способ организации различных технологий, которые могут использоваться для реализации трехуровневых разработок. Рассмотрели три уровня: уровень представления, уровень бизнес-логики (средний уровень) и уровень доступа к данным. Дали определения таким понятиям, как "бедный клиент" и "богатый клиент". Богатый клиент может полностью использовать предоставляемые Win32 возможности и подключаться к среднему уровню с помощью DCOM. Бедный клиент может работать в любой системе, оснащенной современным Web-броузером, используя для соединения с Web-сервером протокол HTTP. Кроме того, мы приподняли завесу и узнали, что кроется за такими аббревиатурами, как IIS, MTS, MSMQ, СОМ и СОМ+. IIS представляет собой полномасштабный Web-сервер фирмы Microsoft, являющийся стандартной частью NT Server и Windows 2000. MTS является частью NT 4.0 Option Pack и обеспечивает множество функций для упрощения разработки среднего уровня распределенного приложения, включая простую поддержку транзакций, прозрачную обработку согласованности и "многопользовательности" и поддержку пула подключений к базам данных. В Windows 2000 MTS как отдельный продукт отсутствует и объединен с СОМ+. Третья важная технология среднего уровня — MSMQ, которая упрощает разработку приложений, основанных на сообщениях, путем реализации однонаправленного асинхронного интерфейса между клиентом и сервером. Для уровня доступа к данным Microsoft поставляет свою собственную СУБД, SQL Server, и определяет важные интерфейсы доступа к данным, такие как OLE DB и ADO, поддерживаемые многими производителями. Кроме технологий для трех уровней, Windows DNA включает "склеивающие" технологии СОМ/СОМ+ и общие сервисы, такие как сетевые сервисы, безопасность и активные каталоги. В следующей главе мы поговорим о настройке полигона для изучения СОМ+, а затем погрузимся в детали множества технологий, о которых было вскользь упомянуто в этой главе, ориентируясь, конечно же, на СОМ и СОМ+.
Глава 3 Полигон для испытаний Windows DNA Эта глава поможет вам настроить полигон для изучения Windows DNA. Поскольку основной темой книги является СОМ+, в первую очередь мы будем работать с Windows 2000 — новой версией операционной системы Windows. Учебные примеры в этой книге освещают различные стороны технологии распределенных приложений, в них используется самый разный инструментарий. Поскольку для иллюстрации распределенных приложений требуется наличие сети, выполнение всех примеров книги не является абсолютно обязательным условием. Хотя вы не обязаны по прочтении этой главы полностью завершить настройку вашей системы, тем не менее рекомендуется приступить к ней именно сейчас, чтобы при возникновении каких-либо проблем у вас имелся запас времени для их разрешения. Указания по настройке некоторых отдельных продуктов, таких, например, как SQL Server, Internet Information Services или MSMQ, будут приведены в последующих главах, по мере необходимости в них. Кроме определения различных свойств операционных систем и инструментария, которые потребуются нам в дальнейшем, в этой главе содержится множество простых тестов, которые позволят вам убедиться в правильности настройки. Хотя для иллюстрации возможностей различных распределенных технологий рекомендуется сеть из трех узлов, почти все примеры могут быть выполнены в сети, состоящей из двух узлов; на самом же деле большая часть работы может быть проведена даже на отдельном компьютере. Поэтому для изучения книги не обязательно иметь полностью оснащенную лабораторию. Общая конфигурация Главное требование состоит в наличии хотя бы одного компьютера с операционной системой Windows 2000. Два таких компьютера — еще лучше, поскольку тогда вы сможете работать с распределенными приложениями. Работоспособность Windows 2000 существенно повышается, если эта операционная система установлена на всех компьютерах предприятия. Вы не сможете протестировать возможности распределенной работы СОМ+, не имея в наличии двух компьютеров под управлением Windows 2000. В то же время одно из основных архитектурных свойств СОМ/СОМ+ — "прозрачность размещения". Это означает, что СОМ- клиент вызывает сервер совершенно одинаково, независимо от того, является ли сервер запущенным (в контексте процесса) как отдельное приложение на том же компьютере или на другой машине сети. В результате
вы сможете создать приложение, имеющее три логических уровня, несмотря на то что все они будут расположены на одной машине. Таком образом, основные концепции книги могут быть изучены на одном компьютере под управлением Windows 2000, при условии, что на нем будут установлены все необходимые компоненты и инструментарий. Хотя прозрачность размещения и означает, что вы не должны перестраивать компоненты СОМ для удаленного их запуска, все же компоненты СОМ, не созданные для распределенной работы, хорошо работать не будут. Хотя DCOM позволяет вам иметь удаленные компоненты, СОМ+ обеспечивает более общий механизм для использования приложений СОМ+ удаленно. Этот процесс может быть существенно автоматизирован с помощью нового инсталлятора Windows 2000. Но если даже у вас нет второго компьютера, вы вполне можете прочесть части книги, посвященные распределенным технологиям СОМ и СОМ+, не выполняя соответствующих примеров. Если у вас есть доступ к третьему компьютеру сети, то вы сможете разместить базу данных на одном компьютере, бизнес-логику -— на втором, а клиент — на третьем, создавая при этом полностью распределенную систему. Если на третьем компьютере установлена NT 4.0, то вы сможете изучить возможности взаимодействия Windows 2000 и NT 4.O. Возможно, вам более удобно работать со знакомыми приложениями под управлением знакомой операционной системы. В последующих разделах этой главы мы рассмотрим детали установки и настройки необходимого программного обеспечения. Я не привожу здесь детальных инструкций по установке Windows 2000 и ее различных компонент. Такая информация может очень быстро стать неактуальной в связи с выходом новых версий Windows. Вы должны в первую очередь полагаться на документацию, поставляемую с программным продуктом; моя же задача — помочь разобраться в логической структуре продукта. Итак, начнем мы с небольшого путеводителя, который обеспечит нас краткой необходимой информацией о рекомендуемых конфигурациях, которые будут верой и правдой служить вам полигоном для испытаний распределенных технологий. Здесь же вы познакомитесь с минимальными конфигурациями, которые позволят вам выполнить все упражнения, рассматриваемые в этой книге, — пусть и без особого комфорта. (Не все перечисленное в этой главе необходимо устанавливать прямо сейчас.) Затем рассмотрим некоторые специальные вопросы, связанные с установкой и работой Windows 2000 Professional, Windows 2000 Server, сетями и активными каталогами. После этого будут описаны инструменты разработки и некоторые тестовые программы. И, наконец, вкратце будет рассмотрена модель СОМ+. Путеводитель Здесь мы приведем рекомендуемую конфигурацию "полигона" из трех компьютеров. Как уже упоминалось, вы можете обойтись и двумя машинами, и даже одним компьютером (все необходимое можно установить на нем), однако в последнем случае вы не сможете взглянуть на распределенные технологии "живьем", включая независимый клиент MSMQ, хотя будете иметь возможность создавать и тестировать логические трехуровневые приложения. Мне представляется удобной и оправданной установка на каждый компьютер Visual Studio, так как при этом на каждом из них у вас будут все необходимые DLL и вы сможете на каждом отлаживать приложения. Если вы установите SQL Server 7.0 на каждый компьютер, то сможете работать с распределенными транзакциями. Вы можете начать работать с компьютером №1 при изучении части II, "Основы СОМ" этой книги, а затем, работая с частью III, "Windows DNA и СОМ+", переключиться на компьютер №2.
Компьютер №1: Windows 2000 1. Windows 2000 Professional. 2. Visual Studio. 3. SQL Server 7.0. 4. MSMQ Independent Client. Компьютер №2: Windows 2000 Domain Controller 1. Windows 2000 Server. 2. Active Directory. 3. Domain Controller. 4. Visual Studio. 5. Platform SDK. 6. SQL Server 7.0. 7. Internet Information Services 5.0. 8. MSMQ Server. Компьютер №3 1. NT 4.0 Server или Workstation или Windows 2000 Professional. 2. Ваши стандартные приложения. 3. Visual Studio. 4. SQL Server 7.0. Во всех приведенных выше списках не отображены Internet Explorer и сетевое обеспечение, которые интегрированы в Windows NT и Windows 2000. Мы будем использовать и то, и другое. График работ Нет необходимости устанавливать все программное обеспечение сразу, до работы с частью III, "Windows DNA и СОМ+" этой книги — установка всего указанного программного обеспечения необходима только в том случае, если вы хотите испытать все возможности СОМ+. В этой главе мы рассмотрим только базовое программное обеспечение, выделенное полужирным шрифтом в списке, относящемся к компьютеру №1. В этой же главе мы рассмотрим и базовое программное обеспечение для компьютера №2, но устанавливать вам его пока нет необходимости. Дополнительно вопросы настройки того или иного продукта будут рассмотрены в тот момент, когда этот продукт потребуется для работы. В книге найдется несколько мест, где примеры приведены вне последовательности изучения (как, например, Web-броузер в главе 1, "Что такое СОМ+"), но эти примеры призваны только обострить ваш интерес и не предназначены для детального выполнения и исследования. Работать с ними можно только при наличии достаточного опыта, позволяющего выполнить упражнение на основе небольшого количества указаний.
Глава 3, "Полигон для испытаний Windows DNA " В этой главе мы должны настроить как минимум одну машину с Windows 2000 и Visual Studio. Такого рабочего места будет достаточно до момента изучения главы 13, "Многопоточность в СОМ", за исключением главы 10, "Введение в DCOM". Хотя это и понадобится не сразу, я бы предложил заодно настроить и второй компьютер с Windows 2000, если это позволяют ваши ресурсы. На этой второй машине следует установить Windows 2000 Server и Active Directory и настроить ее в качестве контроллера домена. Неплохо также установить на ней Platform SDK, в котором содержится много документации, отражающей последние изменения, и примеров программ, включая документацию по СОМ+. Глава 10, "Введение в DCOM" В этой главе нам понадобится второй компьютер и сеть. Как отмечалось ранее, рекомендуемая операционная система для второго компьютера — Windows 2000 Server. Глава 17, "Windows 2000 и безопасность СОМ+ " К этому моменту у нас должен быть второй компьютер с работающей на нем Windows 2000 Server (или Advanced Server). Нам потребуется активный каталог и работа этого компьютера в роли контроллера домена. Затем вам придется сыграть роль администратора и поработать с учетными записями пользователей и настройками безопасности. Если у вас имеется только один компьютер с Windows 2000 Professional, вам придется заменить ее Windows 2000 Server. Глава 18, "SQL Server и ADO" В этой главе вам придется настроить SQL Server на одной или нескольких машинах. В этой и последующих двух главах упор будет сделан на базах данных. Глава 20, "Использование СОМ+ в Web-приложениях " В этой главе вам придется использовать Internet Information Services 5.0 и, конечно же, Internet Explorer. Глава 21, "Microsoft Message Queue" В этой главе вы настроите сервер MSMQ на вашей машине под управлением Windows 2000 Server. Если вы хотите рассмотреть, как использовать MSMQ для передачи сообщений по сети, вам придется дополнительно установить независимый клиент MSMQ на машине под управлением Windows 2000 Professional. Windows 2000 Вашей первостепенной задачей является установка операционной системы Windows 2000, которая поставляется в четырех версиях: Professional, Server, Advanced Server и Datacenter Server. Я рекомендую воспользоваться Professional и Server как версиями, которые позволят вам изучить весь материал, приведенный в данной книге, не перегружая компьютеры излишествами. Предлагаю начать с Windows 2000 Professional. После того как вы установите эту операционную систему и поработаете с ней, вам будет гораздо проще установить Windows 2000 Server на второй машине и обеспечить между ними сетевое соединение.
Требования к аппаратному обеспечению Самое главное для нормальной работы Windows 2000 — достаточное количество памяти. Для работы с примерами этой книги я рекомендую иметь как минимум 128 Мбайт оперативной памяти. Неплохо также иметь запас места на диске. 4 Гбайт будет достаточно, хотя при желании можно выкрутиться и с двухгигабайтным винчестером (4 Гбайта позволят вам установить на диск MSDN). Рассмотрите также возможность компрессии раздела диска (требует NTFS). Процессор должен работать на частоте не ниже 200 МГц, но, конечно, чем более высокоскоростной процессор, тем лучше. Windows 2000 Professional Windows 2000 Professional — самая "облегченная" версия Windows 2000, предлагаемая в качестве замены Windows 95/98 и Windows NT Workstation. Эта операционная система представляет собой полностью адекватную среду для программирования и тестирования СОМ-программ и изучения множества сервисов СОМ+. Установив Windows 2000 Professional, вы увидите при ее загрузке экран приветствия, показанный на рис. 3.1. Этот экран будет появляться все время, пока вы не отмените опцию "Show this screen at startup". Рис. 3.1. Экран приветствия Windows 2000 Professional Если вы — новичок в Windows, то можете сделать экскурсию, щелкнув мышью на опции "Discover Windows". Если же вы знакомы с Windows, но новичок в Windows 2000, то можете просто бегло ознакомиться с новыми возможностями операционной системы. Знакомство с Windows 2000 Professional Если вы знакомы с предыдущими версиями Windows NT, то вначале вас может смутить новое размещение различных утилит и инструментария. Большое количество разнообразных инструментов находится в папке под названием Administrative Tools, показанной на рис. 3.2.
Рис. 3.2. Папка Administrative Tools может быть найдена в Control Panel Еще одной особенностью Windows 2000 является повсеместное использование консоли управления Microsoft (Microsoft Management Console — MMC). Многие административные инструменты встроены в ММС и больше не являются автономными приложениями. Так, все показанные в папке Administrative Tools инструменты автономными приложениями не являются. В части III, "Windows DNA и СОМ+" мы будем интенсивно использовать инструмент Component Services (или, как его часто называют, СОМ+ Explorer). Многие задачи могут быть выполнены с помощью встроенного инструмента Computer Management (на рис. 3.3 показан Disk Administrator из состава Computer Management). Рис. 3.3. Computer Management позволяет решать множество задач, включая администрирование дисков В целом все, что вам надо, чтобы обойти всю Windows 2000 Professional, — это немного поэкспериментировать. Как гласит старая программистская мудрость, "если ничто не помогает — обратись к документации", и к вашим услугам — большая справочная система, доступная по команде Starts Help.
Windows 2000 Server Эта часть полигона понадобится вам немного позже, поэтому устанавливать ее немедленно не обязательно. В конечном счете вам потребуется Windows 2000 Server. Даже если в вашем распоряжении — только один компьютер, вам придется заменить установленную на нем Windows 2000 Professional системой Windows 2000 Server. Если же у вас есть возможность выделить для работы два компьютера, установите Windows 2000 Server на втором из них — через какое-то время он станет для вас первым и основным. Если вы установили Windows 2000 Server на втором компьютере, то должны завершить работу с этим разделом до того, как приступите к главе 10, "Введение в DCOM", в которой он будет нужен. Если у вас только один компьютер — можно подождать с переустановкой системы до главы 17, "Windows 2000 и безопасность СОМ+", когда вам понадобится служба активного каталога. При загрузке Windows 2000 Server вы увидите экран, озаглавленный Windows 2000 Configure Your Server. Этот экран будет появляться постоянно, пока вы не отмените опцию Show this screen at startup. Обратиться при необходимости к этому экрану вы сможете, воспользовавшись командой Start^Administrative Tools^Configure Your Server. На этом экране будет предоставлен удобный интерфейс ко множеству функций настройки сервера (впрочем, все настройки могут быть сделаны и непосредственно). Мне бы не хотелось приводить здесь руководство по использованию Windows 2000 Server — уж очень обширна эта тема. Вам должен помочь, в первую очередь, ваш собственный опыт работы с более ранними версиями NT Server. Кроме того, потратьте некоторое время на ознакомление со справочной системой, а также можете несколько раз переустановить Windows 2000 Server для получения большей практики в этом вопросе. При работе с бета-версией мне очень помогла книга издательства Microsoft Press Microsoft Windows 2000 Beta Training Kit. Я думаю, что вы сможете найти любое другое подобное издание, освещающее работу в Windows 2000. Сетевые возможности Для иллюстрации распределенной работы СОМ+ вам, конечно, потребуется сеть. Вы обязательно должны установить в своей сети TCP/IP. Если вы работаете на компьютерах, подключенных к сети, то в этом может помочь ваш системный администратор. Если же вы создаете собственную маленькую сеть, то должны будете сами администрировать ее, назначив адреса своим машинам. Так, я использовал адреса 131.107.2.200, 131.107.2.201 и 131.107.2.202 с маской подсети 255.255.0.0. Если вы не подключены к Internet, выбор адресов может быть произволен. После установки сети вы должны проверить связь между компьютерами, "пингуя" их. Вызовите командную консоль (если вы не можете найти ее в меню Start, введите cmd в приглашении Run). Затем введите команду ping с именем компьютера или его IP-адресом в качестве параметра (рис. 3.4). Domain Name System (DNS) Система доменных имен (Domain Name System — DNS) представляет собой распределенную базу данных, реализованную над TCP/IP и использующуюся для перевода имен компьютеров в числовые IP-адреса. Поскольку DNS использует то же соглашение по именованию, которое применяется и в Internet, то, если у вас установлен DNS, вы можете подключиться к серверам вашей локальной сети, применив такие же имена, что и в случае подключения к серверу в Internet.
Рис. 3.4. "Пингование" компьютеров в сети Имена в DNS создаются в виде иерархии, с точкой, разделяющей уровни иерархии. Вершина иерархии представляет собой корневой домен, представленный точкой. Домен верхнего уровня: com — для коммерческих организаций, gov — для правительственных организаций, edu — для учебных заведений и др. Сюда же входят коды стран, такие, например, как иа (Украина) или ru (Россия). Домены второго уровня являются уточнением доменов высшего уровня и, как правило, представляют собой название организации, например: microsoft.com или harvard.edu. На нижнем уровне иерархии находятся имена узлов, которые определяют конкретную машину в Internet или локальной сети. Вы можете установить DNS с помощью выпадающего списка Networking, расположенного на левой панели экрана Configure Your Server, выбирая в списке и щелкая на DNS. В процессе инсталляции вам придется указать имя вашего домена и назначить IP-адрес DNS-серверу, который должен быть вашим основным компьютером под управлением Windows 2000 Server. Если вы перейдете непосредственно к мастеру установки Active Directory, то он должен сам установить DNS на вашем компьютере. Active Directory Windows 2000 Server предоставляет Active Directory в качестве сервиса каталогов, который идентифицирует все ресурсы в сети. Active Directory имеет также программные службы, которые делают информацию, содержащуюся в каталоге, доступной пользователям и приложениям. Ресурсы, хранящиеся в каталоге, известны как объекты, примерами которых могут служить пользователи, группы, принтеры, базы данных и др. Компьютеры и другие ресурсы в сети организованы в домены (domain). Домен представляет часть ресурсов всего каталога и управляется одним или несколькими компьютерами под управлением Windows 2000, известными как контроллеры доменов (domain controller). Все контроллеры определенного домена равноправны, и каждый из них имеет копию части каталога для данного домена. Домен представляет область безопасности. Так, например, пользовательские учетные записи устанавливаются на основе доменов. Когда пользователь успешно подключается к некоторому контроллеру домена, он получает доступ ко всем ресурсам, к которым дает право доступа его учетная запись. Active Directory обеспечивают единое администрирование всех ресурсов в сети. Все домены связаны между собой, и администратор может управлять всеми ресурсами с помощью одного входа в систему. До появления описываемого сервиса для управле-
ния теми или иными ресурсами администратор должен был иметь отдельные учетные записи для каждого домена, что сильно затрудняло администрирование в большом предприятии со множеством доменов. Active Directory используют DNS в качестве сервиса доменных имен. Active Directory использует стандартный протокол LDAP (Lightweight Directory Access Protocol), который позволяет Active Directory взаимодействовать с другими службами каталогов, такими, например, как Novell's Network Directory Service (NDS). Active Directory поддерживает также HTTP, который позволяет представить любой объект сети на HTML- странице в Web-броузере. Логическая структура активных каталогов основана на доменах; физическая же структура базируется на узлах (site), которые могут рассматриваться как одна или несколько IP-подсетей. Если вы администрируете маленькую тестовую сеть, то, как правило, все компьютеры такой сети принадлежат одной подсети и входят в состав одного узла Active Directory. Установка Active Directory Использование Active Directory в Windows 2000 делает процесс настройки контроллера домена немного отличающимся от такового в NT 4.O. Как и в случае NT 4.0, вам необходима серверная версия операционной системы. Однако, в отличие от NT 4.0, выбор компьютера для функционирования в качестве контроллера домена производится после установки операционной системы. Компьютер становится контроллером домена после установки на нем сервиса Active Directory. После этого вы должны выбрать или добавить контроллер для существующего домена (или создать первый контроллер для нового домена). Установив Active Directory, вы сделаете ваш обычный сервер контроллером домена. Запустить программу-мастер установки Active Directory можно, выбрав Active Directory на экране Configure Your Server. Присоединение к домену Установив Active Directory на вашем основном компьютере и сделав его контроллером домена, вы сможете присоединить другие компьютеры сети в этому домену. Этот процесс выполняется в два шага. Вначале вам надо создать новые объекты- компьютеры в активном каталоге для присоединяемых компьютеров. Это — операция, выполняемая на компьютере, служащем контроллером домена. Затем для присоединения к домену вам следует изменить свойства компьютеров. Для создания новых объектов-компьютеров вызовите Active Directory Users and Computers (с помощью команды меню Start^Programs^Administrative Tools). Откройте дерево просмотра вашего домена, выберите Computers и щелкните правой кнопкой мыши на Computers. Выберите в контекстном меню New^>Computer и введите имя компьютера, присоединяемого к домену. Если хотите, вы можете также изменить группу пользователей, имеющих права на присоединение компьютера к домену. По умолчанию используется группа Domain Admins. Теперь перейдем к другой машине. В случае компьютера под управлением NT 4.0 щелкните правой кнопкой мыши на Network Neighborhood (на рабочем столе), выберите меню Properties, затем — вкладку Identification и щелкните на Change. На компьютере под управлением Windows 2000 щелкните правой кнопкой мыши на пиктограмме My Computer, выберите в контекстном меню пункт Properties, затем — вкладку Network Identification и щелкните на кнопке Advanced. После этого вы можете с помощью кнопок-переключателей сделать компьютер членом домена, а не рабочей группы. Введите имя домена. После этого на экране появится вопрос об имени пользователя и пароле для присоединения компьютера к домену. Если вы оставляете настройки по умолча-
нию, то можете ввести имя администратора домена (Administrator — если, конечно, вы не изменяли его) и пароль. Новые установки вступят в силу после перезагрузки компьютера. Затем при входе в систему в соответствующем диалоговом окне вы должны будете выбирать учетную запись рабочей группы (локальная машина) или домена. Если вы входите в машину, используя учетную запись домена, не удивляйтесь, если все установки рабочего стола, ярлыки и прочее окажутся измененными, так как учетная запись домена представляет собой нового пользователя, а если настройка производится для каждого пользователя в отдельности, то новый пользователь получит настройки по умолчанию. Управление пользователями Установив активный каталог, вы сможете использовать его для управления всеми ресурсами сети, включая пользовательские учетные записи. Получить доступ к функциям управления активным каталогом можно с помощью экрана Configure Your Server, но обычно типичные действия при работе с этим экраном сводятся к согласию с настройками по умолчанию и закрытию экрана. После этого доступ к различным службам активного каталога вы получаете с помощью меню Start. Для управления учетными записями пользователей воспользуйтесь командой Start=>Administrative Tools=>Active Directory Users and Computers (рис. 3.5). Рис. 3.5. Управление учетными записями пользователей с помощью Active Directory Заметьте, что в дереве, представленном в левой части диалогового окна, отображены домены, которыми может управлять администратор. Enterprise Administrator может управлять всеми доменами каталога. В примере, приведенном на рис. 3.5, показан только один домен — oi.com. Для добавления пользователя вы можете выбрать Users в левой части диалогового окна, а затем щелкнуть на нем правой кнопкой мыши и выбрать в контекстном меню команду New^User. Затем в появившемся диалоговом окне Create New Object (User) вам следует ввести всю необходимую информацию о пользователе.
Средства разработки Ядром комплекта разработки Microsoft является Visual Studio (версия 6.0 или более поздняя). Нам потребуются две части из общего комплекта: Visual C++ и Visual Basic. Кроме того, при желании вы можете установить Visual InterDev в качестве редактора для HTML и ASP-файлов. Я бы рекомендовал Enterprise-версию этих продуктов, но большинство приведенных в книге задач может быть решено с помощью Standard-версии. Время от времени Microsoft меняет понятия о той или иной версии продукта, так что за последней информацией об Visual C++, Visual Basic и Visual Studio обращайтесь на Web- узлы Microsoft: msdn.microsoft.com/vstudio, msdn.microsoft.com/visualc, msdn.microsoft.com/vbasic. Если вы занимаетесь разработкой СОМ+, то, вероятно, будете использовать ее на уровне предприятия, и вам, скорее всего, потребуются другие инструменты Microsoft, например, такие как SQL Server. Самый экономичный путь получить полную среду разработки Microsoft — воспользоваться MSDN Universal subscription (msdn.microsoft.com) (не считая, конечно, покупки набора пиратских компактов, — прим. перев.). В настоящее время Visual Studio интегрирован в MSDN Library; и оперативная справочная система теперь является MSDN. Так что не удивляйтесь, если после инсталляции Visual Studio вам будет предложено установить MSDN. Вам придется согласиться (как будто у вас есть выбор!) и инсталлировать MSDN. Если у вас большой винчестер (хорошо, чтобы это было так — ведь мы работаем с продуктами Microsoft), ставьте все на него, чтобы затем при работе не заниматься постоянной сменой компакт-дисков в дисководе. Хотя в книге широко используются и Visual Basic, и Visual C++, тем не менее, если вы знакомы только с одним из них, рекомендуем прочитать всю книгу, она вам будет полезна. Если вы — программист на C++, я бы советовал вам познакомиться с Visual Basic — языком исключительно простым и необременительным для головного мозга. Спрячьте тяжелую амуницию C++ до тех времен, когда она вам действительно понадобится. Если уж вы установили Visual Studio, стоит построить и запустить маленький пример. В следующих разделах мы займемся именно этим — и во время работы я немного расскажу вам о некоторых концепциях Visual Studio. Строить или не строить — вот в чем вопрос... Покупая программный пакет, вы приобретаете бинарный код. Когда вы работаете с примерами разработок, то обычно имеете исходный код. Если при этом имеются и бинарные файлы (DLL, EXE) — должны ли вы создавать их с нуля, с исходного кода? Я бы рекомендовал строить все с нуля, так как при использовании готового бинарного кода могут "всплыть" вопросы совместимости с вашим текущим окружением (различные версии библиотек и тому подобные проблемы). Несовместимость возможна даже при использовании книг, подобной этой, в которой все примеры испытывались с применением бета-версий программного обеспечения. Именно поэтому я стараюсь никогда не поставлять с книгами бинарные файлы — кроме, разве что, Visual Basic ActiveX DLL, где бинарные файлы требуются для того, чтобы избежать проблем с генерацией различный GUID (об этом — немного позже). Visual Basic Два основных типа проектов, которые вы можете построить с помощью Visual Basic, — ActiveX DLL (сервер) и стандартный EXE (клиент). В качестве простого примера ActiveX DLL вы можете воспользоваться проектом HelloVB.vpb, содержащимся в каталоге Chap3\HelloVB. Соберите этот проект (воспользуйтесь командой меню
Flle^Make HelloVB.dll и ответьте Yes на вопрос о том, следует ли заменить существующую DLL). В результате вы получите зарегистрированный, а следовательно, доступный клиентским программам сервер. Поскольку требуемая DLL имеется в бинарном виде, ее можно не строить, а зарегистрировать с использованием файла reg_hello.bat. В качестве примера построения стандартного ЕХЕ можно использовать проект HelloClientVB.vbp, содержащийся в каталоге Chap3\LateClientVB. Вы можете запустить проект из интегрированной среды разработки или создать ЕХЕ с помощью команды меню File^Make HelloClientVB.exe. В результате вы получите простую форму, которая позволяет использовать сервер Visual Basic (VB) или Visual C++ (VC). Щелкните на кнопке VB — при этом вы должны получить небольшое приветствие от СОМ- сервера Visual Basic, показанное на рис. 3.6. Рис. 3.6. Клиент Visual Basic вызывает VB СОМ-сервер Конечно, щелчок на кнопке VC не приведет к какому-либо результату, так как сервер Visual C++ еще не построен. В этой клиентской программе используется позднее связывание, и поэтому мы не узнаем об отсутствии сервера до тех пор, пока не попытаемся запустить его. После того как построим сервер Visual C++, мы сможем испытать программу с ранним связыванием, которая находится в каталоге Chap3\EarlyClientVB. Раннее и позднее связывание мы обсудим немного позже, в главе 11, "Автоматизация и программирование СОМ на Visual Basic". Visual C++ Два основных типа проектов, которые мы создадим с помощью Visual C++, — ATL COM AppWizard (сервер, DLL или ЕХЕ) и MFC AppWizard (клиент). У нас есть и сервер, и клиент "hello" на Visual C++. Для построения сервера откройте проект Hello.dsw в каталоге Chap3\HelloVC и воспользуйтесь командой меню Build«=>Build Hello.dll, которая зарегистрирует сервер. Для построения примера приложения MFC AppWizard откройте проект HelloClientVC.dsw, расположенный в каталоге Chap3\HelloClientVC. С помощью Microsoft Foundation Classes (MFC) вы можете создавать различные типы программ, включая многодокументный интерфейс (MDI), однодокументный интерфейс (SDI) и приложения на базе диалоговых окон. В этой книге мы будем использовать приложения на базе диалоговых окон. Постройте проект с помощью команды меню Build«=>Build... и запустите его. Вы получите форму, очень похожую на форму клиента VB. Теперь, после построения сервера VC, вы сможете вызвать оба сервера — как VC, так и VB. Если сейчас вы вернетесь к клиенту VB, то обнаружите, что он также может вызвать оба сервера. Эта маленькая программа иллюстрирует независимость СОМ от используемого языка программирования. Вы можете создавать клиент и сервер СОМ с помощью разных языков программирования и получить положительный результат.
Visual InterDev Visual InterDev является частью Visual Studio и обладает множеством возможностей, упрощающих разработку Web-приложений. В этой книге мы обсудим Web-приложения, но Visual InterDev нам не понадобится. Если он у вас установлен, вы получите определенные преимущества от использования его возможностей. Одно из них — выделение цветом синтаксиса HTML. Поскольку синтаксис HTML достаточно капризен, такое цветовое выделение (имеющееся также и в Visual C++) можно считать существенным достоинством продукта. Еще одна возможность, полезная при работе с активными страницами сервера (Active Server Page — ASP), — технология Microsoft "IntelliSense", которая предоставляет в ваше распоряжение маленькое всплывающее окно с доступными методами, параметрами, одним словом, со всем тем, что может понадобиться при написании сценариев ASP. Platform SDK Последний инструмент разработки, который следует установить, — Platform SDK. Он не понадобится вам при изучении части II, "Основы СОМ", но будет жизненно важен при рассмотрении части III, "Windows DNA и СОМ+". Platform SDK содержит полную документацию по СОМ+ и большое количество программ-примеров. Вам следует установить Platform SDK на самом сконфигурированном компьютере, где будут изучаться различные передовые возможности СОМ+, такие, например, как MSMQ. Краткий обзор СОМ+ Теперь мы познакомимся с СОМ+ поближе. Являясь частью ядра инфраструктуры Windows 2000, устанавливаемой автоматически при инсталляции операционной системы, СОМ+ не требует какой-либо специальной установки и настройки. Для администрирования приложений СОМ+ можно воспользоваться элементом Component Services (который иногда называют СОМ+ Explorer) из консоли управления. Убедитесь в том, что только что созданное приложение "Hello" работоспособно. Сейчас мы приступим к созданию приложения СОМ+ с компонентом HelloVB.dll. Вызовите Component Services с помощью команды Start^Programs^Administrative Tools^Component Services (если вы работаете с Windows 2000 Server) или Start^Settings^Control Panels Administrative Tools^Component Services (если у вас Windows 2000 Professional). Откройте окно просмотра дерева консоли и найдите в нем СОМ+ Applications, как показано на рис. 3.7. Следующий шаг состоит в создании "приложения" СОМ+ (называемом пакетом (package) в MTS), которое содержит компонент HelloVB.dll. Вначале создайте пустое приложение СОМ+, выбрав СОМ+ Applications и щелкнув на нем правой кнопки мыши, а затем выбрав в контекстном меню New^Application. В вызванной таким образом программе-мастере введите имя приложения " HelloVB" и согласитесь с настройками по умолчанию. Вы увидите, что к дереву просмотра добавится HelloVB. Выберите его, щелкните правой кнопкой мыши на Components под HelloVB и выберите в контекстном меню команду New«=>Component (или воспользуйтесь меню Action). В COM Component Install Wizard щелкните на Install new component(s). В появляющемся диалоговом окне Select files to install перейдите в каталог Chap3\HelloVB, выберите HelloVB.dll и щелкните на кнопке Open, затем — на Next и Finish. Повторите этот процесс для инсталляции Hello.dll из chap3\HelloVC\Debug. Когда все это будет сделано, на правой панели должны появиться две маленькие пиктограммы, изображающие шарики, как показано на рис. 3.8.
Рис. 3.7. Управление приложениями СОМ+ с помощью Component Services Рис. 3.8. В приложении HelloVB установлены два компонента Теперь запустим программу-клиент HelloVBClient.exe. Ее поведение должно быть идентично поведению при непосредственном вызове объекта СОМ, без его установки в качестве приложения СОМ+. В некоторых последующих примерах вы увидите, как шарик "вращается", когда работает приложение-клиент. В нашем приложении вы не сможете этого заметить, так как вызов клиентом сервера происходит очень быстро — объект создается, используется и тут же уничтожается. Размещение на удаленном компьютере Установка компонента СОМ в приложении СОМ+ — несложное дело. Но, как мы увидим в части III, "Windows DNA и СОМ+", при настройке компонента СОМ таким способом возникает ряд дополнительных возможностей. Сейчас мы продемонстрируем одну из них — простоту размещения приложения СОМ+ на удаленной машине.
Экспортируйте наше простое приложение. Выберите и щелкните правой кнопкой мыши на HelloVB в окне просмотра дерева. Выберите в контекстном меню пункт Export, и в окне вызванного приложения-мастера перейдите в каталог Deploy, расположенный в каталоге примеров книги. Назначьте имя HelloVB создаваемому файлу приложения. Обратите внимание на расширение .msi, которое используется программой установки Windows 2000 Installer. Выберите Application proxy. Мы позволяем программе-клиенту на компьютере №2 работать с сервером на компьютере №1 (рис. 3.9). Рис. 3.9. Удаленное размещение клиента Рис. 3.10. Вы можете удалить ваше приложение, как и любую другую программу Щелкните на кнопке Next, а затем — на кнопке Finish. Теперь скопируйте файлы HelloVB.msi и HelloVBClient.exe (из папки LateVBClient) в тестовый каталог на компьютере №2. Попытайтесь запустить программу-клиент на компьютере №2
(щелкните на кнопке VB). Программа работать не будет. Щелкните дважды на файле HelloVB.msi. При этом будет вызвана программа Windows 2000 Installer и прокси-код будет установлен на компьютере №2. Это — "склеивающий" СОМ-код, позволяющий клиенту вызывать удаленный сервер. Попробуйте запустить программу-клиент еще раз. Теперь все должно получиться. Так же, как и все прочее корректное программное обеспечение для Windows, ваше приложение может быть деинсталлировано. Откройте Add/Remove Programs в Control Panel, выберите ваше приложение и удалите его, как показано на рис. 3.10. Резюме В этой главе мы познакомились с настройкой полигона для изучения СОМ+ и Windows DNA. Здесь была описана полная конфигурация полигона, включающая три компьютера. В связи с "прозрачностью размещения" большинство аспектов программирования Windows DNA может быть изучено на одной машине, так что для работы с данной книгой вам не обязательно иметь полный комплект машин. При изучении части II, "Основы СОМ", в которой рассматриваются основы функционирования СОМ, вы сможете вполне работать с одним компьютером под управлением Windows 2000 Professional. При изучении главы, посвященной DCOM, понадобится второй компьютер. Полномасштабный сервер, включающий Windows 2000 Server, Active Directory и подобное программное обеспечение, не потребуется вам до тех пор, пока вы не приступите к изучению части HI, "Windows DNA и СОМ+". В этой главе также были описаны необходимые инструменты разработки, включая Visual Basic и Visual C++, и приведены простейшие тестовые примеры для них. Кроме того, был сделан краткий обзор СОМ+, включающий удаленное размещение приложения в сети. Теперь мы готовы приступить к изучению части II, "Основы СОМ", включающей описание основ СОМ, на которой базируются СОМ+ и Windows DNA.
ЧАСТЬ Основы СОМ 4 Клиенты СОМ: концепции и программирование 89 5 C++и СОМ 116 6 СОМ-серверы контекста приложения 136 7 Active Template Library 152 8 Поддержка СОМ в Visual C++ 172 9 ЕХЕ-серверы 178 10 Введение в DCOM 194 11 Автоматизация и программирование СОМ на 212 Visual Basic 12 Обработка ошибок и отладка 230 13 Многопоточность в СОМ 252 Во второй части этой книги речь пойдет о фундаментальных принципах СОМ, лежащих в основе СОМ+. Знание приведенного в этой части материала вам необходимо для того, чтобы уметь работать с СОМ+, так как каждый компонент, импортируемый в богатую среду СОМ+, должен, в первую очередь, представлять собой стандартный компонент СОМ. Для создания компонентов СОМ+ используются те же инструменты — Active Template Library или Visual Basic, — что и для разработки компонентов СОМ. {Сроме того, знание базовых концепций СОМ необходимо для понимания СОМ+. Я попытался сделать эту книгу самодостаточной, чтобы вам не пришлось получать информацию о СОМ и СОМ+ из каких-либо дополнительных источников. Все, что необходимо знать о СОМ для успешной работы с СОМ+, вы найдете в этой части книги. Если вы имеете опыт работы с
СОМ, то можете пропустить эту часть и перейти к части HI, "Windows DNA и СОМ+", лишь при необходимости возвращаясь к части II, "Основы СОМ". В этой части я попытался рассмотреть все самые важные вопросы как концептуального уровня, так и уровня непосредственного программирования. Приведенные примеры просты и конкретны, и опыт работы с ними должен помочь вам при изучении следующей части книги и более сложных примеров, рассматриваемых в ней. Здесь я использую как Visual C++, так и Visual Basic. Оба этих языка жизненно важны при разработке программ в среде Microsoft; использование обоих языков в одном проекте вполне типично. Некоторые ключевые компоненты, критичные в части производительности системы, могут быть созданы с использованием C++, а все остальные — с использованием высокопроизводительной среды Visual Basic. Таким образом, очень важно изучить, как работают с СОМ оба этих языка. В данную часть включены небольшое введение в DCOM и обсуждение многопоточ- ности в СОМ. Имеются также практические примеры обработки ошибок и отладки.
Глава 4 Клиенты СОМ: концепции и программирование Наиболее фундаментальным аспектом программирования СОМ является написание программы-клиента, которая вызывает объекты СОМ. Фактически, в среде Microsoft COM настолько распространена, что многие программисты используют СОМ, не реализуя ее. Так, Visual Basic построен на основе СОМ. При использовании различных управляющих элементов Windows в форме вы также не используете Win32 непосредственно, а прибегаете к технологии ActiveX, представляющей собой "оболочку", располагаемую поверх низкоуровневых вызовов управляющего элемента. Поэтому, даже если вы и не разрабатываете объекты СОМ, то используете вы их наверняка. Назначение этой главы состоит в пояснении модели программирования СОМ на уровне абстракции, необходимом для реализации программ — клиентов СОМ. Мы изучим, каким образом создаются программы — клиенты СОМ как с помощью Visual Basic, так и с помощью Visual C++, а также как использовать некоторые инструменты, помогающие лучшему пониманию объектов СОМ. Достаточно просто описать подход к созданию клиентов СОМ, в особенности с помощью Visual Basic (разработка клиентов с помощью Visual C++ также не представляет особой сложности). Однако дело в том, что надо не только создавать программы, но и понимать, как они работают. Моя задача состоит в том, чтобы помочь вам понять СОМ, что я и попытаюсь сделать. Вначале мы проанализируем программу-пример, а затем рассмотрим базовую терминологию и концепции. Понимание этих концепций крайне важно для работы над остальной частью книги. Здесь мы встретимся с реальным кодом, реализующим клиентов СОМ на языках Visual Basic и Visual C++, рассмотрим некоторые специфические вопросы, касающиеся программирования СОМ, например, такие как использование Unicode и BSTR для строк или способы управления памятью в СОМ. И, наконец, узнаем о роли системного реестра в хранении жизненно важной информации о серверах СОМ. Сервер банковского счета Использующийся в этой главе пример программы представляет собой простой сервер банковского счета. Сервер реализует два класса. Первый из них обеспечивает приветствие, похожее на то, которое было рассмотрено в предыдущей главе, а второй класс управляет самим счетом, обеспечивая методы для внесения и изъятия вкладов и подведения итогового баланса. Второй интерфейс может использоваться для отображения текущего состояния счета в диалоговом окне.
Сервер расположен в папке Chap4\Bank, а клиенты — в папках Chap4\BankClientVb и Chap4\BankClientVc. Начнем с построения сервера, для чего дважды щелкните на файле bank.dsw в каталоге Chap4\Bank (открыв проект в среде разработки Visual C++), а затем воспользуйтесь командой меню BuJld«=>Build Bank.dll. Запустите программу-клиент на Visual Basic (оба клиента обладают идентичной функциональностью), как показано на рис. 4.1. Рис. 4.1. Программа-клиент для сервера банковского счета Когда вы запускаете программу, то вначале должны увидеть окно сообщения с информацией о создании и уничтожении объекта Greet. Этот объект используется для обеспечения приветственного сообщения, выводимого в качестве заголовка окна. Объект Account создается по щелчку на кнопке Create. Затем отображается начальное состояние счета — 100. Вы можете делать вклады и изъятия со счета и просматривать текущее состояние счета в окне сообщений. Если вы завершаете работу, то должны щелкнуть на кнопке Destroy. Как и в случае объекта Greet, для объекта Account также имеется окно сообщения о его создании и уничтожении. Эти окна сообщений обеспечиваются сервером с целью помочь вам понять жизненный цикл объектов СОМ. Изучение структуры СОМ-сервера В этом разделе мы рассмотрим структуру СОМ-сервера. Его можно рассматривать как источник библиотечных функций, пригодных к немедленному использованию. Цель СОМ-сервера — обеспечить повторное применение кода для вашей программы, подобно тому, как вы делаете это при использовании статических библиотек (например, библиотеки времени выполнения С) или динамических библиотек (таких как DLL, являющихся частью Windows и приложений). Функциональность СОМ- сервера может быть описана как библиотека типов, которая определяет классы, интерфейсы и методы. Мы будем следовать индуктивному подходу в изучении структуры СОМ-сервера. Анализируя сервер банковского счета, мы будем использовать определенный инструментарий, а также рассмотрим сущности типа "класс" и "метод". Рассмотрев пример сервера, мы более системно подойдем к терминологии и концепциям СОМ, после чего изучим простую модель программирования программ-клиентов, вызывающих функции СОМ-сервера, и перейдем к практической ее реализации с помощью языков программирования Visual Basic и Visual C++. Visual Basic Object Browser В Visual Basic встроен инструмент просмотра структуры СОМ-серверов, называемый Object Browser, который может быть вызван в меню View. Откройте проект BankClientVb и вызовите Object Browser. В выпадающем списке выберите BANKLib —
библиотеку типов для нашего примера сервера банковского счета. Выберите метод Deposit класса Account. После этого на нижней панели будет представлено описание параметров этого метода, как показано на рис. 4.2. Рис. 4.2. Visual Basic Object Browser Классы, методы и свойства Object Browser отображает классы. Мы видим, что СОМ-сервер, как описано в его библиотеке типов, реализует классы. При выборе класса отображаются его методы и свойства. Метод можно рассматривать как функцию, а свойства — как данные. Например, класс Greet имеет свойство Greeting, а класс Account — методы Deposit, Withdraw и GetBalance. Метод, возвращающий данные, может быть реализован как свойство. Когда выбирается определенный метод, Object Browser отобразит нам "подпись" метода, указывающую параметры и их типы данных. Например, метод Deposit получает один параметр типа Long. К сожалению, Object Browser не указывает, как именно передаются параметры, например, как ByVal или ByRef. OLE/COM Object Viewer (OLE View) Более детальная информация о COM/Server может быть получена с помощью OLE/COM Object Viewer. Эта программа может быть запущена из меню Tools Visual C++, а также с помощью команды меню Starts Programs»^ Microsoft Visual Studio^ Microsoft Visual Studio Tools^OLE View. Этот инструмент выполняет множество функций, но в настоящий момент нас интересует только его применение для просмотра библиотеки типов (внимание: как известно, Type Library Viewer не работает под управлением Windows 95, но ведь вы все равно должны работать с NT 4.0 или Windows 2000). После запуска OLE View вы можете вызвать Type Library Viewer, выбрав пункт меню File^View TypeLib. Перейдите в каталог Bank и откройте либо bank.tlb, либо Debug\bank.dll (в случае сервера, созданного с помощью Visual C++, информация о типе доступна как в DLL-файле, так и в специальном файле библиотеки типов TLB. Для Visual Basic сервер — это только DLL-файл. ЕХЕ-сервер содержит информацию о типах в ЕХЕ. Как показано на рис. 4.3, вы можете получить более детальную информацию об объектах.
Рис. 4.3. OLE/COM Object Viewer Просмотр в виде дерева в левой панели — это удобный путь для работы с библиотекой. Соответствующий код IDL (Interface Definition Language — Язык определения интерфейса), правая панель, предоставляет детальные спецификации. Если вам интересно, как будет выглядеть большая библиотека типов, — откройте одну из объектных библиотек Office, например, такую как Msword8.olb (Microsoft Word) или библиотеку ADOmsadol5.dll. IDL для сервера банковского счета Язык определения интерфейса (Interface Definition Language — IDL) представляет собой язык для точной спецификации СОМ-сервера. Он не содержит никакой реализации — это только спецификация, подобная заголовочному файлу в программе на языке С или C++. Позже, при создании СОМ-сервера, мы будем работать с IDL. Сейчас мы рассмотрим IDL код для примера сервера банковского счета. При выборе корня показанного дерева для BANKLib (Bank 1.0 Type Library) на правой панели появится полный IDL-код. При желании его можно скопировать в буфер обмена и вставить в другой текстовый файл, для чего выделите текст и воспользуйтесь стандартной командой Copy (<Ctrl-C>). Весь IDL-файл приведен ниже. Сейчас нет необходимости понимать все, что в нем приведено. Некоторые интересные фрагменты этого файла выделены полужирных шрифтом. // Generated .IDL file (by the OLE/COM Object Viewer) // // typelib filename: bank.dll [ uuid(0FFBDAAl-FCA7-HD2-8FF4-00105AA45BDC) , version(1.0), helpstring("Bank 1.0 Type Library")
] library BANKLib { //TLib: //TLib: OLE Automation: 4> {00020430-0000-0000-C000-00000000004 6} importlib("STD0LE2.TLB"); // Forward declare all types defined in this typelib interface IAccount; interface IDisplay; interface IGreet; [ uuid(0FFBDAAE-FCA7-HD2-8FF4-00105AA45BDC) , helpstring("Account Class") ] coclass Account { [default] interface IAccount; interface IDisplay; }; [ odl, uuid(0FFBDAAD-FCA7-llD2-8FF4-00105AA45BDC) , helpstring("IAccount Interface") ] interface IAccount: IUnknown { [helpstring("method Deposit")] HRESULT _stdcall Deposit([in] int amount); [helpstring("method GetBalance")] HRESULT _stdcall GetBalance([out] int* pBalance); [helpstring("method Withdraw")] HRESULT _stdcall Withdraw([in] int amount); odl, uuid(42135D00-2F41-HDl-A0lB-00A024D06632) , helpstring("IDisplay Interface") interface IDisplay: IUnknown { [helpstring("method Show")] HRESULT _stdcall Show(); /; uuid(7A5E6E82-3DF8-HD3-903D-00105AA45BDC) , helpstring("Greet Class") coclass Greet { [default] interface IGreet; I;
[ odl, uuid(7A5E6E81-3DF8-llD3-903D-00105AA45BDC), helpstring("IGreet Interface") ] interface IGreet: IUnknown { [propget, helpstring("property Greeting")] HRESULT _stdcall Greeting([out, retval] BSTR* pVal); }; }; Определение библиотеки типа Вначале следует определение самой библиотеки типа, которое выполняется тремя различными путями. Основной путь идентификации любого объекта в СОМ состоит в использовании универсально уникального идентификатора (universally unique identifier — UUID), который иногда называют глобально уникальным идентификатором (globally unique identifier — GUID). Гарантируется повсеместная уникальность этой 128- битовой величины, чем снимается проблема пересечения имен. Однако для представления информации пользователю следует воспользоваться более "дружественным" именем, которое задается строкой helpstring ("Bank 1.0 Type Library") и используется для представления в программах просмотра. Вы можете увидеть, как это длинное имя используется в Visual Basic в диалоговом окне References, которое вызывается с помощью команды меню Projects References, как показано на рис. 4.4. Рис. 4.4. В диалоговом окне References представлено длинное имя библиотеки типов Если вы создаете программу-клиент СОМ на Visual Basic, то первое, что нужно сделать, — добавить ссылки на все СОМ-серверы, которые будете использовать. Третье имя — то, под которым библиотека известна программе и которое выводится в Object Browser (см. рис. 4.2). Имя библиотеки используется в программе при необходимости разрешения неоднозначности имен. Предположим, например, что мы используем две различные библиотеки, в каждой из которых имеется класс Account. Тогда для того, чтобы однозначно именовать этот класс из библиотеки BANKLib, используется ИМЯ BANKLib.Account.
Наличие трех имен, как в данном случае, — общее правило в СОМ. Вы можете рассматривать UUID как "машинное имя"; длинное имя, задаваемое оператором helpstring, — как "пользовательское" имя, а третье имя — как "программное". Вы никогда не перепутаете UUID с другими именами, но будьте осторожны и не используйте второе имя вместо третьего или наоборот! Определение сокласса Далее идет определение СОМ-класса, или "сокласса" (coclass). Их в одной библиотеке может быть несколько. Каждый класс также именуется тремя способами. Вначале идет машинное имя, или UUID. В случае класса этот параметр часто называют идентификатором класса (CLSID). Затем следует пользовательское имя (в нашем случае — "Account Class") и имя, используемое в программе ("Account"). Чтобы окончательно вас запутать, скажу, что на самом деле классы имеют еще и четвертое имя, называемое идентификатором программы, но об этом — немного позже. Интерфейсы Кроме классов, IDL определяет то, какие интерфейсы поддерживает класс. Интерфейс представляет собой нечто специфичное для СОМ. Если вы знакомы с объектно- ориентированными языками программирования типа C++, то наверняка знаете о классах и о том, что классы могут иметь методы (или функции-члены). В языках, подобных C++, нет группирования методов класса. Но в СОМ мы говорим не о методах класса, а о методах интерфейса. Родственные методы группируются в интерфейсы. Такой подход дает большую логическую организацию поддерживаемой классом функциональности. Класс Account поддерживает два интерфейса — lAccount и I Display. Обратите внимание на соглашение об именах, согласно которому имена интерфейсов начинаются с буквы "i". Первый интерфейс, lAccount, разработан как интерфейс по умолчанию. В программе на Visual Basic вы получаете доступ к методам интерфейса по умолчанию, ссылаясь на сам класс. Этот момент станет более понятен, когда мы будем работать с кодом клиента на Visual Basic. Методы Следующие спецификации представляют собой точные "подписи" методов, определяющие каждый параметр, его тип данных и то, является параметр входным или выходящим (или и тем, и другим). Свойства Для свойств существует специальная запись. Так, интерфейс iGreet имеет одно свойство "только для чтения" по имени Greeting. Заключение Наш пример сервера банковского счета имеет одну библиотеку типов (как и все СОМ-серверы). Есть два класса — Account и Greet. Класс Account поддерживает два интерфейса — lAccount (с тремя методами) и I Display (с одним методом), а класс Greet — один интерфейс, IGreet, с одним свойством.
Терминология и концепции СОМ Надеюсь, наше рассмотрение примера сервера банковского счета с анализом IDL стало хорошим стартом для понимания структуры СОМ-сервера. При написании этого раздела наша цель состояла в том, чтобы дать краткий обзор терминологии и ключевых концепций СОМ. Учите, что имеется некоторая несовместимость и путаница в использовании терминов в литературе о СОМ. Термин "объект" иногда используется там, где следует использовать термин "класс"; то же самое происходит с термином "компонент". В пределах этой книги терминология, по крайней мере, не противоречива, и я надеюсь, что приведенные здесь пояснения сослужат вам добрую службу при чтении книг о СОМ других авторов. Интерфейсы Самая фундаментальная концепция в СОМ — это концепция интерфейсов. Интерфейс можно рассматривать как точно определенный контракт между сервером и его клиентами. Будучи однажды определенным, интерфейс должен оставаться неизменным на протяжении всего своего существования. Если изменения крайне необходимы, следует определить новый интерфейс, но не изменять старый. Интерфейс состоит из группы методов. Метод можно трактовать как функцию с параметрами определенных типов. Интерфейс описывается с помощью языка определения интерфейса (Interface Definition Language — IDL). Вот пример IDL-кода для интерфейса iAccount. interface IAccount: IUnknown { HRESULT Deposit([in] int amount); HRESULT GetBalance([out] int* pBalance); HRESULT Withdraw([in] int amount); >; hresult представляет собой стандартный возвращаемый тип для методов интерфейса и указывает код ошибки или код успешного завершения операции. Программы Visual Basic не работают с hresult непосредственно, и мы обсудим его более подробно в разделе о клиентах Visual C++. (В IDL, показанном OLE/COM Object Viewer и приведенном выше, имеется также параметр _stdcall, который представляет собой стандартное соглашение о вызове, определяющее, каким образом аргументы передаются в стек, и т.п. Это директива для компилятора, и поскольку _stdcall является вызовом по умолчанию, она может быть опущена.) Интерфейс IUnknown В СОМ все интерфейсы происходят от специального интерфейса IUnknown и, следовательно, в дополнение к своим собственным методам имеют три метода IUnknown. Вот как выглядит представление IUnknown в IDL: interface IUnknown { HRESULT QueryInterface(REFIID iid, void** ppvObject); ULONG AddRef(); ULONG Release(); }; Первый метод поддерживает согласование интерфейсов, что дает возможность клиенту найти дополнительные интерфейсы. Оставшиеся методы поддерживают счетчик ссылок, который позволяет клиенту управлять временем жизни объекта. Все это будет подробно обсуждаться ниже.
Классы Интерфейс представляет собой абстрактную спецификацию. Интерфейсы реализуются классами. Класс в СОМ подобен классу в объектно-ориентированных языках программирования. Класс инкапсулирует данные и поведение в единую сущность. Не имея классов, вы должны поддерживать огромное количество переменных и передавать их в качестве параметров изолированным функциям. Группируя родственные данные и функции в класс, вы создаете абстракцию, упрощающую программирование и позволяющую расширить программную модель вашими собственными типами данных. СОМ расширяет эту программную модель, позволяя классу иметь несколько интерфейсов, каждый из которых поддерживает определенные свойства с помощью групп взаимосвязанных функций. Например, в нашем сервере банковского счета класс Account реализует интерфейсы lAccount и iDisplay, а также lUnknown, который поддерживается любым СОМ-объектом (рис. 4.5). N lAccount ) IDisplay ч lUnknown ) Account Рис. 4.5. Класс Account реализует три интерфейса Объекты Объект представляет собой экземпляр класса. Класс можно рассматривать как код, который обеспечивает возможности, описываемые интерфейсами, которые поддерживаются этим классом. Но для того чтобы выполнить некоторую работу, вам необходимо создать как минимум один экземпляр класса. Такой экземпляр объекта имеет некоторые ассоциированные с ним данные. Другой экземпляр объекта будет иметь другие собственные данные. Рассмотрим два одновременно работающие экземпляра программы-клиента (для запуска двух экземпляров программы Visual Basic можно либо запустить две сессии Visual Basic или, если вы можете построить ЕХЕ-файл, запустить его дважды с помощью Windows Explorer). С помощью каждого из экземпляров вы можете создать объект. В нашем примере новый объект всегда начинает работу с балансом 100. Первый объект может сделать два вклада, а второй — дважды снять деньги со счета. Понятно, что мы имеем два различных экземпляра объекта, каждый из которых содержит собственные данные (рис. 4.6). Создание экземпляра объекта Программа-клиент должна иметь возможность создавать экземпляры объектов класса. Система времени выполнения СОМ обеспечивает функцию API CoCreatelnstanceEx (или более простую и более старую функцию CoCreatelnstance), которая может непосредственно использоваться в программе на C++. Программа на Visual Basic может использовать оператор New или функцию CreateObject. Мы рассмотрим все три способа, поскольку они приводят к пониманию другого важного вопроса, а именно: "Каким образом идентифицируются классы и интерфейсы?"
Рис. 4.6. Два экземпляра объектов класса Account Очень важным является также то, что создание экземпляра приводит не к ссылке на объект, а к ссылке на определенный интерфейс. Это различие имеет очень важное значение и может привести к путанице — в частности, в Visual Basic, где, как мы вскоре увидим, не имеется непосредственной поддержки интерфейсов. CoCreatelnstance (C++) Вначале давайте рассмотрим процесс создания экземпляров объектов в C++. Это приведет нас гораздо ближе к пониманию того, как работает СОМ (по сравнению с упрощениями Visual Basic). Для простоты мы рассмотрим более старую функцию — CoCreatelnstance. Позже, при рассмотрении DCOM, я поясню, как работает более современная функция — CoCreatelnstanceEx. Ниже приведен код для создания экземпляра объекта класса Account. (Полный код вы сможете найти в функции CBankClientDlg: :OnCreateObject проекта BankClientVc. Указатель на интерфейс описан в файле BankClientDlg.h.) IAccount* m_pAccount; HRESULT hr; hr = CoCreatelnstance(CLSID_Account, NULL, CLSCTX_SERVER, IID_IAccount, (void**) &m_pAccount) ; Обратите внимание на то, что класс определяется идентификатором класса, а интерфейс — идентификатором интерфейса. New (Visual Basic) Первый путь создания экземпляра объекта в Visual Basic заключается в использовании оператора New. Вначале вы должны добавить ссылку на библиотеку типа (пункт меню Project^References; см. рис. 4.4). При этом будет вызвана библиотека BANKLib, включающая класс Account (см. рис. 4.2 — пример использования Object Browser). Следующий код, взятый из проекта BankClientVb, иллюстрирует создание экземпляра объекта. Обратите внимание на то, что класс определяется именем.
Dim objAccount As Account Set objAccount = New Account В Visual Basic говорится, что мы получаем "ссылку на объект" (object reference) класса Account. Однако такая терминология неверна, поскольку, как мы видели, СОМ работает со ссылками на интерфейсы. Вспомним о том, что при рассмотрении IDL для сервера банковского счета iAccount являлся интерфейсом класса Account по умолчанию. Это означает, что ссылка на объект Account на самом деле представляет собой ссылку на интерфейс IAccount. Когда мы рассмотрим полный код клиента Visual Basic, то увидим, как получить и использовать второй интерфейс с помощью Visual Basic. CreateObject (Visual Basic) Второй путь создания экземпляра объекта в Visual Basic заключается в использовании функции CreateObject. Этот способ представляет собой первоначальный путь создания экземпляров объектов в Visual Basic и остается единственным способом создания объекта в VBScript. Изначально Visual Basic использовал только специальный вид СОМ, известный как "автоматизация" (Automation), и мог обмениваться информацией только с серверами СОМ, чьи классы обеспечивали диспетчерские интерфейсы. Автоматизация менее эффективна, чем непосредственная работа с СОМ, но и более гибка, поскольку поддерживает позднее связывание, при котором вид класса определяется во время работы (требование, существенное для языков сценариев в таких приложениях, как Internet Explorer, когда нет возможности знать заранее о том, какого типа объекты могут быть использованы на HTML- странице). Эта тема более детально рассматривается в главе И, "Автоматизация и программирование СОМ на Visual Basic". Оказывается, что функция CreateObject может также обеспечивать раннее связывание, если ссылка на библиотеку типов добавляется к проекту, как мы поступили выше. Нижеследующий код, также полученный из проекта BankClientVB, создает экземпляр объекта класса Greet. Заметьте, что класс определяется идентификатором программы. Dim objGreet As Greet Set objGreet = CreateObject("Bank.Greet.1") Идентификаторы в COM Приведенные примеры проиллюстрировали применение различных типов идент и- фикаторов, используемых в СОМ. Теперь мы можем пояснить, что представляет собой каждый из них. Глобально уникальный идентификатор (GUID) Языки программирования используют переменные, классы и другие элементы языков с именами, пригодными для чтения пользователем. Конфликт имен — явление возможное, но вполне разрешимое в рамках одного проекта. Классы СОМ реализуют бинарные компоненты, которые должны быть уникальны для очень широкой области. Интерфейсы и другие элементы СОМ требуют идентификационного механизма, который мог бы предупредить наличие конфликтов имен. Distributed Computing Environment (DCE) из Open Software Foundation предоставило решение в виде концепции универсально уникального идентификатора (universally unique identifier — UUID), который представляет собой 128-битовую величину, которая может быть алгоритмически сгенерирована таким образом, чтобы гарантировать виртуальную уни-
кальность. Microsoft адаптировала этот механизм для СОМ, назвав такой идентификатор менее "грандиозно" — глобально уникальным идентификатором (globally unique identifier — GUID). Для различных элементов СОМ имеются разные типы GUID. Так, мы уже встретились с GUID для библиотеки типов, класса (CLSID) и интерфейса (IID). Программируя на C++, вы можете непосредственно обращаться к этим GUID в вашем коде. Обычно для обращения к GUID используются явно определенные в заголовочных файлах константы. В высокоуровневом окружении, таком как Visual Basic, ваша программа будет пользоваться удобочитаемыми именами, но среда Visual Basic будет применять соответствующие GUID при обращении к СОМ от вашего имени. Идентификатор программы (ProgID) Идентификатор программы (ProgID) тесно связан с CLSID. ProgID представляет собой строку, которая может использоваться в качестве заместителя CLSID. ProgID зачастую имеет вид application, class или application, class . N, где application — определенное приложение или сервер, a class — некоторый класс, реализуемый этим сервером. К этому может также быть добавлен номер версии. Пр и- мером ProgID может служить Bank.Greet. 1. Имя класса IDL для сервера содержит оператор coclass, используемый для того, чтобы дать имя соклассу (классу СОМ). Двумя соклассами в нашем примере сервера банковского счета являются Account и Greet. coclass Account { [default] interface IAccount; interface IDisplay; }; coclass Greet { [default] interface IGreet; }; Visual Basic использует имя сокласса, когда вы описываете ссылку на объект с помощью оператора Dim или New. "Пользовательские" имена Все упоминавшиеся выше идентификаторы используются для программирования. Конечный пользователь никогда не встречается ни с GUID, ни с ProgID или именем сокласса. Но, тем не менее, существуют элементы СОМ, которые могут быть показаны конечному пользователю. Превосходным примером могут служить классы составного документа в OLE. Конечный пользователь клиента OLE (или контейнера), такого как Microsoft Word, может вставлять в документ "объекты", созданные другими приложениями. Для этого используется команда меню 1п- sert^Object, в появляющемся диалоговом окне отображаются все классы OLE системы. Отображаемые в этом диалоговом окне, пример которого приведен на рис. 4.7, имена представляют собой "пользовательские" имена классов (иногда называемые типами объектов). Различные пользовательские имена создаются с помощью оператора helpstring в IDL.
Рис. 4.7. "Пользовательские " имена классов OLE Время жизни объектов Одним из свойств СОМ является то, что в управлении временем жизни объектов участвуют как клиент, так и сервер. Никто из них не в состоянии управлять временем жизни объекта самостоятельно. Ясно, что сервер не имеет права удалить объект, пока клиент с ним работает, поэтому клиент должен сообщить серверу о прекращении работы с объектом. Но это не означает, что сервер может удалить объект, потому что с ним может работать другой клиент. Решение заключается в том, что сервер поддерживает счетчик ссылок для каждого объекта. Базовый интерфейс iUnknown предоставляет два метода управления счетчиком ссылок. AddRef увеличивает его. Этот метод вызывается при создании экземпляра объекта, поэтому счетчик начинает работу со значения 1. Когда клиент копирует ссылку на интерфейс, он должен вызвать AddRef, поскольку теперь на объект имеется еще одна ссылка. По окончании работы с указателем на интерфейс клиент вызывает метод Release. Когда все клиенты освободят все свои ссылки на объект, счетчик ссылок станет равным 0, и сервер сможет безопасно удалить объект. Время жизни объекта в Visual Basic Одно из приятных свойств Visual Basic заключается в том, что во многих случаях управление временем жизни объекта происходит автоматически, не требуя от вас дополнительного кода. Если оператор Dim объявляет ссылку на объект как локальную переменную в процедуре или функции, то ссылка на объект будет освобождена при выходе из области видимости этой процедуры или функции. Если у вас имеется ссылка на объект в глобальной области видимости, то вы можете управлять ею самостоятельно, освобождая ее присвоением значения Nothing. Так, программа-клиент на Visual Basic BankClientvb реализует обработчик кнопки Destroy следующим образом: Dim objAccount As Account 'глобальная область видимости Private Sub cmdDestroy_Click() Set objAccount = Nothing txtBalance = "" End Sub
Согласование интерфейсов Тот факт, что класс СОМ может поддерживать несколько интерфейсов означает, что есть механизм, предназначенный для того, чтобы клиент, имеющий ссылку на один интерфейс, мог получить ссылку и на другой интерфейс этого класса. Получение другого указателя на интерфейс выполняется с помощью процесса, известного как "согласование интерфейсов", и использования третьего метода lUnknown, а именно QueryInterface. HRESULT Querylnterface(REFIID iid, void** ppvObject); Первый параметр используется для передачи идентификатора требуемого интерфейса; второй — для получения указателя на интерфейс, если последний поддерживается. Если запрашиваемый интерфейс не поддерживается, возвращаемое значение hresult указывает на ошибку. Вызов Querylnterface из Visual C++ Программа на Visual C++ вызывает Querylnterface непосредственно. В качестве примера взгляните на реализацию OnShow. void CBankClientDlg::OnShow() { //Запрос второго интерфейса HRESULT hr = m_j>Account->QueryInterface (IID_IDisplay (void**) &m_j>Display) ; if (FAILED(hr)) { MessageBox("Querylnterface failed"); return; } if (!m_pDisplay) return; hr = m_pDisplay->Show(); if (FAILED(hr)) { MessageBox("Show failed"); return; } m_pDisplay->Release(); m_pDisplay = NULL; Использование Querylnterface в Visual Basic Программа на Visual Basic не вызывает Querylnterface непосредственно. В действительности Visual Basic даже не показывает, что он работает с интерфейсами, которые в Visual Basic представлены как классы. Когда Visual Basic обращается к библиотеке типа, для каждого интерфейса он отображает класс. Интерфейс по умолчанию назначается классу с именем, соответствующим соклассу. Таким образом, классом Visual Basic, соответствующим интерфейсу lAccount, является класс Account. Для других интерфейсов Visual Basic назначает имена классов, такие же, как и имена интерфейсов. Следовательно, классом Visual Basic, соответствующим интерфейсу iDisplay, будет просто класс iDisplay. Поэтому код Visual Basic для работы с Querylnterface очень прост. Вы описываете ссылку для требуемого интерфейса и выполняете присваивание исходного интерфейса. Вот код для обработки кнопки Show:
Private Sub cmdShow_Click () If Not objAccount is Nothing Then Dim ifcDisplay As IDisplay Set ifcDisplay = objAccount ifcDisplay.Show End If End Sub Заметьте, что в этом коде есть небольшая сложность: вместо того чтобы просто присвоить значение новой ссылке, вначале проверяется корректность значения objAccount (т.е. что оно не равно Nothing). Обратите также внимание на соглашения об именовании. По традиции мы именуем ссылку на интерфейс по умолчанию как objAccount и говорим о ней, как о ссылке на объект. Такие "угрызения совести" за неверное именование в случае интерфейса IDisplay нас не мучают, так как приставка ifc ясно показывает, что переменная представляет собой ссылку на интерфейс. Сервер Теперь мы опишем, что представляет собой сервер в СОМ. Это — просто программный модуль (в Windows это — DLL или ЕХЕ), который обеспечивает выполняемый код для одного или нескольких классов. Обратите внимание на использующуюся при этом иерархию. Сервер может реализовывать несколько классов, класс — поддерживать несколько интерфейсов, интерфейс — иметь несколько методов. На рис. 4.8 показана общая структура нашего примера сервера банковского счета (для простоты интерфейс iunknown не показан). Bank lAccount IDisplay IGreet Account Greet Рис. 4.8. Иерархическая структура сервера банковского счета Библиотека типов Код сервера располагается в программном модуле, который обычно представляет собой DLL. Кроме самого кода, СОМ интенсивно использует описание кода. Это описание называется информацией о типе (type information) и хранится в виде библиотеки
типов (type library). Библиотека сама по себе имеет GUID, имя (BANKLib — в нашем примере) и пользовательское имя ("Bank 1.0 Type Library"). Библиотека хранит описания классов, реализуемых сервером; интерфейсов, поддерживаемых классами; методов интерфейсов и точную информацию о параметрах и типах данных этих методов. Определяется также интерфейс каждого класса по умолчанию. Программная модель клиента СОМ Теперь мы можем очень кратко описать программную модель клиента СОМ. (Примечание по терминологии: в C++ мы ссылаемся на интерфейсы, используя указатели. Так что далее мы будем использовать нейтральный термин "ссылка на интерфейс", а для интерфейса по умолчанию в Visual Basic — "ссылка на объект". Переходя к вопросам действительного кодирования, в разговоре о C++ мы будем использовать термин "указатель на интерфейс".) Существует шесть основных шагов, которые вы должны выполнить. В зависимости от используемого вами программного окружения некоторые из поставленных задач могут решаться автоматически. 1. Инициализировать систему времени выполнения СОМ. Для этого COM API предоставляет возможность вызова Colnitialize (или CoInitializeEx). MFC-приложения могут вызвать AfxOlelnit. Visual Basic выполняет инициализацию автоматически. 2. Получить первоначальную ссылку (или указатель) на интерфейс. В C++ это делается с помощью вызова CoCreatelnstance или CoCreatelnstanceEx. В Visual Basic используется для этого оператор New или вызов CreateOb ject. 3. Посредством указателя на интерфейс вызвать методы интерфейса. 4. Если вам требуется вызов методов другого интерфейса, выполнить Querylnterface. В C++ вы вызываете Querylnterface посредством указателя на интерфейс, а в Visual Basic — посредством операции присвоения. 5. После работы с указателем на интерфейс в C++ вызвать Release. В Visual Basic вы либо выходите из области видимости естественным путем, либо явным образом присваиваете соответствующей переменной значение Nothing. 6. По окончании работы с СОМ деинициализировать СОМ путем вызова CoUninitialize. Этот шаг выполняется автоматически в Visual Basic и MFC. Программирование клиента СОМ Теперь мы можем представить полный программный пример СОМ-сервера. У нас есть три программы-примера. Первая из них представляет собой клиент на языке Visual Basic — BankClientVb. Вторая является консольной реализацией на Visual C++ — BankConsoleVc. И, наконец, имеется программа на Visual C++, в которой используется графический интерфейс пользователя, реализованная с помощью MFC, — BankClientVc. Клиент СОМ на Visual Basic Программа BankClientVb строится как стандартный проект ЕХЕ. Вы должны добавить к проекту ссылку на библиотеку типов сервера банковского счета ("Bank 1.0 Type Library") с помощью команды меню Projects References (см. рис. 4.4). Поместите
управляющие элементы в форму, как показано на рис. 4.1. Полный код, который вам потребуется после этого, приведен ниже. Option Explicit Dim objAccount As Account Private Sub cmdCreate_Click() Set objAccount = New Account UpdateBalance End Sub Private Sub cmdDeposit_Click() 'ВНИМАНИЕ. Мы не проверяем корректность ссылки 'на объект!! См. пример корректной обработки в 'коде Withdraw и Show correct pattern objAccount.Deposit txtAmount UpdateBalance End Sub Private Sub cmdDestroy_Click() Set objAccount = Nothing txtBalance = "" End Sub Private Sub cmdShow__Click () If Not objAccount Is Nothing Then Dim ifcDisplay As IDisplay Set ifcDisplay = objAccount ifcDisplay.Show End If End Sub Private Sub cmdWithdraw_Click() If Not objAccount Is Nothing Then objAccount.Withdraw txtAmount UpdateBalance End If End Sub Private Sub Form_Load() 'Note use of Progld and CreateObject Dim objGreet As Greet Set objGreet = CreateObject("Bank.Greet.1") Forml.Caption = objGreet.Greeting txtAmount =25 End Sub Private Sub UpdateBalance() Dim balance As Long objAccount.GetBalance balance txtBalance = balance End Sub К этому времени код должен представляться вам самодокументированным. Отметим только один момент — наличие дополнительного кода для защиты приложения. Так, в обработчиках show и withdraw проверяется корректность ссылки на объект. Таким образом, если вы запустите приложение и не создадите объект, show и withdraw не будут выполнять никаких действий. Но вы получите ошибку, если
щелкнете на кнопке Deposit, предварительно не воспользовавшись Create. Текстовое поле для баланса будет пустым при отсутствии объекта, а при его наличии в текстовом поле будет отображен текущий баланс. Другой проверки ошибок в программе нет. Для того чтобы программа была более мощной и стойкой к ошибкам, можно воспользоваться возможностями, предоставляемыми оператором On Error в Visual Basic. Обработка ошибок — очень важный вопрос в СОМ, который будет рассматриваться в главе 12, "Обработка ошибок и отладка". Консольная программа-клиент на Visual C++ Рассмотрим теперь программу BankConsoleVc, создающую консольное приложение Win32. В нашей программе C++ мы не будем работать с библиотеками типа, а получим всю необходимую информацию из заголовочного файла и файла кода прое к- та. Наша программа имеет файлы Bank.h и Bank_i.c, скопированные из проекта сервера. Файл Bank.h содержит описания интерфейсов lAccount, iDisplay и iGreet. He беспокойтесь, если вам этот файл будет непонятен — данный файл сгенерирован с помощью компилятора MIDL RPC, но об этом мы поговорим позже. А пока вам следует знать только то, что этот файл необходимо включить в проект при использовании указателей на интерфейсы. Второй файл, Bank__i.c, содержит определения GUID. Он должен быть включен только в один модуль компиляции и, следовательно, не должен включаться в заголовочный файл (если вы сделаете это, то можете получить сообщение об ошибке множественного определения идентификатора). Поскольку этот проект имеет только один файл с исходным кодом, мы включим оба файла — и Bank.h, и Bank_i.c — в BankConsole.cpp. Ключевой заголовочный файл для включения СОМ-библиотеки следующий: objbase.h. Полный код приведен ниже. Он не должен вызывать сложностей для понимания. Один из нюансов состоит в обработке символьных строк. Все строки в СОМ представляют собой строки Unicode, а некоторые строки — частный случай строк Unicode, известный как BSTR. Класс Greet возвращает строку приветствия как BSTR, которая должна быть конвертирована. Мы обсудим Unicode и В ST немного позже. // BankConsole.cpp #include <stdio.h> #include <objbase.h> #include "bank.h" #include "bank_i.c" #include <comdef.h> int main(int argc, char* argv[]) { // Инициализация COM HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { printf ("Colmtialize failedW) ; return 0; } // Создание экземпляра объекта Greet, // получение указателя на интерфейс IGreet* pGreet; hr = CoCreateInstance(CLSID_Greet, NULL, CLSCTX_SERVER, IID_IGreet, (void **) &pGreet); if (FAILED(hr))
{ printf("CoCreatelnstance failedXn"); return 0; } // Вывод приветствия и освобождение BSTR bstr; hr = pGreet->get_Greeting(&bstr); if (FAILED(hr)) { printf("getJSreeting failed\n")/ return 0; } else { _bstr_t greeting(bstr); printf("%s\n", (const char*) greeting); pGreet->Release(); } // Создание экземпляра объекта Account, // получение указателя на интерфейс IAccount* pAccount; hr = CoCreatelnstance(CLSID_Account, NULL, CLSCTX_SERVER, IID_IAccount, (void **) &pAccount); if (FAILED(hr)) { printf("CoCreatelnstance failedXn"); return 0; } // Использование указателя на интерфейс // для вызова метода // Получение и вывод начального баланса int balance; hr = pAccount->GetBalance(&balance); if (FAILED(hr)) { printf("GetBalance failed\n"); pAccount->Release(); return 0; } printf("balance = %d\n", balance); // Депозит 25 hr = pAccount->Deposit(25); if (FAILED(hr)) { printf("Deposit failedXn"); pAccount->Release(); return 0; } // Получение информации о балансе hr = pAccount->GetBalance(&balance); if (FAILED(hr)) printf("GetBalance failedXn"); else printf("balance = %d\n", balance); // Запрос IDisplay, вызов Show и освобождение
IDisplay* pDisplay; hr = pAccount->QueryInterface(IID_IDisplay, (void **) &pDisplay); if (FAILED(hr)) { printf("Querylnterface failed\n"); pAccount->Release() ; return 0; } hr = pDisplay->Show(); if (FAILED(hr)) printf("Show failedW); else pDisplay->Release(); pAccount->Release() ; return 0; } Программа-клиент на Visual C++ с использованием MFC Эта программа создана как стандартный проект MFC AppWizard (exe). На первом шаге в качестве типа приложения вы выбираете Dialog Based, а на втором шаге для простоты отменяете выбор опции About Box. Согласитесь с предлагаемой по умолчанию поддержкой управляющих элементов ActiveX. Мы не используем управляющих элементов ActiveX в своем приложении, но выбор этой опции удобен тем, что влечет за собой подключение всех необходимых вам заголовочных файлов MFC. Примите также и все прочие предлагаемые по умолчанию настройки проекта. Как и в случае консольного проекта, нам необходимы файлы Bank.h и Bank_i.c. Файл Bank.h включается при декларировании указателей на интерфейсы, в нашем случае — в файле определения класса диалога BankClientDlg.h. Второй файл, Bank_i.c, содержит определения GUID. Он должен быть включен только в один модуль компиляции и, следовательно, не должен включаться в заголовочный файл (если вы сделаете это, то можете получить сообщение об ошибке множественного определения идентификатора). Мы включаем его в файл BankClientDlg.cpp. MFC-программа может включать заголовочный файл afxdisp.h для работы с библиотеками СОМ (см. stdafx.h). MFC-программа может инициализировать СОМ посредством функции AfxOlelnit. MFC принимает все необходимые меры для деи- нициализации СОМ. Остальная часть программы функционально подобна консольной программе, хотя имеет несколько иную структуру. MFC-программа реализует графический интерфейс пользователя, и, таким образом, содержит дополнительный код обработчиков различных кнопок. Инициализация СОМ происходит в CBankClientApp: : Initlnstance, а объект Greet используется для инициализации заголовка окна в CBankClientDlg: :OnlnitDialog. Остальная часть программы не должна представлять трудностей для понимания — если вы знаете MFC и то, как работает консольная версия программы. Дополнительные вопросы программирования СОМ-клиентов Имеется также ряд других вопросов, связанных с программированием клиентов СОМ, которые следует обсудить. Мы рассмотрим представление строк в СОМ в виде Unicode и BSTR. Для конвертирования строк нам потребуется соответствующий API.
Мы также рассмотрим некоторые вопросы управления памятью. Часто возникают ситуации, когда СОМ-сервер выделяет память, освободить которую должно приложение-клиент. Для такой кооперации в работе с памятью СОМ предоставляет специальный интерфейс — iMalloc. Управление памятью, преобразование строк и другие задачи решаются с использованием библиотечных функций. Мы увидим, каким образом библиотеки СОМ организованы в функции API и интерфейсы СОМ. Некоторые важные функции предоставляются Win32 API или функциями-оболочками, которые подобны предоставляемым MFC. Этот раздел представляет интерес, в первую очередь, для программистов на C++; последний же раздел этой главы, посвященный системному реестру, должны прочесть все. Unicode Unicode является широко используемым стандартом 16-битового представления символов. NT и Windows 2000 используют Unicode для внутреннего представления символов. СОМ адаптирована для работы с этим стандартом. Это означает, что, если вы передаете строку методу или API-функции СОМ, то должны передать ее как Unicode. Если вы получаете строку из такого вызова, она будет представлена как строка Unicode. По умолчанию приложения Visual C++ используют стандартную 8-битовую кодировку ANSI ("multibyte") с дополнительными байтами (при необходимости). Пока вы не укажете явно, что вы строите приложение, работающее с Unicode, вам придется принимать специальные меры для преобразования строк. Преобразование с использованием Win32 Преобразование строк между описанными выше кодировками Unicode и multibyte выполняется с помощью функций API MultiByteToWideChar и WideCharToMultiByte. Примером использования этих функций может служить проект UseCmd, представленный в каталоге Chap4. Эта консольная программа позволяет пользователю ввести ProgID, вызвать функцию COM API для преобразования его в CLSID, конвертировать CLSID в строковое представление, вывести его и перейти к созданию экземпляра объекта. Вот пример конвертирования строки в Unicode: char progid[80]; WCHAR wbuf[80]; // Конвертация ProgID в Unicode CLSID clsid; MultiByteToWideChar(CP_ACP, 0, progid, -1, wbuf, 80); А вот пример конвертирования из Unicode. LPOLESTR ostr; char buf[80]; WideCharToMultiByte(CP_ACP, 0, ostr, -1, buf, 80, NULL, NULL); Преобразование с использованием макросов MFC (а также ATL) предоставляют серию макросов для упрощения преобразования строк. Для использования этих макросов вы должны включить заголовочный файл afxpriv.h. Перед тем как вызвать любой из макросов, следует с помощью макроса uses_conversion выделить локальную память для проведения преобразований.
Затем вы можете использовать преобразующие макросы, такие как OLE2CT, T20LE и другие, для преобразования "традиционных" строк т в строки OLE и наоборот. Вот как это делается в файле BankClientDlg.cpp (проект BankClientVc). #include <afxpriv.h> BSTR bstr; hr = pGreet->get_Greeting(&bstr); if (FAILED(hr)) MessageBox("get_Greeting failed"); else { // _bstr_t greeting(bstr); USES_CONVERSION; SetWindowText((const char*) OLE2CT(bstr)); ::SysFreeString(bstr); pGreet->Release() ; } BSTR Специализированным видом Unicode являются строки BSTR (от ifosic String). Символы в строке хранятся в виде 16-битовых символов Unicode, однако вместо нулевого завершающего символа в строке используется 16-битовый префикс, указывающий количество байт в строке. Таким образом, строки BSTR могут использоваться для передачи бинарных данных, включающих нулевые значения. Visual Basic применяет этот тип для представления строковых данных, так же как и многие функции и методы COM API. В Visual C++ BSTR имеет добавленный нулевой завершающий символ, и указатель на символ указывает на первый символ строки (идущий после количества байт в строке). Таким образом, существует возможность рассматривать BSTR в программах Visual C++ как обычную строку Unicode. Когда BSTR передается через СОМ, действуют некоторые специальные соглашения об управлении памятью. Память для строки выделяется на сервере и должна освобождаться клиентом. BSTR выделяется с помощью функции Win32 API SysAllocString и освобождается с помощью функции SysFreeString. _BSTR_ Visual C++ предоставляет большое количество полезных классов для помощи в программировании СОМ. Одним из них является _bstr_t, представляющий собой оболочку для BSTR. У него имеются различные конструкторы, операторы преобразования типов и деструкторы, выполняющие автоматическое освобождение памяти. Для использования этих классов вы должны включить в проект заголовочный файл <comdef .h>. Способ его применения показан ниже. #include <comdef.h> BSTR bstr; hr = pGreet->get_Greeting(&bstr); if (FAILED(hr)) MessageBox("get_Greeting failed"); else { _bstr__t greeting (bstr) ; SetWindowText((const char*) greeting);
::SysFreeString(bstr); pGreet->Release() ; } Обратите внимание на тот факт, что при использовании этого класса Visual C++ освобождение памяти производится его деструктором автоматически. Программирование библиотек СОМ Мы изучили программные соглашения, использующиеся при разработке СОМ- клиентов, вызывающих СОМ-серверы. Подобные соглашения имеются и для программ, которым требуются библиотеки СОМ. Первое, в чем требуется разобраться, — это базовая структура библиотеки СОМ. Подобно любой другой библиотеке, имеющихся в Windows, библиотека СОМ содержит большое количество обычных функций API, которые вы можете вызывать. Основное отличие библиотеки СОМ заключается в том, что основная часть ее функциональности предоставляется классами СОМ, вызываемыми с помощью указателей на интерфейсы. Шаблон действий для вызова метода одного из этих встроенных классов выглядит следующим образом. 1. Вызовите функцию API для получения указателя на интерфейс. 2. Вызовите необходимые методы с помощью этого указателя. 3. По окончании работы освободите указатель на интерфейс. В качестве примера рассмотрим часть кода программы UseCmd (к которой мы уже обращались ранее). LPOLESTR ostr; hr = StringFromCLSID(clsid, &ostr); LPMALLOC pMalloc; hr = CoGetMalloc(MEMCTX_TASK, fipMalloc); pMalloc->Free(ostr); pMalloc->Release(); Большое количество функций API служит оболочкой серии вызовов. Так, приведенная выше последовательность вызовов для получения указателя на интерфейс, метода и освобождения указателя на интерфейс может быть заменена вызовом API CoTaskMemFree(ostr). Системный реестр Windows и СОМ Реестр (Registry) представляет собой конфигурационную базу данных для Windows. Он был введен в Windows 3.1 и представлял собой большой . iNi-файл, основным недостатком которого являлся его текстовый вид, — хранящуюся в нем информацию можно было легко повредить. Современный реестр представляет собой бинарную базу данных, доступ к которой осуществляется только программно и с помощью специального инструмента — Registry Editor (REGEDIT), — для использования которого требуются привилегии системного администратора. Реестр хранит также информацию о СОМ-серверах. Для того чтобы ориентироваться, как именно и какая информация о серверах хранится в реестре, рассмотрим различные элементы реестра нашего сервера банковского счета. Инструментарий, который мы будем при этом использовать: OLE/COM Object Viewer и REGEDIT. Мы также познакомимся с тем, как СОМ-серверы регистрируются и дерегистрируются.
Информация о СОМ и реестре более важна при программировании СОМ- серверов, чем при разработке клиентов. За предоставление механизма для корректного размещения информации в реестре несет ответственность сервер. После того как сервер был корректно инсталлирован, программа-клиент должна иметь возможность использовать его в соответствии с программной моделью, описанной в этой главе, безотносительно к тому, каким образом информация хранится в реестре. Однако иногда что-то не работает, и для устранения ошибок очень полезно знать, какая именно информация должна содержаться в реестре. Информация в реестре организована в виде ключей и значений. При изучении реестра мы встретимся с большим их количеством. Использование OLE/COM Object Viewer Ранее в этой главе мы уже применяли OLE/COM Object Viewer для получения информации о библиотеке типов сервера банковского счета. Теперь этот инструмент будет использоваться для получения иной информации. Мы рассмотрим также другие его применения — например, для создания экземпляра объекта. Вызовите OLE/COM Object Viewer (из меню Tools в Visual C++ или с помощью команды Starts Programs1^ Microsoft Visual Studio^ Microsoft Visual Studio Tools^OLE View). Убедитесь, что отмечен пункт Expert Mode меню View. Настройте дерево в левой панели таким образом, чтобы видеть верхние узлы Object Classes, Application IDs, Type Libraries и Interfaces, а также расположенные сразу за Object Classes узлы Grouped by Component Category, OLE 1.0 Objects, COM Library Objects и All Objects (рис. 4.9). Рис. 4.9 Узлы верхнего уровня в OLE/COM Object Viewer Теперь раскроем All Objects (для этого и нужно установить Expert Mode) и рассмотрим Account Class. Это то место, где находятся уже знакомые нам имена (OLE/COM Object Viewer отображает пользовательское имя — длинное и легко читаемое). На правой панели в одном месте отображается вся информация о классе, разбросанная по всему системному реестру (рис. 4.10). Информация об Account Class организована в системном реестре в трех узлах: CLSID, Bank.Account.1 и TypeLib. Для просмотра этой информации мы будем использовать Registry Editor. CLSID Под CLSID находится ключ, являющийся числовым GUID (показанным в шестна- дцатеричной записи), представляющим класс. Соответствующим значением является пользовательское имя "Account Class". Этот ключ содержит несколько подключей. Первым из них является lnProcServer32, чье значение представляет собой путь к сер-
веру. Это и есть самая важная запись в системном реестре для класса. Когда клиент вызывает CoCreatelnstance и передает CLSID, СОМ обращается к реестру и находит путь к серверу, который после этого может быть загружен. Рис. 4.10. Информация из системного реестра об Account Class Следующий подключ — ProgID, в котором находится "Bank.Account.Г'. Это также "не зависящий от версии" ProgID, не имеющий суффикса с номером версии. И, наконец, подключ TypeLIb, дающий GUID для библиотеки типа. Bank.Account. 1 Под этим ключом мы находим просто CLSID. Это — обычная избыточность, которую можно часто наблюдать в системном реестре. Реестр — иерархическая база данных, созданная для быстрого поиска, а не нормализованная реляционная база данных. TypeLib Здесь вы найдете информацию о библиотеке типов, включая ее пользовательское имя "Bank 1.0 Type Library". Под ключом Win32 приводится путь к библиотеке типов, представляющей собой DLL-файл. Создание экземпляра объекта OLE/COM Object Viewer можно использовать для создания экземпляра объекта. Если на левой панели выбран класс, вы можете дважды щелкнуть на нем мышью, затем — на знаке "+" в узле дерева или щелкнуть правой кнопкой мыши и выбрать в контекстном меню Create Instance. Если вы сделаете это для сервера банковского счета, то увидите окно с сообщением о создании объекта. Для освобождения объекта щелкните правой кнопкой мыши и выберите в контекстном меню пункт Release Object. В случае сервера банковского счета появится окно с сообщением об уничтожении объекта. Registry Editor Теперь рассмотрим, каким образом информация хранится в системном реестре, с помощью низкоуровневого инструмента Registry Editor. Его можно запустить из OLE/COM Object Viewer из меню File, а можно воспользоваться командой Start=>Run и ввести имя
программы — regedit. При запуске редактора реестра вы увидите на левой панели дерево, содержащее некоторое количество "ветвей" (дословно — "hives", или "роев", — прим. перев). Для СОМ особую важность представляет ветвь hkey_classes_root (рис. 4.11). Рис. 4.11. Ветви в окне просмотра Registry Editor Открыв hkey_classes_root, вы увидите большое количество расширений файлов, за которыми следуют идентификаторы программ. Взгляните на " Bank.Account. 1" — здесь вы сможете найти соответствующий CLSID, как показано на рис. 4.12. Рис. 4.12. Зная ProglD, можно найти CLSID Теперь, зная числовое значение CLSID, вы сможете найти другую информацию, такую, например, как путь к серверу. Откройте на левой панели узел CLSID и прокрутите список в поисках нужного вам CLSID. После того как вы найдете его, на правой панели появится интересующая вас информация, представленная на рис. 4.13. Рис. 4.13. Наиболее важная информация в реестре хранится в разделе CLS1D
Саморегистрация сервера Одно из важных свойств СОМ-серверов состоит в том, что они саморегистрируемы. Это упрощает размещение информации в системном реестре и получение ее оттуда. DLL СОМ-сервера должна поддерживать входы DllRegisterServer и DllUnregisterServer для регистрации и дерегистрации сервера соответственно. Вы можете вызвать эти функции с помощью инструмента regsvr32 .exe. Для регистрации Bank.dll выполните команду regsvr32 path\bank.dll Для дерегистрации воспользуйтесь опцией командной строки /и: regsvr32 /и path\bank.dll Примеры этой книги включают пакетные файлы для регистрации и дерегистрации серверов. Для того чтобы посмотреть их, дважды щелкните на файле unreg_bank.bat в каталоге Bank. При этом сервер должен быть дерегистрирован, что должно быть подтверждено соответствующим окном сообщения. Попытайтесь теперь запустить программу-клиент, например клиент Visual Basic BankClientVb.exe,— вы должны получить сообщение об ошибке, показанное на рис. 4.14. Рис. 4.14. Создание экземпляра объекта незарегистрированного сервера невозможно Просмотрите информацию об "Account Class" в реестре с помощью OLE/COM Object Viewer (при необходимости обновите информацию ) — вы попросту не найдете ее там. Восстановите информацию, дважды щелкнув на файле reg_bank.bat. Теперь вы можете запустить программу-клиент вновь, и она должна нормально работать. Резюме В этой главе освещается множество фундаментальных вопросов, которые могут быть разделены на две основные группы. К первой группе относятся вопросы, связанные с разработкой клиентов СОМ с помощью Visual Basic и Visual C++. Вторая группа — это вопросы, касающиеся понимания того, что и как при этом делается. Самый важный материал, который следует запомнить, — базовая программная модель клиента, вызывающего СОМ-сервер. Вы должны создать экземпляр объекта СОМ-класса, что в C++ можно сделать, вызвав CoCreatelnstance[Ex], а в Visual Basic— оператор New или функцию CreateObject. При этом вы получите ссылку на интерфейс. Посредством этой ссылки (указателя в C++) вы можете вызывать методы. По окончании работы ссылку следует освободить, вызвав Release в C++; в Visual Basic освобождение происходит либо при выходе из области видимости, либо путем непосредственного присвоения глобальной ссылке Nothing. СОМ-класс может поддерживать несколько интерфейсов, доступ к которым обеспечивает механизм Querylnterfасе. Мы также рассмотрели некоторые программные детали, такие как использование Unicode или BSTR для строк СОМ, а также кратко обсудим, как можно использовать системный реестр для хранения информации о СОМ-классах, включая путь к СОМ-серверу. В следующих двух главах речь пойдет о реализации СОМ-серверов. В главе 5, "C++ и СОМ" рассматриваются некоторые детали протокола СОМ и его реализации на C++. Программисты на Visual Basic могут либо пропустить эту главу, либо бегло просмотреть ее, почерпнув основные идеи. В главе 6, "СОМ-серверы контекста приложения" рассмотрена реализация СОМ-сервера на C++ (сложный вариант) и Visual Basic (упрощенный вариант). Если вы — программист на C++, не отчаивайтесь, так как глава 7, "Active Template Library" познакомит вас со средством, резко упрощающим реализацию СОМ-серверов на C++.
Глава 5 C++ и СОМ Эта глава предназначена для программистов на C++. В данной главе мы попытаемся ответить на два главных вопроса, первый из которых звучит так: "Почему я должен заниматься этим СОМ — не лучше ли продолжать писать обычные приложения C++?" Если вы сумели ответить для себя на первый вопрос положительно, перед вами встанет второй вопрос: "Каким образом это сделать, т.е. как реализовать СОМ на C++? Как вы смогли убедиться в главе 4, "Клиенты СОМ: концепции и программирование", написать клиент на C++ достаточно просто, но написать сервер — это совершенно другая история. Есть целая инфраструктура, которую должен обеспечивать сервер. В случае C++ эта инфраструктура не полностью абстрагирована (как в случае Visual Basic). Конечно, имеются инструменты, которые позволяют избавить вас от значительной части тяжелой и монотонной работы, но для того чтобы эффективно использовать инструментарий, например, типа ATL, следует иметь ясное понимание того, что именно ATL может сделать вместо вас. Итак, в этой главе мы опишем СОМ как объектно-ориентированное основание компонентного программного обеспечения. Компонент имеет множество характеристик, но объект — еще больше. Компоненты обеспечивают базу для повторного использования программного обеспечения способом, к которому так стремятся объектно-ориентированные языки программирования типа C++ или Smalltalk, но который никогда полностью не достигается. Мы сравним объектные модели C++ и СОМ. Для того чтобы получить пользу от этой главы, вам необходимо ясное понимание C++ как объектно-ориентированного языка программирования. Конечно, для понимания концепций не требуется совершенное владение C++, но, конечно, для работы с примерами программ желательно иметь опыт работы с этим языком. По ходу дела мы рассмотрим основные принципы работы объектной модели C++. В частности, обратим ваше внимание на виртуальные функции и виртуальные таблицы C++, так как структура виртуальных таблиц C++ определяет бинарный формат интерфейсов СОМ. Это именно та спецификация, которая делает СОМ независимым от используемого языка программирования. Имеется ряд ключевых концепций, лежащих в основе СОМ, которые включают в себя представление об интерфейсах СОМ, роль глобально уникальных идентификаторов, интерфейс I Unknown и его место в согласовании интерфейсов и обеспечении счетчиков ссылок. Эти концепции являются частью протокола СОМ и могут быть приложены к любому общению между клиентами СОМ и объектами. В данном случае объект представляет собой часть приложения клиента, без вызова сервера и использования библиотек СОМ. Надеюсь, что повышенное внимание к базовым аспектам протокола СОМ сослужит вам службу в дальнейшем, позволив вам прийти к глубокому его пониманию. В следующей главе мы рассмотрим и другие особенности протокола СОМ, такие как фабрики классов, требующиеся при реализации СОМ-объектов на сервере.
После обсуждения основных свойств СОМ мы перейдем к детальным примерам программ. Вы увидите, насколько полезна возможность множественного наследования в C++ для разработки множественных интерфейсов. Объекты, компоненты и СОМ В этом первом разделе главы мы расскажем, что собой представляет объект и чем он отличается от компонентных объектов. И все это мы рассмотрим с точки зрения повторного использования кода. Компонентные объекты Объекты инкапсулируют данные и поведение в единое целое. Если бы не было объектов, нам бы пришлось иметь множество переменных и передавать их отдельным функциям. Однако группирование родственных данных и функций в объект позволяет создать упрощающую программирование абстракцию и расширить программную модель включением ваших собственных типов данных. СОМ расширяет эти возможности объектного структурирования. Компонентные объекты могут иметь множественные интерфейсы, каждый из которых поддерживает определенные возможности посредством групп родственных функций. СОМ определяет бинарный стандарт, позволяющий компонентным объектам, реализованным на одном языке программирования, быть вызванными клиентом, созданным с помощью другого языка программирования. Компонентное программное обеспечение Розовая мечта программной индустрии — создание повторно используемых компонентов, которые можно было бы связать в готовое приложение. В этом можно усмотреть аналогию с аппаратным обеспечением, когда компьютер, например, собирается из отдельных плат. Мечта эта вызвана как техническими, так и коммерческими причинами. Объектно-ориентированные языки программирования определяют объе к- ты на уровне исходных текстов программ, ограничивая тем самым потенциал широкомасштабного взаимодействия. Программное обеспечение в силу своей высокой ги б- кости часто изменяется, создавая значительные проблемы, связанные с несоответствием версий компонентов. Для создания взаимодействующих компонентов требуются промышленные стандарты, поддерживаемые множеством производителей. СОМ обеспечивает техническую основу компонентного программного обеспечения, а положение Microsoft в программной индустрии — поддержку стандарта СОМ сторонними производителями. Свидетельством такой поддержки является наличие о г- ромного количества управляющих элементов ActiveX, разработанных сторонними производителями, широкая поддержка СОМ в инструментах разработки приложений и планы по применению СОМ во многих промышленных разработках. На сегодняшний день имеется еще одна важная компонентная схема — CORBA (Common Object Request Broker Architecture — Общая архитектура брокеров объектных запросов), представляющая собой объектно-ориентированный стандарт взаимодействия, перешагнувший границы множества платформ. Этот стандарт очень полезен для интеграции старых приложений в современную распределенную среду. Хотя Java и представляет собой скорее язык программирования, чем компонентную схему, он также предоставляет многие аспекты компонентного программного обеспеч е- ния. Компоненты Java Beans работают в Java-программах с графическим интерфейсом пользователя аналогично управляющим элементам ActiveX в программах Windows, a распределенные компоненты Enterprise Java Beans подобны компонентам DCOM.
В программной промышленности идут пылкие дебаты о достоинствах той или иной технологии. Однако мы не будем в них вмешиваться, обсуждая только СОМ и СОМ+. В конце концов, рассматривайте эту архитектуру как выбранную вами для работы в мире Microsoft. Хотя СОМ реализована на разных платформах, но по природе своей она ближе всего к Windows. CORBA — это, в первую очередь, многоплатформенная технология. Как CORBA, так и СОМ в состоянии работать со многими языками. Java, естественно, фокусируется на языке Java. Component Object Model COM представляет собой основу, на которой строится современная системная архитектура Microsoft, обеспечивая следующее: ■ концепцию интерфейса, состоящего из групп родственных функций, которые клиент может использовать для получения сервисов сервера; ■ архитектуру, в которой объект может поддерживать множественные интерфейсы, указатели на которые клиент может получить с помощью вызова функции Querylnterface; ■ механизм счетчика ссылок, обеспечивающий доступность сервера до завершения работы всеми клиентами; ■ легкость управления памятью, позволяющая выделять память серверу, а освобождать — клиенту; ■ модель для получения расширенной информации об ошибках и состоянии программы; ■ механизм, при котором объекты могут одинаково обмениваться информацией как в пределах процесса, так и между процессами, и между машинами в сети; ■ механизм, позволяющий определенному приложению или DLL, реализующему сервис, быть динамически идентифицированным и загруженным в работающую систему; ■ механизм для динамического создания экземпляров объектов (фабрики классов). C++ и СОМ И C++, и СОМ поддерживают объектно-ориентированное программирование, хотя их цели, философия и структура существенно отличаются. C++ поддерживает объектно-ориентированное программирование в контексте языка программирования, в то время как СОМ является бинарным стандартом, созданным для поддержки взаимодействия компонентов, разработанных с помощью различных языков программирования, различными производителями. И C++, и СОМ разрабатывались с учетом вопросов эффективности работы. C++, в соответствии с традициями эффективности работы С, имеет по умолчанию статическое связывание, поддерживает встроенные (inline) функции, не содержит систем автоматической сборки мусора и т.п. СОМ не является промежуточным программным обеспечением и "уходит с дороги", как только между клиентом и компонентом устанавливается связь. СОМ использует бинарный формат виртуальных таблиц C++, упрощая реализацию объектов СОМ на C++. В этом разделе мы сравним объектные модели, лежащие в основе C++ и СОМ, с точки зрения: ■ классов и интерфейсов; ■ идентификации классов;
■ инкапсуляции; ■ создания объектов; ■ объектов класса; ■ времени жизни объектов; ■ версий и согласования интерфейсов; ■ механизма повторного использования; ■ распределенных объектов. Классы и интерфейсы И C++, и СОМ имеют понятие класса, из которого создаются экземпляры объектов. В C++ класс представляет как интерфейс, так и реализацию. Для определения интерфейса без реализации может использоваться абстрактный класс. В СОМ же фундаментальным понятием является интерфейс, определяющий соглашение без реализации. Класс может реализовать несколько интерфейсов. Таким образом, функциональность может быть разложена на ряд независимых интерфейсов. Имеется интересная взаимосвязь между множественными интерфейсами и множественным наследованием. В C++ класс содержит только один интерфейс и открытые функции-члены. В нем не предусмотрено никакой логической группировки функциональности в меньшие модули. Но если класс порожден от нескольких базовых классов, то базовые классы могут представлять эти подмодули. Java не поддерживает множественного наследования, но в нем реализована концепция независимого от класса интерфейса, а класс может содержать несколько интерфейсов. В этом плане объектные модели Java и СОМ весьма подобны. Концептуально Java превосходный язык для реализации СОМ-классов, и расширения Java, включенные фирмой Microsoft в Visual J++, преследуют именно эту цель. Идентификация классов В C++ классы представляют собой конструкции языка программирования и идентифицируют классы с помощью удобочитаемых имен. При этом конфликт имен — вполне распространенное явление, но разрешимое в контексте одного проекта. Механизм пространства имен (namespace) ANSI C++ предоставляет средство для управления именами классов, импортированных из различных независимых источников. Классы СОМ реализуют бинарные компоненты, которые должны быть уникальны в очень широкой области. Классы СОМ идентифицируются глобально уникальными большими бинарными числами. Точно так же уникальные бинарные числа идентифицируют интерфейсы СОМ и другие элементы, такие как категории компонентов или библиотеки типов. Инкапсуляция Основная цель C++ (как и любого другого объектно-ориентированного языка программирования) состоит в обеспечении механизма инкапсуляции. Обычно данные в программах C++ закрыты, и обращение к ним, и работа с ними осуществляются через открытые функции-члены класса. Однако такая инкапсуляция — не более чем соглашение, класс может также иметь открытые данные. Инкапсуляция в СОМ строже: данные объекта всегда закрыты и доступ к ним осуществляется исключительно через методы интерфейсов.
Создание объектов В C++ объект всегда является частью программы и создается с помощью конструктора. Конструктор для глобальных и автоматических переменных вызывается неявно; явный вызов конструктора выполняется при использовании оператора new для создания объекта в выделяемой памяти. Объекты СОМ могут создаваться в другом модуле (DLL или другом ЕХЕ) и требуют тщательно разработанного отдельного механизма. Системой времени выполнения СОМ вначале создается объект фабрики классов (class factory), а затем для создания экземпляра объекта СОМ, принадлежащего некоторому классу, используется метод интерфейса фабрики классов. Объекты класса В C++ могут применяться статические данные-члены класса. Статические данные принадлежат всему классу, а не отдельным его экземплярам. В СОМ существуют объекты класса. Но может быть только один объект класса, независимо от того, какое количество экземпляров объекта было создано. Объекты класса обычно используются для реализации фабрики классов. Время жизни объекта В C++ объекты существуют до тех пор, пока не будет вызван деструктор. Деструктор вызывается неявно при выходе объекта из области видимости или явно при вызове оператора delete. СОМ поддерживает механизм счетчика ссылок для управления временем жизни объекта. Если объект больше не используется в данном контексте, счетчик ссылок уменьшается, а при достижении им нулевого значения объект уничтожается. Программа-клиент отвечает за явный вызов функций для увеличения и уменьшения количества ссылок. Версии и согласование интерфейсов В C++ не предусмотрен механизм для управления версиями. В традиционной разработке программного обеспечения вопросы версий — одни из труднее всего решаемых. Рассмотрим существующий библиотечный компонент, используемый множеством потребителей. Добавление в библиотеку новых возможностей может вызвать неработоспособность существующих приложений, в результате потребуется как минимум перекомпоновать код. DLL могут быть обновлены без перекомпоновки, но DLL связана с именем файла, и перед операционной системой встанет задача поддержки различных версий одной DLL, используемых разными пользователями. Предположим, например, что клиент покупает новое приложение, использующее более новую версию DLL, чем применяемая уже имеющимся на машине клиента приложением. При установке нового приложения старая DLL может быть заменена новой, и старое приложение может перестать корректно работать. Имеется множество вариаций этого сценария, и все они одинаково печальны. С помощью согласования интерфейсов компоненты СОМ могут обновляться дополнительными интерфейсами, и новые приложения могут пользоваться новыми возможностями; старые приложения, ничего не зная о них, никогда не запросят новых интерфейсов и будут продолжать корректную работу. Эта возможность позволяет развивать как сервер, так и программы-клиенты, оставляя при этом работоспособными старые версии приложений.
В качестве примера рассмотрим эволюцию двух приложений Windows, использующих OLE. Как сервер, так и клиент поддерживают стиль OLE 1.0 составных документов, в котором редактирование внедренного объекта выполняется в отдельном окне. Теперь предположим, что сервер обновлен и поддерживает работу со внедренными объектами в контексте вызывающего приложения, т.е. редактирование будет происходить в окне клиента. Существующий клиент не в состоянии получить доступ к этой новой функции, но может продолжать работу с новым сервером в старом стиле. Теперь предположим, что клиент также обновлен для работы со внедренными объектами в контексте основного приложения. Теперь у нас есть старый и новый серверы и старый и новый клие н- ты. Новый клиент будет запрашивать у сервера доступ к дополнительным интерфейсам, поддерживающим контекстное редактирование внедренных объектов. Если они имеются (сервер обновлен), клиент будет использовать новые возможности сервера. Если же новый клиент обращается к старому серверу, то в этом случае запрос нового интерфейса будет неудачен, и клиент станет использовать старый способ работы в отдельном окне. Повторное использование C++ поддерживает повторное использование кода посредством наследования. Использование наследования — это механизм "белого ящика": реализация структур данных базового класса открыта посредством заголовочного файла, и программист должен быть хорошо осведомлен о семантике базового класса, поскольку в его компете н- цию входит перегрузка функций класса. СОМ поддерживает повторное использование посредством сдерживания/делегирования и агрегации. Оба этих механизма представляют собой "черные ящики". Метод сдерживания/делегирования более прост, но может вызвать большое повторение кода при большом количестве методов. Агрегация разрешает непосредственное использование методов повторно используемого объекта, но сложна для самостоятельной реализации. Отметим, что как MFC, так и ATL поддерживают агрегацию. Распределенные объекты Объектная модель C++ предназначена для отдельных программ. Имеется множество путей реализации распределенных программ с использованием C++, но все средства распределенности (гнезда (sockets), RPC, CORBA и другие) являются внешними по отношению к языку. СОМ по своей природе поддерживает распределенные объекты, которые могут размещаться: ■ в DLL, работающей в том же процессе, что и клиент; ■ в ЕХЕ, работающем на той же машине, что и клиент; ■ в ЕХЕ, работающем на другой машине (распределенная СОМ, или DCOM); ■ значительная часть этой книги посвящена различным аспектам DCOM. Фактически, основная цель СОМ+ состоит в обеспечении простоты реализации распределенных систем. Реализация классов СОМ с использованием C++ Дальше в этой главе будет подробно описана реализация классов СОМ с использованием C++. Знакомство с ней поможет вам лучше понять протокол СОМ. На практике вам вряд ли придется работать на таком низком уровне, поскольку высокоуров-
невый инструментарий типа ATL сделает всю базовую работу вместо вас, но здесь вы получите знания о том, как на самом деле работает СОМ. Даже если вы не заинтересованы в детальном изучении кода, все равно стоит прочесть этот раздел, поскольку здесь рассмотрены многие важные концепции, включая интерфейсы СОМ, глобально уникальные идентификаторы (GUID) и интерфейс lUnknown. Кроме того, здесь же будут рассмотрены фундаментальные концепции C++, относящиеся к виртуальным функциям и виртуальным таблицам. Пример объекта Account Мы будем работать с объектом банковского счета, подобным рассмотренному в предыдущей главе. Этот объект в качестве данных хранит состояние банковского счета и использует следующие методы интерфейса iAccount: ■ GetBalance; ■ Deposit. Позже мы расширим наш пример добавлением еще одного метода — Withdraw — в интерфейс IAccount и второго интерфейса — I Display. Интерфейсы СОМ Объекты СОМ состоят из одного или нескольких интерфейсов, каждый из которых содержит одну или несколько функций-членов, или методов. Данные объекта закрыты, и доступ к ним может осуществляться только посредством методов интерфейсов. По соглашению, имена интерфейсов начинаются с буквы "i". На рис. 5.1 показаны два интерфейса, поддерживаемых объектом Account. lUnknown IAccount Account Рис. 5.1. Объект Account lUnknown является стандартным интерфейсом (который должен поддерживаться любым СОМ-объектом) и имеет следующие методы: ■ Querylnterface; ■ AddRef; ■ Release. IAccount — пользовательский интерфейс, имеющий следующие методы: ■ GetBalance; ■ Deposit. Бинарное представление интерфейсов Доступ к методам интерфейса производится через указатель на интерфейс. Указатель на интерфейс указывает на область памяти экземпляра объекта, который содержит указатель на таблицу виртуальных методов объекта, или, если говорить проще, на виртуальную таблицу (vtable) объекта. Таблица содержит массив указателей на функ-
ции, которые реализуют методы интерфейса. Таблица связана с "классом", соответствующим объекту — для всех экземпляров объектов имеется одна виртуальная таблица (рис. 5.2). plnterface »- Экземпляр объекта vptr Закрытые данные viable ^ ч код Рис. 5.2. Виртуальная таблица представляет интерфейсы Представление интерфейсов в C++ СОМ разработана так, что бинарное представление интерфейсов в точности то же, что и у стандартной виртуальной таблицы, используемой большинством компиляторов C++. Объявим абстрактный класс (с чисто виртуальными функциями) для определения интерфейса. class IAccount: public IUnknown { public: virtual HRESULT GetBalance(int* nBal) = 0; virtual HRESULT Deposit(int amount) = 0; >; Объявим конкретный класс, порожденный от класса-интерфейса для реализации последнего. class CAccount: public IAccount { public: HRESULT GetBalance(int* nBal); HRESULT Deposit(int amount); private: int m_nBalance; }; Функции-члены интерфейса могут вызываться непосредственно с помощью указателя на интерфейс: IAccount * pBalance; int balance; hr = pAccount->GetBalance(&balance); Пример виртуальных функций Виртуальные функции C++ демонстрируются в следующей простой программе virtdemo.cpp, содержащейся в каталоге Chap5\Demos\VirtDemo (в каталоге ChapSWirtDemo имеется окончательная версия программы). Постройте консольное приложение и запустите его.
// virtdemo.cpp #include <iostream.h> class В { public: void f (); void g() ; private: long x; }; class D : public В { public: void f () ; void g() ; private: long y; }; void B::f() {cout « "B::f" « endl;} void B::g() {cout « "B::g" « endl;} void D::f() {cout « "D::f" « endl;} void D::g() {cout « "D::g" « endl;} int mam() { В b, *pb; D d, *pd; pb = &b; pd = &d; b.f (); d.f (); pb->f (); pd->f (); pb = pd; // верно?? pb->f(); pd = pb; // верно?? cout « "size В = " « sizeof(В) « endl; cout « "size D = " « sizeof(D) « endl; return 0; } Задача 1. Перед тем как приступать к построению, предскажите, о каких ошибках сообщит компилятор. Прокомментируйте ошибочные строки и соберите проект заново. 2. Перед запуском программы предскажите, что именно выведет эта программа, обращая особое внимание на второй вызов pb->f () после присвоения указателя. 3. Каким образом следует изменить определение базового класса, для того чтобы получить ожидаемый вывод от оператора pb->f () после переприсвоения указателя, дабы он указывал на объект D?
Решение 1. Второе присвоение указателя неверно. Если бы такое присвоение было допустимо, вы могли бы вызвать функцию-член порожденного класса с помощью указателя на базовый объект и получили бы аварийное завершение работы приложения. Компилятор C++ отслеживает такие ситуации. Закомментируйте эту строку и скомпилируйте приложение заново. 2. Вы можете ожидать, что, когда указателю присваивается новое значение, для того чтобы он указывал на объект D, вы получаете "D-версию" вызываемой функции. Однако в обоих случаях упорно вызывается "в-версия", что и показывает вывод программы: В: :f D: :f В: :f D: :f В: :f size В = 4 size D = 8 Мы имеем статическое связывание, и pb->f () всегда вызывает "в-версию" функции, поскольку тип указателя в*. 3. Для получения желательного для нас поведения программы, объявите функцию в базовом классе как виртуальную (virtual void f О ;), перекомпилируйте программу и запустите ее вновь. Теперь используется динамическое связывание, и когда указатель указывает на объект D, вызывается D-версия функции: В: :f D: :f В: :f D: :f D: :f size В = 8 size D = 12 Размер объекта при этом увеличивается на 4 байта, поскольку каждый экземпляр объекта теперь содержит указатель vptr на виртуальную таблицу — взгляните на рис. 5.3, а после этого вернитесь к структуре виртуальной таблицы, показанной на рис. 5.2. nh ъ \JU W d vptr X У vtable Указатель на f Указатель на g Рис. 5.3. К экземпляру объекта добавляется указатель на таблицу виртуальных функций Глобально уникальные идентификаторы Интерфейсы и другие элементы СОМ требуют идентификационного механизма, который бы предотвратил возможные коллизии имен. Распределенная вычислител ь- ная среда (Distributed Computing Environment — DCE) Open Software Foundation предоставляет решение в виде концепции "универсально уникального идентификатора"
(UUID), который представляет собой 128-битовую величину, генерируемую таким образом, чтобы гарантировать ее уникальность. Microsoft адаптировала этот механизм для СОМ, назвав идентификатор глобально уникальным (GUID). В Visual Studio имеется утилита guidgen, генерирующая GUID (и ее аналог uuidgen, запускающейся из командной строки). Microsoft поддерживает возможность выделения блока идентификаторов. GUIDGEN Вы можете добавить эту утилиту в меню Tools Visual Studio, для чего следует воспользоваться командой меню Tools^Customize (сама программа размещается в Visual Studio— в файле Microsoft Visual Studio\Common\Tools\Guidgen.exe). Выберите формат, в котором вы хотели бы получить GUID и щелкните на кнопке Сору. При этом GUID будет помещен в буфер обмена, из которого вы сможете вставить его в свой код (рис. 5.4). // {BA3F2E41-FB4E-lld3-8011-FDB9B3A18E30} static const GUID «name» = { 0xba3f2e41, 0xfb4e, 0xlld3, { 0x80, 0x11, Oxfd, 0xb9, 0xb3, Oxal, 0x8e, 0x30 } }; Рис. 5.4. GUIDGEN из состава Visual Studio IUnknown ы Query Interface lUnknown является фундаментальным интерфейсом COM, который должен поддерживаться каждым компонентным объектом, iUnknown обеспечивает механизм получения указателя на любой другой интерфейс, поддерживаемый объектом, посредством метода Querylnterface. В функцию Querylnterface вы передаете идентификатор интерфейса (GUID, представляющий интерфейс), который вы хотите получить. Если объект поддерживает интерфейс, вы получите указатель на него. Если интерфейс объектом не поддерживается, вы получите результат hresult, указывающий, что интерфейс не найден, а в качестве указателя на интерфейс — значение NULL. Таким образом, если вы можете получить указатель на определенный интерфейс, то гарантированно можете вызывать любой метод этого интерфейса, что не верно для некоторых объектных систем. Например, в Win32 API имеется множество дескрипто-
ров. Эти дескрипторы представляют собой непрозрачные идентификаторы для различных типов объектов, но нет никакой гарантии, что если вы получили дескриптор, то он корректен. Следовательно, для того чтобы приложение с использованием Win32 API работало стабильно и корректно, перед передачей дескриптора функции следует убедиться в его корректности. Такой код в результате оборачивается потерями времени программиста, памяти и производительности программы. Счетчики ссылок Кроме этого, lUnknown поддерживает счетчики ссылок с помощью двух методов: ■ AddRef, увеличивающего значение счетчика; ■ Release, уменьшающего значение счетчика и удаляющего объект по достижении счетчиком нулевого значения. Счетчики ссылок очень важны в СОМ, поскольку объект может использоваться в различных местах и должен уничтожаться после того, как все пользователи завершили работу с ним. Фабрики классов Класс определяется в других программах уникальным идентификатором класса (CLSID), частным случаем GUID. Для создания экземпляра объекта по его CLSID вам потребуется фабрика классов. Фабрика классов предоставляется в виде отдельного объекта, реализующего интерфейс iciassFactory. Мы рассмотрим их более подробно в следующей главе. Фабрика классов не требуется объекту, который создается локально, без обращения к CLSID. В СОМ имеется множество функций API, предназначенных для создания экземпляров специальных типов объектов; кроме того, методами для создания э к- земпляров объектов обладают некоторые интерфейсы. Вы можете реализовать специальные создающие функции для ваших объектов, не имеющих CLSID. Реализация СОМ-объекта При реализации СОМ-объекта без CLSID на C++ предполагаются следующие шаги. 1. Объявление абстрактного класса (с чисто виртуальными функциями) для определения интерфейсов, поддерживаемых объектом, включая lUnknown. 2. Объявление конкретного класса-наследника интерфейсного класса для реализации объектов, поддерживающих интерфейс. 3. Реализация конкретного класса, который в свою очередь реализует интерфейс lUnknown и все другие интерфейсы, поддерживаемые объектом. 4. Реализация функций создания объекта специального назначения, которые будут создавать экземпляры ваших объектов и возвращать указатели на интерфейс (эти функции будут заменены механизмом фабрики классов, если объект имеет CLSID и реализуется на сервере). 5. Определение IID (идентификатор интерфейса) для каждого пользовательского интерфейса (вы можете воспользоваться для этой цели инструментом GUIDGEN).
Определение интерфейса Имеется ряд способов определения интерфейса. Первый из них — использовать стандартные макросы, которые могут определить интерфейс на языке С или C++. Использовать макросы в C++ достаточно просто, поскольку механизм использования виртуальных таблиц встроен в C++. В С ситуация сложнее, так как здесь виртуальные таблицы должны создаваться явным образом с использованием таблиц указателей на функции. Второй подход заключается в непосредственном использовании специфич е- ского синтаксиса C++. Третий же подход состоит в применении языка определения интерфейсов (IDL). Мы будем использовать комбинацию синтаксиса C++ и стандартных макросов. При использовании IDL определение интерфейса упрощается благодаря специализированному инструментарию — компилятору RPC (компилятор RPC фирмы Microsoft midl.exe входит в состав Visual C++). Компилятор MIDL существенно упрощает создание пользовательских интерфейсов, реализуемых в различных выполняемых файлах. MIDL и IDL обсудим несколько позже, а пока вспомним, что в главе 4, "Клиенты СОМ: концепции и программирование" мы уже сталкивались с примером IDL. Определение интерфейса IAccount Этот интерфейс определяется абстрактным базовым классом. // bank.h class IAccount : public IUnknown { public: // Методы IAccount STDMETHOD(GetBalance)(int* pBalance) = 0; STDMETHOD(Deposit)(int amount) = 0; }; Абстрактный базовый класс IUnknown (определенный в стандартном заголовочном файле) определяет чисто виртуальные функции метода iUnknown. Если вы хотите увидеть, где определены различные классы и макросы, можете воспользоваться Visual C++ Source Browser (щелкнув правой кнопкой мыши на интересующем вас символе и выбрав в контекстном меню Go to Definition Of...). Стандартные макросы Несколько стандартных макросов определено в файле objbase.h. ■ STDMETHOD (method) используется для определения метода, возвращающего значение hresult (применяется в заголовочном файле). ■ stdmethod_(type,method) используется для определения метода, возвращающего значение некоторого другого типа (применяется в заголовочном файле). ■ stdmethodimp представляют собой соответствующие макросы для использования в файлах реализации. ■ refiid представляет собой постоянную ссылку на идентификатор интерфейса. ■ hresult представляет собой long, стандартный тип возвращаемого значения большинства СОМ-функций. //Win32 versions #define STDMETHODCALLTYPE stdcall
#define STDAPICALLTYPE stdcall #define STDMETHOD(method) virtual HRESULT \ STDMETHODCALLTYPE method #defme STDMETHOD_ (type, method) virtual type \ STDMETHODCALLTYPE method #define STDMETHODIMP HRESULT STDMETHODCALLTYPE #defme STDMETHODIMP_(type) type STDMETHODCALLTYPE #define REFIID const IID & Соглашения об именах интерфейсов По принятому соглашению, имена интерфейсов в СОМ начинаются с символа I, за которым следует (с прописной буквы) имя, в котором могут использоваться как прописные, так и строчные буквы (в качестве примеров можно привести lUnknown и IClassFactory). Другое соглашение действует при создании типов указателей на интерфейсы с помощью оператора typedef — при этом из имени интерфейса удаляется первый символ I, добавляются LP (от "long pointer" — тяжкое наследие 16-битового режима Windows) и остаток имени, набранного в верхнем регистре, например: typedef IAccount FAR * LPACCOUNT; В принципе, мы не будем заниматься созданием типов для своих интерфейсов, но нам часто будут встречаться указатели на стандартные интерфейсы (lpunknown, LPDISPATCH И др.). Реализация интерфейса Теперь мы рассмотрим то, как реализуются интерфейсы в СОМ. Сюда включается не только реализация методов, определенных нами, но и стандартных методов интерфейса lUnknown. Мы продолжим работу с нашим примером объекта Account и создадим работоспособную программу. После изучения простейшего начального варианта примера добавим к нему еще один метод (что очень просто) и еще один интерфейс (что уже более интересно). Все исходные тексты можно найти в каталоге Chap5\BankCom. Есть два пути решения поставленной задачи. Конкретный класс Интерфейс определяется абстрактным классом, а для его реализации требуется класс конкретный. // Account.h : Описание CAccount class CAccount : public IAccount { public: CAccount() { m_nRef = 0; m_nBalance = 100; } public: // Методы lUnknown STDMETHOD(Querylnterface)(REFIID, void**); STDMETHOD_(ULONG, AddRef)(); STDMETHOD (ULONG, Release)();
// Методы IAccount STDMETHOD(GetBalance)(int* pBalance); STDMETHOD(Deposit)(int amount); protected: ULONG m_nRef; // Количество ссылок int m_nBalance; // Банковский баланс }; Заметьте, что у нас имеется два члена-данных: один из них специфичен для данного класса и хранит банковский баланс, а другой является стандартным для любой реализации СОМ-класса и содержит счетчик ссылок. Реализация Querylnterface Мы должны реализовать методы I Unknown. Вначале приступим к методу Querylnterface. Входной параметр метода — идентификатор интерфейса, выходной — указатель на интерфейс. В данном случае поддерживаются только интерфейсы lUnknown и IAccount. При возврате указателя на интерфейс создается новая ссылка на объект, и мы должны увеличить счетчик ссылок, для чего будем использовать функцию AddRef. // Account.cpp : Реализация CAccount #include "stdafx.h" #include "guid.h" #include "bank.h" #include "account.h" // CAccount STDMETHODIMP CAccount::Querylnterface(REFIID iid, void** ppv) { if (iid == IID_IUnknown) *ppv = (IAccount*) this; else if (iid == IID_IAccount) *ppv = (IAccount*) this; else { *ppv = NULL; return E_NOINTERFACE; } AddRef () ; return NOERROR; Реализация счетчика ссылок Оставшаяся нереализованной часть функциональности lUnknown — это работа со счетчиком ссылок, включающая в себя функции AddRef и Release. Функция AddRef тривиальна и просто увеличивает значение счетчика. Release немного интереснее. Мы уменьшаем значение счетчика, и если оно достигает нуля, то уничтожаем объект путем удаления указателя this. Кажется несколько странным то, что мы уничтожаем объект в его собственной функции, но все наши действия вполне корректны и соответствуют тому, что мы должны сделать.
STDMETHODIMP_(ULONG) CAccount::AddRef() { return ++m_nRef; } STDMETHODIMP_(ULONG) CAccount::Release() { if(—m_nRef == 0) { delete this; Trace("Object destroyed", "CAccount"); return 0; } return m_nRef; } Для демонстрационных целей у нас имеется функция Trace, которая может быть реализована в виде окна сообщений, как показано ниже, либо выводить информацию в журнальный файл, либо вы можете делать с ней то, что вы сочтете нужным. //stdafx.cpp #mclude "stdafx.h" void Trace(const char* msg, const char* title) { ::MessageBox(NULL, msg, title, MB_OK); } Реализация методов IAccount Теперь приступим к реализации специфичных методов нашего интерфейса. Решение этой задачи не нуждается в комментариях. STDMETHODIMP CAccount::Deposit(int amount) { m_nBalance += amount; return S_OK; } STDMETHODIMP CAccount::GetBalance(int* pBalance) { *pBalance = m_nBalance; return S_OK; } Функция создания объекта При создании экземпляра СОМ по заданному CLSID объект должен создаваться общими механизмом, известным как "фабрика классов", который мы обсудим в главе 6, "СОМ-серверы контекста приложения". Наша программа-клиент из главы 4, "Клиенты СОМ: концепции и программирование" создавала объект с использованием механизма фабрики классов, когда вызывала функцию CoCreatelnstance для создания экземпляра объекта, принадлежащего определенному классу СОМ. Создание СОМ-объекта, не имеющего CLSID, выполняется с помощью функции специального назначения. Ее задача — создать объект и вернуть соответствующий указатель на интерфейс. Новый интерфейс начинает работу со счетчиком ссылок, равным 1, что может быть достигнуто вызовом функции Query Inter face, которая,
как мы видели, вызывает AddRef. Если Querylnterfасе терпит неудачу то, вы должны удалить вновь созданный объект. Отслеживающий код сообщает о создании нового объекта. BOOL CreateAccount(IAccount** ppAccount) { HRESULT hr; if (ppAccount == NULL) return FALSE; // Создание объекта CAccount* pAccount = new CAccount; if (pAccount == NULL) return FALSE; // Получение интерфейса с неявным вызовом AddRef hr = pAccount->QueryInterface(IID_IAccount, (void**) ppAccount); if (SUCCEEDED(hr)) { Trace("Object created", "CAccount"); return TRUE; } else { delete pAccount; return FALSE; } } Состояние COM и сообщение об ошибках Наш код включает использование состояния СОМ и механизм сообщения об ошибках. Большинство функций COM API и методов интерфейсов возвращают значение типа HRESULT, как показано в методах интерфейса IAccount. HRESULT представляет собой просто 32-битовое целое число с той же структурой, что и коды ошибок Win32. Старший 31-й бит представляет собой бит, указывающий на наличие (если он равен 1) или отсутствие (при 0 значении) ошибки. Биты 31—16 указывают, к какой группе кодов состояния принадлежит полученный результат (Microsoft оставляет за собой определение этих кодов). И, наконец, биты 15—0 хранят код состояния, который в точности описывает, что именно произошло. Этот код может быть специфицирован любым СОМ, определяющим интерфейс. Коды могут быть приложены только к определенному интерфейсу. Поскольку имеется множество состояний успешного и неудачного завершения функции, для них также действуют соглашения об именах. Е_хххх означает неудачное завершение функции, a s_xxxx — что функция выполнена успешно. Так, s_ok означает код для описания общего успешного завершения функции, a e_fail — общего неудачного завершения. Со множеством подобных кодов вы познакомитесь в процессе работы с функциями и методами COM API. COM SDK содержит макросы succeeded и failed, которые используются для проверки успешного или неудачного завершения функции. Вот фрагмент типичного кода: HRESULT hr = somefunction(); if (FAILED(hr)) { MessageBox(NULL,"failed. ..",...); } else // Вызов был успешно завершен
Использование объекта СОМ Мы полностью описали все этапы определения интерфейсов СОМ, их реализации и обеспечения механизма для создания объекта СОМ, поддерживающего эти интерфейсы. Для полноты картины следует рассмотреть использование созданного нами объекта СОМ. Процедура использования такого СОМ-объекта без CLSID аналогична описанной в главе 4, "Клиенты СОМ: концепции и программирование", за тем лишь исключением, что вместо функции CoCreatelnstance, применяемой для создания экземпляра объекта, для создания объекта мы пользуемся нашей специализированной функцией. Следуйте приведенной ниже инструкции. 1. Вызовите функцию создания СОМ-объекта для получения указателя на интерфейс. 2. Вызовите методы объекта, используя указатель на интерфейс. 3. По окончании работы вызовите метод Release. 4. При необходимости работы с методами другого интерфейса объекта воспользуйтесь функцией Querylnterface. 5. Убедитесь в том, что вы освободили все дополнительные интерфейсы, полученные вами. Тестовые программы для СОМ-объектов Имеются различные стратегии написания тестовых программ для СОМ-объектов. ■ Написание консольного приложения (интерфейс командной строки). ■ Создание графического интерфейса пользователя с использованием MFC. ■ Создание графического интерфейса пользователя с использованием Visual Basic. В главе 4, "Клиенты СОМ: концепции и программирование" мы использовали все три подхода и будем продолжать применять их до конца книги. Консольные приложения, как и приложения Visual Basic, очень просты. Несложно создать и простое диалоговое приложение с помощью MFC, но для работы с MFC вы должны знать основы работы с MFC и соответствующий инструментарий, такой, например, как AppWizard, ClassWizard и редакторы ресурсов. В этой книге не предполагается, что читатель досконально изучил MFC, хотя для того, чтобы написать или модифицировать простую диалоговую программу, много знаний и не требуется. Впрочем, для приводимых в книге примеров и упражнений имеются заранее разработанные тестовые приложения. Программа для работы с объектом Account Постройте и запустите программу из каталога Chap5\BankCom\Stepl. Это диалоговая MFC-программа, содержащая код как сервера, так и клиента рассмотренного ранее СОМ-объекта. Пользовательский интерфейс, показанный на рис. 5.5, содержит кнопки Withdraw и Show для тестирования дополнительного метода iAccount и второго интерфейса iDisplay. Запустите программу и изучите ее код.
Рис. 5.5. СОМ-объект Account Дополнительные интерфейсы Реализация СОМ-объекта с дополнительными интерфейсами достаточно проста. Вы должны объявить абстрактный класс (с чисто виртуальными функциями) для определения каждого дополнительного интерфейса, поддерживаемого объектом. Например, следующий код объявляет новый интерфейс I Display с методом Show: class IDisplay : public IUnknown { public: // Методы IDisplay STDMETHOD(Show)() = 0; }; Вам также необходимо создать идентификатор для нового интерфейса. Это можно сделать с помощь инструмента guidgen. Теперь вы используете множественное наследование для создания вашего конкретного класса, порожденного всеми поддерживаемыми объектом интерфейсами. Например, класс CAccount происходит от интерфейсов I Account и IDisplay. class CAccount: public IAccount, IDisplay { Вы должны изменить реализацию Querylnterface так, чтобы метод позволял получить любой из дополнительных интерфейсов. Эта задача включает в себя необходимость преобразования типов, поскольку у разных интерфейсов виртуальные таблицы различны. Следующий код представляет собой корректную реализацию метода Querylnterface объекта Account С интерфейсами IAccount И IDisplay: STDMETHODIMP CAccount::Querylnterface(REFIID iid, void** ppv) { if (iid == IID_IUnknown) •ppv = (IAccount*) this; else if (iid == IID_IAccount) •ppv = (IAccount*) this; else if (iid == IID_IDisplay) *ppv = (IDisplay*) this; else { *ppv = NULL; return E NOINTERFACE;
} AddRef () ; return NOERROR; } Полностью с примером СОМ-класса с несколькими интерфейсами можно познакомиться в каталоге Chap5\BankCom\Step2. Резюме Как C++, так и СОМ поддерживают объектно-ориентированное программирование, хотя их цели, философия и структура различаются. Интерфейс СОМ представляет собой группу родственных функций, определяющую определенные функциональные возможности, поддерживаемые объектом. Бинарное представление объектов СОМ идентично механизму виртуальных таблиц, используемому в большинстве компиляторов C++, что существенно упрощает реализацию интерфейсов СОМ на языке C++. Глобально уникальные идентификаторы (GUID) представляют собой 128- битовые величины, однозначно определяющие элементы СОМ и препятствующие возникновению коллизий имен, iunknown является фундаментальным интерфейсом, который должен поддерживаться всеми компонентными объектами. Метод Querylnterface используется для получения указателя на другие интерфейсы, поддерживаемые объектом. Методы AddRef и Release применяются для реализации технологии счетчика ссылок СОМ-объектов. В этой главе мы реализовали СОМ-класс, но не СОМ-сервер. В следующей главе мы реализуем СОМ-сервер, работающий в контексте процесса (DLL), используя как Visual C++, так и Visual Basic.
Глава 6 СОМ-серверы контекста приложения В предыдущей главе был рассмотрен СОМ-класс без CLSID. Такой класс вызывается клиентом непосредственно и не требует библиотек времени выполнения СОМ СОМ просто предоставляет протокол, связывающий объект и его клиент. Если же объект представляет собой сервер, расположенный в DLL или отдельном ЕХЕ-файле, СОМ вызывается в процессе работы для осуществления соединения В этой главе мы рассмотрим, как реализуются СОМ-классы в DLL. Особенно важной темой этой главы будет интерфейс iciassFactory, который обеспечивает стандартный механизм создания СОМ объектов. Мы рассмотрим процедуру создания приложением объекта, реализованного в DLL, и выполним полную реализацию СОМ класса в DLL. Для изучения классов в нашей системе мы воспользуемся OLE/COM Object Viewer, а для хранения и получения информации о СОМ-классах и интерфейсах — системным реестром. Иллюстрируя работу СОМ, мы будем работать на низком уровне. Затем мы рассмотрим решение тех же задач с помощью Visual Basic, а в следующей главе расскажем об использовании ATL для упрощения реализации СОМ серверов на C++. Концепции СОМ-сервера В этом разделе мы рассмотрим некоторые базовые концепции СОМ- серверов. Многие из этих концепций применимы независимо от того, реализован сервер как DLL или как ЕХЕ, и отличаются только некоторыми деталями реализации В следующем разделе мы детально рассмотрим код, необходимый для реализации DLL-сервера. Локальная/удаленная прозрачность СОМ-классы (в отличие от чисто локальных, известных только в пределах приложения) реализуются на серверах. Сервер, работающий в контексте основного приложения {in-process server) располагается в DLL, которая отображается в адресное пространство клиента. Локальный сервер запускается как отдельный ЕХЕ в своем собственном адресном пространстве. Удаленный сервер работает как отдельный ЕХЕ на другой машине, что обеспечивается технологией DCOM, реализованной в NT 4.0. Локальная/удаленная прозрачность позволяет клиенту обращаться и использовать серверы независимо от их размещения. То, как работает сервер (в контексте приложения, локально или удаленно), называется контекстом выполнения {execution context).
Фабрика классов Экземпляр СОМ-класса без CLSID, вызываемый его клиентом непосредственно, может быть создан с помощью специальной создающей функции, известной приложению. Такой объект был описан в главе 5, "C++ и СОМ". При реализации классов в серверах для создания экземпляров объектов используются фабрики классов. Фабрика классов представляет собой объект, поддерживающий специальный, широко известный в СОМ-интерфейс iciassFactory. Система времени выполнения СОМ может запросить этот интерфейс, который имеет два метода: ■ Create Instance, создающий экземпляр объекта и возвращающий указатель на интерфейс, ■ LockServer, предоставленный для поддержки счетчика блокировок, который может использоваться для удержания сервера в памяти даже в том случае, если временно отсутствуют экземпляры объектов, благодаря чему повышается общая производительность. Начальная загрузка объекта Вспомним, что сервер состоит из одного или нескольких классов. Каждый класс поддерживает один или несколько интерфейсов, реализующих определенные возможности класса. Отдельный класс используется для реализации интерфейса фабрики классов. Если мы попытаемся реализовать фабрику классов как дополнительный интерфейс для нашего класса, то вынуждены будем создать экземпляр этого объекта для получения фабрики классов, которая позволит создать нам экземпляр нашего объекта (что-то вроде сейфа, запертого на ключ, находящийся в этом сейфе, — прим. перев.). На рис. 6.1 изображена схема фабрики классов. Сервер о {J Г\ {J г- — — lAccount lUnknown ICIassFactory lUnknown Класс Фабрика классов Рис. 6.1 Фабрика классов Объект класса Фабрику классов иногда называют просто объектом класса (class object). Для каждого класса имеется ровно один объект класса. Объект класса может использоваться для представления данных класса, подобно статическим членам в C++. Объект класса
создается непосредственно СОМ. Объект класса может поддерживать множественные интерфейсы. Могут быть ситуации, когда необходим лишь единичный объект, и тогда объект класса не требует реализации iclassFactory. Идентификаторы класса и системный реестр Компоненты идентифицируются уникальным идентификатором класса (CLSID) — специальным видом 128-битового GUID. Записи в системном реестре устанавливают связь между идентификатором класса и модулем (DLL или ЕХЕ), реализующим соответствующий класс. Системный реестр является базой данных Windows, в которой хранится информация о системной конфигурации. Клиент, знающий идентификатор класса компонента, может запросить у СОМ доступ к компоненту. Часть Windows, известная как Service Control Manager (SCM, произносится как "scum" ("скам" дословно переводится как "отбросы", — прим. перев.)), выполняет всю работу по поиску и запуску сервера, созданию объекта, установке локальной/удаленной прозрачности и возвращению указателя на интерфейс. После того как указатель на интерфейс передан клиенту, СОМ уходит в сторону (за исключением случая локальной/удаленной прозрачности, если сервер не работает в контексте процесса клиента). Структура компонента Компоненты СОМ имеют одинаковую базовую структуру независимо от того, реализованы они как DLL или как ЕХЕ, однако некоторые важные детали все-таки отличаются. ■ Идентификатор класса компонента. ■ Запись в системном реестре, связывающая идентификатор класса с модулем сервера, реализующего компонент. ■ Реализация объекта фабрики классов, поддерживающего интерфейс IClassFactory. ■ Механизм, который СОМ может использовать для доступа к фабрике классов серверы. ■ Механизм выгрузки, упрощающий удаление сервера из памяти после того как он прекратил обслуживание объектов. Реестр Системный реестр представляет собой центральное хранилище информации о настройках Windows. Кроме того, в реестре Win32 может храниться информация о приложениях, которая ранее размещалась в .iNi-файлах. Каждая часть информации идентифицируется ключом, который может иметь связанное с ним значение. Размещение информации иерархическое, т.е. ключ может содержать другие ключи. Системный реестр организован в виде деревьев, называемых ветвями3. Вот наиболее важные корневые ветви: ■ hkeyjjsers содержит настроечную информацию всех пользователей компьютера; ■ hkey_current_user представляет настроечную информацию текущего пользователя (ранее содержавшуюся в win. ini и пользовательских . INi-файлах); J Дословно — hives, т.е. улья, рои Однако в данном случае перевод "ветви " более подходит к древовидной структуре системного реестра. — Прим. перев.
■ hkey_local_machine содержит информацию о конфигурации аппаратного и системного программного обеспечения (ранее хранившуюся в system.ini); ■ hkey_class_root содержит информацию, необходимую для приложений- оболочек (типа File Manager) и приложений OLE. Редактор системного реестра Записи системного реестра могут быть просмотрены и отредактированы с помощью редактора системного реестра Registry Editor regedit.exe. Редактором системного реестра следует пользоваться осторожно. Все вносимые изменения сохраняются сразу и без возможности отмены. Использование команд экспортирования и импортирования в меню Registry — правильное решение (только не забывайте, что при восстановлении реестра не удаляются новые ключи). Запустить редактор системного реестра вы можете с помощью меню Start^Run (введя имя программы — regedit). Кроме того, запустить редактор системного реестра можно из OLE/COM Object Viewer с помощью команды меню File^Run The Registry Editor. Файлы записей системного реестра Информацию в системный реестр можно внести и с помощью файлов записей системного реестра, имеющих расширение . reg. Дважды щелкните на таком файле для запуска редактора системного реестра, который внесет данные из файла в реестр. Файл начинается со служебного слова regedit, за которым следуют строки, содержащие имена ключей и соответствующие им значения, которые должны быть внесены в реестр. Подключи отделяются от своих родительских ключей символом обратной косой черты (\), а значения отделены от ключей знаком равенства (=). Вот пример файла системного реестра: REGEDIT HKEY_CLASSES_ROOT\Account.Lab.Ob3ect\CLSID = {50D56720-28 63-1231-A0CB- 00A024D04332} HKEY_CLASSES_ROOT\CLSID\{50D56720-2863-1231-A0CB-00A024D04332} = Account Lab Object DLL HKEY_CLASSES_ROOT\CLSID\{50D56720-2863-1231-AOCB- 00A024D04332}\InprocServer32 = c:\complus\examples\bankdll\stepl\Debug\bank.dll HKEY_CLASSES_ROOT\CLSID\{50D5 6720-28 63-1231-A0CB-00A024D04 332}\ProgId = Account.Lab.Object HKEY_CLASSES_ROOT\Interfaced 40D92120-2863-1ldl-A01B-00A024D06632} = IAccount HKEY_CLASSES_ROOT\Interface\{40D92120-2863-lldl-A01B- 00A024D06632}\NumMethods = 3 HKEY_CLASSES_ROOT\Interface\{5723B700-2878-lldl-A01B-00A024D06632} = IDisplay HKEY_CLASSES_ROOT\Interfaced 5723B700-2878-1ldl-AOlB- 00A024D06632}\NumMethods = 1 Важная информация реестра Вы можете просмотреть всю необходимую информацию с помощью программы OLE/COM Object Viewer. В качестве небольшого примера построим DLL-сервер, содержащегося в каталоге Cnap6\BankDll. Добавьте к системному реестру инфор-
мацию из файла bank.reg, дважды щелкнув на нем. Затем вызовите OLE/COM Object Viewer и найдите Account Answer Object DLL в разделе All Objects. Создайте экземпляр объекта, дважды щелкнув мышью или щелкнув правой кнопкой и выбрав в контекстном меню Create Instance. Вы должны увидеть информацию, показанную на рис. 6.2. Рис. 6.2. Информация из системного реестра в OLE/COM Object Viewer Имеется несколько способов, которыми вы можете обратиться к СОМ-классу. Три из них иллюстрируются приведенным примером. Фундаментальный идентификатор класса — CLSID, 128-битный GUID — уникальным образом идентифицирует класс среди всех вычислительных систем мира. Недостатком применения CLSID является его громоздкость. Альтернативой CLSID служит идентификатор программы ProglD. Это имя не является внутренним именем класса, а связывается с ним в процессе регистрации. ProglD объекта банковского счета — Account.Answer.Object. Третье имя, которое обычно несколько длиннее, можно назвать "пользовательским именем". Это имя более удобочитаемо, чем ProglD, и выводится в системном реестре как значение, ассоциированное с ключом CLSID. Заметьте, что OLE/COM Object Viewer использует это имя в качестве имени объекта, отображаемое в левой панели. В нашем примере пользовательское имя — Account Answer Object DLL. В COM имеются и другие важные имена, которые мы обсуждали в главе 4, "Клиенты СОМ: концепции и программирование", например, имя "сокласса", используемое в Visual Basic. Это имя, назначенное применяемой в Visual Basic библиотеке типов. Это также "независимый от версии идентификатор программы". В главе 7, "Active Template Library" мы вновь столкнемся с этими именами. Системный реестр представляет собой иерархическую базу данных, созданную для быстрого просмотра, что объясняет ее избыточность и наличие перекрестных ссылок. Реестр не является нормализованной реляционной базой данных. Одно из наиболее важных сведений о СОМ-классе, хранящихся в реестре, — путь к серверу. Чтобы найти его для сервера DLL, взгляните на информацию, хранящуюся в ключе lnprocServer32. При реализации DLL-сервера одной из важных задач является регистрация сервера. Один из путей ее решения состоит в создании .REG-файла; предпочтительная альтернатива этому пути — программная регистрация, которую упрощает такой инструментарий, как ATL (ее мы обсудим в следующей главе).
Интерфейсы в системном реестре Самые важные глобально уникальные идентификаторы хранятся в системном реестре как CLSID. Они являются ключами, которые обеспечивают поиск необходимой информации о сервере по данному CLSID. Системный реестр, кроме того, имеет ключ Interfaces, в котором хранятся идентификаторы интерфейсов (IID). Все стандартные интерфейсы СОМ находятся здесь, и программа OLE/COM Object Viewer может использовать эту информацию для вывода интерфейсов, поддерживаемых классом. Заметьте, что СОМ не предоставляет механизм для непосредственного поиска интерфейсов, поддерживаемых классом. Если вам известен определенный интерфейс, поддерживаемый классом, то его найти можно, вызвав Querylnterface. Но вы не можете запросить у объекта список поддерживаемых им интерфейсов. Тем не менее СОМ предоставляет несколько путей, позволяющих получить эту информацию окружным путем. Простейший путь — поместить в системный реестр список интерфейсов, как было показано в приведенном выше примере .REG-файла. Второй механизм — использование библиотек типов, обсуждавшихся в главе 4, "Клиенты СОМ: концепции и программирование". Реализация СОМ-сервера контекста приложения с использованием C++ В этом разделе мы рассмотрим детали реализации СОМ-сервера, работающего в контексте основного приложения, с использованием C++. СОМ-сервер должен поддерживать I Unknown и некоторые специфические методы интерфейсов, предоставляемые нашим классом. Эта часть работы идентична той, которую мы провели в предыдущей главе. Принципиально новым является реализация фабрики классов; имеется также ряд особенностей, характерных для DLL-сервера. Одна из них — специальная функция, используемая для получения указателя на объект класса, что очень важно для поддержки механизма фабрики классов. Другая функция, которая должна предоставляться сервером, используется для выгрузки DLL из памяти. Для экспортирования этих специальных функций создается файл определения модуля. Пример, используемый в этом разделе, — класс банковского счета, уже знакомый нам по предыдущей главе. Мы добавим к нему все необходимое, чтобы сделать его сервером DLL. Попутно рассмотрим некоторые вопросы использования DLL, в частности, вопросы отладки. Рис. 6.3. Тестовая программа для СОМ-сервера, работающего в контексте приложения Окончательная версия нашего примера находится в каталоге Chap6\Bankdll. К этому времени вы уже должны были построить и зарегистрировать сервер. Теперь п о- стройте тестовую программу, являющуюся частью того же проекта, и запустите ее
(рис. 6.3). Программа ведет себя так же, как и программа, рассматриваемая в главе 5, "C++ и СОМ", хотя ее реализация несколько отличается от реализации проекта из предыдущей главы. В ней СОМ-класс был частью тестового приложения; теперь же СОМ-класс располагается в отдельной DLL. Мы добавили немного дополнительного отслеживающего кода, который показывает загрузку DLL, создание объекта фабрики классов и его уничтожение. Определение фабрики классов Для реализации объекта фабрики классов нам понадобится заголовочный файл определения классов C++. Это — конкретный класс, который должен реализовывать методы интерфейсов lUnknown и iciassFactory. Реализация iunknown идентична реализации, которую мы уже осуществляли ранее, и повторять ее в этой главе не стоит: // account.h class CAccountClassFactory : public IClassFactory { public: CAccountClassFactory() { m_nRef = 0; g_cLock++; Trace("Class factory object created"); } -CAccountClassFactory() { g_cLock—; Trace("Class factory object destroyed"); } // Методы lUnknown STDMETHOD(Querylnterface)(REFIID, void**); STDMETHOD_(ULONG, AddRef)(); STDMETHOD_(ULONG, Release) (); //Члены IClassFactory STDMETHOD(Createlnstance)(LPUNKNOWN, REFIID, void**); STDMETHOD(LockServer)(BOOL); protected: ULONG m_nRef; // Счетчик ссылок }; Реализация фабрики классов Теперь рассмотрим реализацию фабрики классов. lUnknown имеет одно маленькое изменение, о котором мы поговорим чуточку позже. // Account.cpp //Счетчики количества объектов и количества блокировок ULONG g_cObj=0; ULONG g_cLock=0; STDMETHODIMP CAccountClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
REFIID riid, void** ppvObj) { CAccount* pObj; HRESULT hr; *ppvObj = NULL; hr = EJDUTOFMEMORY; // Мы не поддерживаем агрегацию if (NULL != pUnkOuter) return CLASS_E_NOAGGREGATION; pObj = new CAccount; if (NULL == pObj) return hr; hr = pObj->QueryInterface(riid, ppvObj); if (FAILED(hr)) delete pObj; else { g_cObj++; } return hr; } STDMETHODIMP CAccountClassFactory::LockServer(BOOL fLock) { if (fLock) g_cLock++; else g_cLock—; return NOERROR; } Метод Create Instance очень схож со специальной создающей объекты функцией, описанной в главе 5, "C++ и СОМ". На выходе функции получаем указатель на интерфейс; заметьте, что пользователь может определить, какой интерфейс будет возвращен. Первый параметр представляет собой указатель на "управляемую неизвестность", которая используется совместно с агрегацией. Мы считаем, что в этом параметре всегда передается значение null, а если это не так, возвращается ошибка. В СОМ-агрегация означает специальную технологию повторного использования классов. В этой книге мы в основном игнорируем агрегацию. Агрегация является важной частью инфраструктуры СОМ, и некоторые системы классов используют ее как неотъемлемую часть. В нашей реализации имеется два счетчика. Мы считаем число объектов и число блокировок. Цель этих счетчиков — поддержка выгрузки процесса. Мы не хотим, чтобы DLL оставалась в памяти без необходимости. Счетчик g_cObj считает количество экземпляров объектов и увеличивается всякий раз при создании нового объекта. Количество блокировок используется в связи с методом LockServer. Параметр fLock определяет, должно ли значение счетчика блокировок увеличиваться или уменьшаться. Программа-клиент может использовать механизм счетчика блокировок как оптимизатор для блокировки сервера в памяти, для того чтобы он оставался в памяти даже при отсутствии объектов, если клиенту требуются частые обращения к серверу.
Экспортируемые функции DLL Обычно функции в DLL предоставляют "внешнему миру" путем их экспорта. Приложение может воспользоваться этими функциями, импортируя их. Эти процедуры постоянно используются в Windows, и в действительности Win32 API — не что иное, как набор экспортируемых функций DLL. Все, что необходимо для импортирования функций из DLL — это знать, какая DLL должна использоваться. Приложение может быть либо связано с библиотекой импорта при сборке приложения, либо явным образом загрузить DLL в процессе работы. СОМ действует иначе. Клиент не обязан знать, какая именно DLL используется для реализации сервера. Неотъемлемой частью модели СОМ является полиморфизм. Программа-клиент может быть написана таким образом, чтобы вести себя полиморфно по отношению к своим объектам. Например, составной документ OLE может содержать различные типы объектов (электронные таблицы, рисунки и др.). Каждый из этих объектов реализуется собственным сервером, и клиент может работать с любым сервером составного документа OLE — даже если со времени написания приложения- контейнера были созданы новые серверы. Клиент к серверу подключается с помощью СОМ, а не непосредственно. В случае DLL система времени выполнения СОМ в нужный момент вернет указатель на интерфейс СОМ-объекта; данный объект находится в том же адресном пространстве, что и клиент. Это означает, что механизм виртуальных таблиц будет пересылать вызов от клиента соответствующей функции. Функция при этом не должна экспортироваться из DLL. Однако DLL должна экспортировать ряд специфичных функций для использования системой времени исполнения СОМ. С двумя из них мы встретимся в этой главе (они обеспечивают получение объекта класса и проверку, может ли DLL быть выгружена из памяти), а две другие (для регистрации и дерегистрации DLL) обсудим в следующей главе. Файл определения модуля Функции DllGetClassOb ject и DllCanUnloadNow должны быть экспортированы. Это можно сделать с помощью файла определения модуля. ; bank.def LIBRARY BANK DESCRIPTION 'BANK DLL Server' EXPORTS DllCanUnloadNow @1 PRIVATE DllGetClassObject @2 PRIVATE Предоставление фабрики классов COM COM нуждается в стандартном механизме доступа к фабрике классов компонентов. DLL предоставляет этот механизм посредством экспортирования функции DllGetClassObject: HRESULT DllGetClassObject(rclsid, riid, ppv) Первый параметр, rclsid, определяет CLSID класса объекта, который должен быть загружен. Второй параметр, riid, определяет интерфейс, который вызывающая программа использует для сообщения с объектом класса. Чаще всего это
HD_lclassFactory. Третий параметр, ppv, указывает либо на указатель на требуемый интерфейс, либо, в случае ошибки, принимает значение null. Следующий код реализует DllGetclassObject для нашего примера класса банковского счета. STDAPI DllGetclassObject(REFCLSID rclsid, REFIID riid, void** ppv) { HRESULT hr; CAccountClassFactory *pObj; if (CLSID_Account != rclsid) return E_FAIL; pObj = new CAccountClassFactory(); if (NULL==pObj) return E_OUTOFMEMORY; hr=pObj->QueryInterface(riid, ppv); if (FAILED(hr)) delete pObj; return hr; } Механизм выгрузки DLL Сервер не требуется, когда счетчик блокировок, поддерживаемый посредством функции LockServer, обнуляется (как и счетчик количества объектов). СОМ определяет, можно ли выгружать сервер, посредством вызова экспортируемой функции DllCanUnloadNow. STDAPI DllCanUnloadNow() { // Можно выгружать при отсутствии // объектов и блокировок SCODE sc; if (g_cObj == 0 && g_cLock == 0) sc = S_OK; else sc = S_FALSE; return sc; } Доступ клиента к фабрике классов Клиент получает доступ к фабрике классов посредством функции COM API GoGetClassObject. LPCLASSFACTORY lpClassFactory; DWORD dwContext = CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER; HRESULT hr = GoGetClassObject( CLSID_Account, dwContext,
NULL, //Используется в DCOM IID_IClassFactory, (void**)&lpClassFactory); После того как будет получен указатель на фабрику классов, экземпляр объекта может быть создан с помощью метода Createlnstance; а по окончании работы этот указатель должен быть освобожден. Функция GoGetClassObject пригодится вам при создании экземпляров ряда объектов. Со Createlnstance Ситуация, когда вы не хотите проходить через сложный трехступенчатый процесс получения указателя на фабрику классов, вызова метода Createlnstance, освобождения указателя на фабрику классов, достаточно обычная. COM API предоставляет удобную функцию для создания единичного экземпляра объекта и возвращения указателя на интерфейс. Это функция CoCreatelnstance, с которой мы уже встречались в главе 4, "Клиенты СОМ: концепции и программирование", при обсуждении написания клиентских программ СОМ. HRESULT hr = CoCreatelnstance( CLSID_Account, NULL, // pUnkOuter; NULL в связи с // отсутствием агрегации CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IID_Account, (void**)&m_pAccount); Контекст выполнения И GoGetClassObject, и CoCreatelnstance получают параметр, который позволяет клиенту определить контекст выполнения. Используя флаг clsctx_inproc_server, клиент определяет сервер, работающий только в контексте основного приложения (DLL). Это означает, что, если сервер реализован в виде ЕХЕ, вызов будет неудачным. С помощью этого клиент имеет возможность некоторого управления характеристиками времени выполнения сервера. Принцип локальной/удаленной прозрачности означает, что синтаксис вызова СОМ-объекта идентичен, независимо от того, где этот объект расположен, однако производительность при этом различна. Сервер контекста приложения самый быстрый, локальный ЕХЕ-сервер несколько медленнее, и самый медленный удаленный сервер. CoFree UnusedLibraries Система времени выполнения СОМ отвечает за выгрузку сервера контекста приложения, когда это можно безопасно выполнить, для чего СОМ вызывает DllCanUnloadNow. Если выгрузка разрешена, СОМ продолжает работу и выгружает библиотеку. Программа-клиент может заставить СОМ проверить возможность выгрузки в любой момент с помощью вызова функции API CoFreeUnusedLibraries. Связывание с библиотеками СОМ При использовании фабрик классов и других возможностей СОМ вы должны скомпоновать проект с помощью библиотек СОМ. Для фабрик классов (CoCreatelnstance, CoGetClassObject и др.) вам надо связать с проектом библио-
теку ole32.1ib. В файле stdafx.h могут быть размещены следующие директивы препроцессора #pragma, обеспечивающие связывание необходимых библиотек без дополнительного изменения настроек проекта: tpragma comment(lib,"oledlg.lib") #pragma comment(lib,"ole32.lib") #pragma comment(lib,"oleaut32.lib") #pragma comment(lib,"uuid.lib") #pragma comment(lib,"urlmon.lib") Это не обязательно при использовании MFC или ATL. Например, afxdisp.h содержит приведенные выше директивы. Работа с DLL В этом разделе кратко освещены некоторые вопросы работы с DLL. При непосредственном вызове DLL вы должны побеспокоиться о том, чтобы DLL находилась в каталоге, где она сможет быть найдена (в случае СОМ-сервера вместо этого вы должны побеспокоиться о том, чтобы она была корректно зарегистрирована в системном реестре). Среда разработки Visual Studio позволяет легко определять точки останова в DLL для ее отладки — в нашем примере мы рассмотрим и эту возможность. Откройте проект Chap6\Demos\BankDll. Это — DLL версия объекта банковского счета, рассмотренного в главе 5, "C++ и СОМ". Это еще не сервер, поскольку пока еще не задействован механизм фабрики классов СОМ. Это просто проект "банк" и проект "тест". 1. Постройте проект "банк", создав bank.dll в каталоге Debug. 2. Постройте проект "тест" и попытайтесь запустить его (например, двойным щелчком на .ЕХЕ-файле в Windows Explorer). Запуск будет неудачен. Вы получите сообщение об ошибке, в котором говорится, что DLL не найдена. Дело в том, что для поиска DLL в системе имеется определенный путь, и она должна быть размещена в каталоге windows либо в каталоге windows\System и т.д. Всегда проверяется каталог, в котором размещен сам .ЕХЕ-файл. 3. Скопируйте bank.dll в каталог Test\Debug и повторите попытку — на этот раз она должна быть успешной. 4. Теперь мы попытаемся отладить DLL. Установим в проекте "банк" точку останова на входе в метод Deposit. 5. Попытаемся запустить проект "банк" под отладчиком. Вам будет предложено ввести имя выполняемого файла. Найдите test.exe и щелкните на кнопке ОК (вы можете также определить выполняемый файл для отладки DLL с помощью команды меню Projects Settings, расположенной во вкладке Debug соответствующего диалогового окна). 6. Тестовая программа должна начать работать. Щелкните на кнопке Deposit. Программа не остановилась. Почему? 7. Ответ: "Объект не был создан". Вначале щелкните на кнопке Create, а затем — на кнопке Deposit. Теперь все должно сработать так, как мы ожидаем, — программа должна приостановить выполнение на точке останова.
Реализация СОМ-сервера контекста приложения с использованием Visual Basic Реализовать СОМ-сервер с использованием Visual Basic очень просто. У вас нет такого полного контроля над тем, что вы делаете, как в C++, но во многих случаях реализуемый с помощью Visual Basic СОМ-сервер вполне адекватен. Ключевая концепция Visual Basic заключается в том, что вам необходим модуль класса, представляющий собой знакомый вам класс из объектно-ориентированных языков программирования и СОМ в Visual Basic. Для СОМ-серверов существует два типа проектов Visual Basic: ActiveX DLL (сервер контекста приложения) и ActiveX EXE (автономный сервер). Каждый из них имеет начальный модуль класса, к которому вы добавляете методы. Для реализации дополнительного интерфейса вы вначале определяете новый интерфейс посредством другого модуля класса, а затем указываете, что ваш исходный класс реализует дополнительный интерфейс, и пишете соответствующий код. В следующем примере вы создадите Visual Basic версию нашего сервера банковского счета. Работу можно выполнять в каталоге Chap6\Demos\BankVb. Проект тестовой программы-клиента находится в каталоге Chap6\Demos\BankClientVb. Полное решение поставленной задачи находится в каталогах Chap6\BankVb и Chap6\BankClientVb. Создание сервера Сперва создадим DLL-сервер. Для простоты в этом разделе мы перейдем прямо к созданию полной DLL; вы же можете останавливаться в любом месте и проводить испытания после реализации каждой из новых возможностей — только учтите, что при этом вы столкнетесь с вопросами несоответствия версий. Каждый раз при построении новой DLL вы будете получать новый GUID и добавлять информацию в системный реестр. Поэтому стоит дерегистрировать DLL всякий раз перед добавлением в нее новой возможности. Когда вы выполните всю работу, вы сможете установить бинарную совместимость версий, о чем будет рассказано немного позже. Тестирование DLL описано в следующем разделе. Создание нового проекта ActiveX DLL Из File^New Project выберите ActiveX DLL, как показано на рис. 6.4. Рис. 6.4 Новый проект ActiveX DLL Переименуйте ваш проект в BankVb, а ваш класс — в Account. Сохраните проект в каталоге Chap6\Demos\BankVb, приняв имена Account.els и BankVb.vbp.
Код для класса Account Добавьте следующий код к модулю класса Account.els. Объявленная закрытая переменная предназначена для хранения значения баланса (который инициализируется значением 200 при создании класса). Создание и уничтожение объекта Account сопровождается выводом окна сообщения. Реализованы методы Deposit, withdraw и GetBalance. Private gBalance As Long Public Sub Deposit(ByVal amount As Long) gBalance = gBalance + amount End Sub Public Sub Withdraw(ByVal amount As Long) gBalance = gBalance - amount End Sub Public Sub GetBalance(ByRef balance As Long) balance = gBalance End Sub Private Sub Class_Initialize() gBalance = 200 MsgBox "Account object created" End Sub Private Sub Class_Terminate() MsgBox "Account object destroyed" End Sub Модуль класса для интерфейса I Display Теперь добавим к проекту новый модуль класса с помощью команды меню Project^Add Class Module. Измените имя класса на I Display. Добавьте код для метода Show (мы используем просто комментарий, для того, чтобы код не был пустым и редактор просто не удалил этот метод). Сохраните файл под именем iDisplay.cls. Public Sub Show() 1 Здесь мы ничего не реализуем End Sub Реализация I Display в классе Account И, наконец, мы обеспечиваем реализацию метода Show в модуле класса Account. Вначале файла мы вставляем следующую строку кода: Implements IDisplay Затем в левом выпадающем списке окна кода выберем IDisplay. Поскольку у него только один метод, увидите в правом выпадающем списке автоматически появится Show. Таким образом, этот метод добавляется к классу Account, как показано на рис. 6.5. Рис. 6.5. Добавление метода Show к классу Account
Метод Show в Account выводит соответствующее окно сообщения. Implements IDisplay Private Sub IDisplay_Show() MsgBox "Balance is " & gBalance, , "IDisplay::Show" End Sub Построение DLL Теперь можно построить DLL, воспользовавшись командой меню File^Make BankVb.dll. Установка бинарной совместимости версий Теперь, когда ваш сервер завершен, очень важно определить "бинарную совместимость". В противном случае при перестройке DLL вы получите новый GUID и полную неразбериху. Выберите пункт меню Project^BankVb Properties. В появляющемся диалоговом окне выберите вкладку Component, а в ней — опцию Binary Compatibility, как показано на рис. 6.6. Рис. 6.6. Установка бинарной совместимости компонента Тестовая программа-клиент Первая проверка вашего сервера будет выполнена с помощью OLE/COM Object Viewer. Найдите BankVb.Account. Обратите внимание: Visual Basic создал то же "пользовательское имя", что и ProglD. Это может привести к некоторой путанице. Для главной проверки запустите программу из каталога Chap6\Demos\BankClientVb. Это та тестовая программа, которая использовалась в главе 4, "Клиенты СОМ: концепции и программирование" для проверки C++ версии сервера банковского счета. Мы закомментировали код, выводящий приветственное сообщение в заголовке, так как класс Greet в нашем сервере на Visual Basic не реализован (однако, если захотите — добавьте необходимый код самостоятельно. Это очень просто!). Если вы просто запустите программу, она вызовет старый сервер, созданный с использованием C++ (если он все еще зарегистрирован в системе). Чтобы все работало так, как надо, следует изменить настройки проекта. С
помощью команды меню Projects References вызовите диалоговое окно References, показанное на рис. 6.7. Отмените опцию Bank 1.0 Type Library (C++ сервер) и пометьте BankVb. Теперь вы можете запустить программу-клиент и вызвать только что созданную Visual Basic версию сервера банковского счета (обратите внимание: сервер банковского счета Visual C++ использовал начальное значение баланса 100, в то время как сервер банковского счета Visual Basic имеет начальное значение счета 200, а это дает вам возможность сразу определить, какой именно сервер работает). Рис. 6.7. Установка ссылки на библиотеку типов BankVb Резюме В данной главе мы завершили рассмотрение основ работы СОМ, разложив "по полочкам" все наиболее существенные части этой технологии. В главе 4, "Клиенты СОМ: концепции и программирование" мы познакомились с тем, как использовать СОМ-классы на сервере. В главе 5, "C++ и СОМ" обсудили то, как реализовывать СОМ-классы, используя при этом такие механизмы I Unknown, как согласование интерфейсов и счетчики ссылок. Чтобы позволить клиенту создавать один из наших объектов, мы разработали специальную функцию. В данной главе были детально рассмотрены фабрики классов, представляющие собой механизм общего назначения для создания объектов на сервере. Мы изучили системный реестр и содержащуюся в нем информацию, связывающую идентификатор класса с сервером. Когда клиент намеревается создать объект по данному идентификатору класса, он вызывает функцию COM API CoGetClassObject, и СОМ загружает сервер (который может быть найден с использованием реестра) и возвращает указатель на фабрику классов. Visual Basic упрощает реализацию СОМ-серверов контекста приложения, создавая проект "ActiveX DLL", и вам нужно просто добавить методы к модулю класса. Если необходимо реализовать дополнительный интерфейс, вы должны вначале определить новый интерфейс с помощью другого модуля класса, а затем — исходный класс как "реализующий дополнительный интерфейс" и добавить необходимый код. Важной особенностью СОМ является прозрачность размещения. Класс СОМ может находиться в DLL, EXE на локальной машине или на удаленной машине. В следующих главах мы рассмотрим все эти случаи, но все-таки прежде всего обсудим Active Template Library — инструментарий, упрощающий создание СОМ-серверов на C++. Одна из задач, которую ATL помогает нам решать, заключается в упрощении использования IDL для определения пользовательского интерфейса и вызова компилятора MIDL RPC.
Глава 7 Active Template Library В двух предыдущих главах мы обсуждали, каким образом реализуются СОМ- объекты на низком уровне. Очень важно сделать это хотя бы однажды для полного понимания механизма СОМ. Однако для практической разработки приложений этот уровень является слишком низким и требует огромного количества рутинной работы, наподобие создания кода для реализации интерфейса IUnknown или фабрики классов. Для повышения производительности труда программиста используются различные инструменты высокого уровня Для C++ одним из наиболее привлекательных инструментов является библиотека активных шаблонов Active Template Library (ATL). В этой главе речь пойдет о базовой структуре ATL и ее использовании для реализации СОМ-сервера. Мы начнем с простейшего применения ATL без каких-либо мастеров Visual C++ (они помогают в работе, но для изучения лучше "пощупать руками " ATL, а уже затем прибегать к помощи мастеров). ATL упрощает процесс саморегистрации ваших компонентов, обеспечивая их регистрацию и дерегистрацию ATL хорошо работает с языком определения интерфейсов IDL, а в случае Visual C++ ATL-проект автоматически вызывает компилятор MIDL для IDL-файлов. В результате становится очень простым построение про- кси и заглушек (о том, что это такое, вы узнаете в главе 9, "ЕХЕ-серверы ", при подробном рассмотрении ЕХЕ-серверов). ATL также предоставляет программисту богатый набор классов-оболочек для указателей на интерфейсы и некоторых типов данных, таких как BSTR. Active Template Library Active Template Library представляет собой библиотеку шаблонов C++ для построения СОМ-объектов. ATL входит в Visual C++ версии 5.0 и более поздних на правах составной части. Гибкая и мощная, ATL помогает вам строить единые бинарные файлы, не требующие системы времени выполнения, обеспечивая при этом поддержку высокотехнологичных возможностей СОМ, таких как альтернативные модели потоков и агрегация. MFCuATL Библиотека MFC предназначена для разработки приложении Windows и инкапсулирует большинство функций Windows API, помимо этого, она предоставляет дополнительные классы высокого уровня, поддерживающие, например, возможности современного документооборота. MFC обеспечивает высокоуровневую поддержку OLE и ActiveX, тем самым сильно упрощая создание сложных приложений, использующих технологии связывания и внедрения объектов. MFC обеспечивает низкоуровневую поддержку СОМ, но не интегрирована в Visual C++ (отсутствуют соответствующие мастера). MFC также требует большого количества ресурсов (так, размер MFC42 .DLL составляет почти мегабайт).
Active Template Library создана специально для создания СОМ-объектов. Это мощная, компактная библиотека, поддерживаемая соответствующим мастером. Инкапсуляция Windows API ограничена — включает только возможности простого отображения сообщений. MFC и ATL В этой книге мы будем использовать как ATL, так и MFC. Для реализации СОМ- объектов на C++ будем использовать ATL, a MFC будет служить для упрощения создания тестовых программ и обеспечения диалогового пользовательского интерфейса. Пе р- воначально мы воспользуемся ATL, не применяя каких-либо мастеров; для лучшего понимания принципов работы построим (с нуля) минимальный СОМ-объекг вручную. Стереотипный код СОМ Реализация СОМ-объекта включает большое количество повторяющегося кода. Но в любом случае должна быть обеспечена базовая функциональность iunknown. Стереотипна и реализация фабрики классов. Передовые возможности, такие как агрегация, требуют дополнительного стандартного кода, который в некоторых случаях может быть достаточно большим. ATL предоставляет повторно используемую реализацию всей этой стандартной функциональности. Реализация IUnknown Для использования ATL-реализации iUnknown мы просто порождаем класс от класса ATL ccomOb jectRootEx, а также от интерфейсов поддерживаемых нами объектов. class ATL_NO_VTABLE CAccount : public CComObjectRootEx<CComS±ngleThreadModel>, public IAccount, public IDisplay, { Параметр шаблона определяет модель потоков (здесь мы воспользовались простейшим случаем). Многопоточность детально обсуждается в главе 13, "Многопоточность в СОМ" этой книги. Для реализации Querylnterface через просмотр таблицы мы должны добавить соответствующие элементы в "карту интерфейсов" для каждого интерфейса, поддерживаемого нашим объектом. BEGIN_COM_MAP(CAccount) COM_INTERFACE_ENTRY(IAccount) COM_INTERFACE_ENTRY (IDisplay) END_COM_MAP() Объявление класса Остальная часть объявления класса идентична случаю без применения ATL, за исключением того, что в данной ситуации собственный счетчик ссылок не требуется. // account.h class ATL_NO_VTABLE CAccount : public CComOb:ectRootEx<CComSingleThreadModel>,
public IAccount, public IDisplay { public: CAccountO : m_nBalance (100) { } BEGIN_COM_MAP(CAccount) COM_INTERFACE_ENTRY(IAccount) COM_INTERFACE_ENTRY(IDisplay) END_COM_MAP() public: // Методы IAccount STDMETHOD(Withdraw)(int amount); STDMETHOD(GetBalance)(int* pBalance); STDMETHOD(Deposit)(int amount); // Методы IDisplay STDMETHOD(Show) () ; protected: int m_nBalance; }; Реализация класса Реализация класса еще проще, поскольку в данном случае реализуются только наши собственные методы (которые те же, что и в случае без использования ATL). // account.срр STDMETHODIMP CAccount::Deposit(int amount) { m_nBalance += amount; return S_OK; } STDMETHODIMP CAccount::GetBalance(int* pBalance) { *pBalance = m_nBalance; return S_OK; } STDMETHODIMP CAccount::Withdraw(int amount) { m_nBalance -= amount; return S_OK; } STDMETHODIMP CAccount::Show() { char buf[80]; wsprintf(buf, "Balance = %d", m_nBalance); Trace(buf, "CAccount::Show"); return S_OK; }
Создание экземпляра СОМ-объекта на основе ATL Для этого реализуется специальная функция, похожая на ту, которую мы создавали при работе с чистым СОМ (мы не пользуемся фабрикой классов в методических целях; поддержка фабрик классов в ATL будет рассмотрена в следующем разделе). declspec(dllexport) BOOL CreateAccount(IAccount** ppAccount) { HRESULT hr; if (ppAccount == NULL) return FALSE; // Создание объекта CComObject<CAccount>* pAccount = new CComObject<CAccount>; if (pAccount == NULL) return FALSE; // Получение интерфейса (с вызовом AddRef) hr = pAccount->QueryInterface(IID_IAccount, (void**) ppAccount); if (SUCCEEDED(hr)) return TRUE; else return FALSE; } С Com Object В действительности класс CAccount представляет собой абстрактный класс. Базовый класс ATL cComObjectRootEx на самом деле не реализует iunknown, но предоставляет вспомогательные функции, которые могут поддержать действительную реализацию. Следовательно, CAccount не содержит реализации чисто виртуальных функций iunknown и, следовательно, является абстрактным классом. Вот почему оператор new в приведенном выше коде применяется не к классу CAccount, а к шаблонному классу CComObject, который получает CAccount в качестве аргумента шаблона. Имеется несколько различных вариантов того, каким образом может быть реализован интерфейс iunknown. Обычный случай представляет собой объект, размещаемый в куче. Однако имеются и альтернативы, например объект, размещаемый в стеке, для которого неприменим вызов AddRef; объект, который может быть создан только как часть агрегата; и т.п. ATL предоставляет шаблонный класс CComObjectxxx<T> для поддержки всех этих вариантов (CComObject реализует обычный случай размещения в куче). Ваш реализуемый класс передается как аргумент шаблона. Пример программы В качестве примера реализации простого класса СОМ с применением некоторых возможностей ATL рассмотрим проект из каталога Chap7\BankAtl\step2. Эта программа подобна примеру из главы 5, "C++ и СОМ". Здесь нет реализованной фабрики классов. СОМ-класс реализован в DLL, но эта DLL вызывается непосредственно из программы-клиента, а не загружается системой времени выполнения СОМ. Нет также и информации в системном реестре.
Рис. 7.1 Реализация простого объекта с использованием A TL Постройте сервер и тестовую программу и попытайтесь запустить последнюю. Выяснится, что она не запускается, поскольку не удается найти DLL. Скопируйте DLL в каталог Test\Debug и повторите попытку запуска тестовой программы. На этот раз программа должна работать, как показано на рис. 7.1. Заметьте, что эта программа несколько проще той, которая рассматривалась в предыдущей главе. В ней нет кнопок Create и Destroy — объект создается в процессе инициализации программы и освобождается в процессе ее завершения. Visual C++ и ATL В этом разделе мы рассмотрим поддержку, предоставляемую Visual C++ для упрощения программирования СОМ с применением ATL. ATL и программы-мастера Visual C++ позволяют легко реализовать СОМ-сервер с нуля. Такой СОМ-сервер автоматически поддерживает саморегистрацию, являющуюся существенным упрощением работы с системным реестром по сравнению с . REG-файлами. В следующей главе мы рассмотрим язык определения интерфейса, также облегчающий разработку СОМ- серверов (впрочем, для добавления новых интерфейсов к объекту вам придется вручную изменить файл, созданный мастером). Работа по созданию сервера, так же как и сервера банковского счета, рассмотренного в главе 4, "Клиенты СОМ: концепции и программирование", выполняется "вручную". Поддержка COM Visual C++ Visual C++ (начиная с версии 5.0) обеспечивает существенную поддержку программирования СОМ. ■ ATL COM AppWizard создает каркас проекта для реализации СОМ-сервера. ■ ATL Object Wizard автоматизирует добавление кода для СОМ-объекта с фабрикой классов. ■ Автоматически генерируется IDL — упрощается определение объектов СОМ и генерация кода, необходимого для удаленных СОМ-объектов. ■ Автоматически создается библиотека типов, используемая броузерами при создании программ-клиентов. ■ Директива импортирования читает библиотеку типов и автоматически генерирует код интеллектуального указателя, который упрощает управление указателями на интерфейсы со стороны программ-клиентов (эта тема будет обсуждаться в главе 8, "Поддержка СОМ в Visual C++").
Демонстрационный СОМ-сервер с применением ATL Для демонстрации создания и использования СОМ-объекта с применением ATL: 1. Создается DLL СОМ-сервер с использованием ATL COM AppWizard. 2. Добавляется код для СОМ-объекта с использованием ATL Object Wizard. 3. Создается диалоговое клиентское приложение с применением AppWizard, которое для упрощения использования СОМ-объекта импортирует библиотеку типов (см. главу 8, "Поддержка СОМ в Visual C++"). Выполнять все описанные действия можно в каталоге Chap7\Demos. Полный проект сервера можно найти в каталоге Chap7\BankWiz, а клиента— в Chap7\BankWizClient И Chap8\SmartClient. Мойте руки перед... реестром Не забывайте дерегистрировать предыдущие серверы. Перед тем как приступить к работе над новым проектом, стоит убедиться, что предыдущий сервер дерегистрирован Это можно сделать, запустив файл unreg_bank.bat из каталога Chap4\Bank. Если вы не дерегистрируете старый сервер, у вас окажется два сервера с разными GUID, но с одинаковыми именами других типов (ProgID, соклассов и др.), что может привести к неимоверной путанице. Нащ демонстрационный объект СОМ будет иметь следующие характеристики (с подобным объектом мы уже работали ранее, но он был попроще). ■ Объект отслеживает состояние счета. ■ Интерфейс I Account имеет методы для вклада на счет, снятия со счета и получения текущего баланса. ■ Интерфейс iDisplay имеет единственный метод для вывода текущего состояния счета в окне сообщения. Демонстрационная программа реализует только интерфейс iAccount с методами Deposit И GetBalance. ATL COM AppWizard Создайте новый проект Bank в каталоге Demos с использованием ATL COM AppWizard, создав каталог Bankwiz (рис. 7.2). Примите предлагаемые по умолчанию настройки, щелкните на кнопке Finish, а затем — на кнопке ОК (рис. 7.3). Тем самым будет создан каркас DLL с необходимыми для поддержки СОМ входами, но все еще не СОМ-объект (рис. 7.4). ATL Object Wizard Добавьте поддержку СОМ объекта с помощью команды меню Inserts New ATL Object..., которая вызовет ATL Object Wizard, как показано на рис. 7.5. Выберите Simple Object и щелкните на кнопке Next. Определите в качестве короткого имени Account, а в качестве имени интерфейса — IAccount и согласитесь с предложенными по умолчанию остальными именами, как показано на рис. 7.6.
Рис. 7.2. Новый проект ATL COMAppWizard Рис. 7.3. Первый шаг ATL COM App Wizard Рис. 7.4. Каркасная DLL
Рис. 7.5. Вставка объекта с помощью ATL Object Wizard Рис. 7.6. Объект Account Имена класса Обратите внимание на различные имена, выбираемые при вводе имени Account. ■ Account Class — имя, которое будет показано пользователю (например, в OLE/COM Object Viewer). ■ Account, представляющее имя сокласса. Это имя класса, которое вы будете использовать в клиентской программе на Visual Basic. ■ Bank.Account — независимый от версии программный идентификатор. ■ Bank.Account. 1 представляет собой ProglD. Атрибуты Щелкните на вкладке Attributes, и выберите простейшие опции (отказавшись от значений, предлагаемых по умолчанию) (рис. 7.7): ■ Single Threading Model; ■ Custom Interface; ■ No for Aggregation.
Рис. 7.7. Атрибуты нового объекта ATL Построение сервера Щелкните на кнопке ОК и постройте DLL. Таким образом, вы создали СОМ- объект, не написав ни одной строчки кода! Воспользуйтесь OLE/COM Object Viewer и исследуйте вновь созданный объект. Убедитесь, что вы находитесь в режиме эксперта (см. меню View). Найдите в списке всех объектов "Account Class" и щелкните на нем, чтобы просмотреть соответствующие записи в системном реестре. Двойной щелчок (или щелчок на "+" в дереве) приведет к созданию СОМ-объекта, принадлежащего этому классу и запросу к системному реестру обо всех поддерживаемых объектом интерфейсах (iunknown). Обратите внимание на то, что пользовательского интерфейса lAccount в системном реестре нет (рис. 7.8). Рис. 7.8. Объект ATL в OLE/COM Object Viewer Определение методов Следующий шаг состоит в добавлении методов к интерфейсу lAccount. Щелкните правой кнопкой мыши на lAccount (панель Workspace) и выберите в контекстном меню Add Method.... Добавьте метод Deposit, который получает один параметр типа int (рис. 7.9). Точно так же добавьте метод GetBalance, который получает один выходной параметр, представляющий собой указатель на int (рис. 7.10).
Рис. 7.9. Добавление метода Deposit к интерфейсу I Account Рис. 7.10. Добавление метода GetBalance к интерфейсу I Ac count IDL-файл Рассмотрите сгенерированный после добавления методов IDL-файл bank.idl. // Bank.idl : IDL source for Bank.dll // import "oaidl.idl"; import "ocidl.idl"; [ object, uuid(0FFBDAAD-FCA7-11D2-8FF4-00105AA45BDC), helpstring("IAccount Interface"), pointer_default(unique) ] interface IAccount : IUnknown { [helpstring("method Deposit")] HRESULT Deposit([in] int amount); [helpstring("method GetBalance")] HRESULT GetBalance([out] int* pBalance); };
[ uuid(0FFBDAAl-FCA7-llD2-8FF4-00105AA45BDC) , version(1.0), helpstring("Bank 1.0 Type Library") ] library BANKLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(0FFBDAAE-FCA7-llD2-8FF4-00105AA45BDC) , helpstring("Account Class") ] coclass Account { [default] interface IAccount; }; }; Определение реализуемого класса Рассмотрите определение СОМ-класса и обратите внимание на использование множественного наследования. Добавьте член-данное для хранения величины баланса счета и инициализируйте его значением 100 в конструкторе. // Account.h : Объявление CAccount class ATL_NO_VTABLE CAccount : public CComObjectRootEx<CComSingleThreadModel>/ public CComCoClass<CAccount, &CLSID_Account>, public IAccount { public: CAccount() : m_nBalance(100) { ::MessageBox(NULL,"Account object created", "CAccount", MB_OK); } -CAccount() { ::MessageBox(NULL,"Account object destroyed", "CAccount", MB_OK); } DECLARE_REGISTRY_RESOURCEID(IDR_ACCOUNT) DECLARE_NOT_AGGREGATABLE(CAccount) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM__MAP (CAccount) COM_INTERFACE__ENTRY (IAccount) END_COM_MAP() // IAccount public: STDMETHOD(GetBalance)(/*[out]*/ int* pBalance);
STDMETHOD(Deposit)(/*[in]*/ int amount); protected: int xn_nBalance; }; Реализация методов Последний шаг состоит в реализации двух методов (каркасы которых созданы автоматически). STDMETHODIMP CAccount::Deposit(int amount) { m_nBalance += amount; return S_OK; } STDMETHODIMP CAccount::GetBalance(int *pBalance) { *pBalance = m__nBalance; return S_OK; } Постройте DLL. Итак, вы создали полнофункциональный COM-сервер контекста приложения, эквивалентный серверу, чей код приведен в каталоге chap7\BankWiz\stepl. Тестовая программа-клиент Если вы хотите протестировать ваш сервер прямо сейчас, то можете использовать его в программе-клиенте, вызвав CoCreatelnstance для создания экземпляра объекта и получения указателя на интерфейс. Затем вы можете посредством этого указателя вызывать различные методы, и вызвать Release по завершении работы с указателем на интерфейс для освобождения объекта. Такую программу-клиент вы можете найти в каталоге Chap7\BankWizClient\Stepl. Перед началом работы вы должны скопировать файлы bank.h и bank_i.c из каталога сервера в каталог клиента. Прогулка по ATL-коду Рассмотрим основные свойства ATL-кода, сгенерированного мастером. Базовая структура построенного мастером ATL-проекта та же, что и у созданного нами ранее, но с некоторыми значительными улучшениями. bank.cpp Экспортируемые функции DllGetObject, DllCanUnloadNow (и другие), карта объекта, CComModule bank. def Файл определения модуля bank.idl IDL-файл account.h Определение класса СОМ-объекта и карта интерфейсов account. cpp Реализация класса СОМ-объекта account. rgs Файл сценария реестра Код, сгенерированный MIDL Несколько дополнительных файлов генерируются при обработке IDL-файла компилятором MIDL. bank.h Определение интерфейса bank.tlb Бинарная библиотека типов, описывающая объекты и их интерфейсы (при наличии соответствующего оператора в IDL)
bank_i.c Определение всех GUID (используйте только в одном компилируемом модуле) bank_p. с Реализация кода прокси/заглушек dlldata. с Код маршалинга параметров интерфейса Два последних файла играют важную роль при построении ЕХЕ-сервера, где требуется пересечение границ процесса, — детальнее об этом мы поговорим в главе 9, "ЕХЕ-серверы". CComModule и карта объекта // bank.cpp : реализация экспорта DLL CComModule _Module; BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Account, CAccount) END_OBJECT_MAP() CComModule содержит карту объекта, в которой хранится информация о СОМ- объектах и идентификаторах их классов, количестве блокировок и т.п. В глобальной области видимости должен существовать только один экземпляр объекта CComModule. CComModule играет роль, в чем-то аналогичную роли cwinApp в MFC. DllMain Экземпляр CComModule инициализируется и деинициализируется. // bank.cpp : реализация экспорта DLL extern "С" BOOL WINAPI DllMain(HINSTANCE hlnstance, DWORD dwReason, LPVOID /*lpReserved*/) { if (dwReason == DLL_PROCESS_ATTACH) { _Module.Init(ObjectMap, hlnstance, &LIBID_BANKLib); DisableThreadLibraryCalls(hlnstance) ; } else if (dwReason == DLL_PROCESS_DETACH) _Module.Term(); return TRUE; //ok } DllCanUnloadNow и DllGetClassObject ATL обеспечивает реализацию функций DllCanUnloadNow и DllGetClassObject, которые должны поддерживаться каждым СОМ-сервером контекста приложения. // bank.cpp : реализация экспорта DLL
// Используется для определения того, // может ли DLL быть выгружена OLE STDAPI DllCanUnloadNow(void) { return (_Module.GetLockCount()==0) ? S_OK : S_FALSE; } // Возвращает фабрику классов для // создания объекта требуемого типа STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) { return _Module.GetClassObject(rclsid, riid, ppv); } Саморегистрация Очень важным свойством СОМ-объектов является то, что они саморегистрируемы. В действительности это — требование, предъявляемое к управляющим элементам ActiveX. Саморегистрация означает, что компоненты содержат код для регистрации и дерегистрации самих себя. В случае сервера контекста приложения саморегистрация поддерживается реализацией двух дополнительных входов DLL: ■ DllRegisterServer — для регистрации; ■ DllUnregisterServer — для дерегистрации. Приведенный ниже код, сгенерированный мастером, работает совместно с CComModule и файлом сценария реестра. // bank.cpp : реализация экспорта DLL // DllRegisterServer - внесение // записей в системный реестр STDAPI DllRegisterServer(void) { return _Module.RegisterServer(TRUE); } // DllUnregisterServer - удаление // записей из системного реестра STDAPI DllUnregisterServer(void) { return _Module.UnregisterServer(TRUE); } Реализация регистрации CComModule основана на файле сценария реестра. // account.rgs HKCR { Bank.Account.1 = s 'Account Class' {
CLSID = s '{0FFBDAAE-FCA7-11D2-8FF4-00105AA45BDC}' } Bank.Account = s 'Account Class1 { CLSID = s •{0FFBDAAE-FCA7-11D2-8FF4-00105AA45BDC}' CurVer = s 'Bank.Account.1f } NoRemove CLSID { ForceRemove {0FFBDAAE-FCA7-11D2-8FF4-00105AA45BDC} = s 'Account Class' { ProgID = s 'Bank.Account.1' VersionlndependentProgID = s 'Bank.Account' InprocServer32 = s '%MODULE%' { } 'TypeLib' = s '{0FFBDAA1-FCA7-11D2-8FF4-00105AA45BDC}' } } } REGSVR32 Программа REGSVR32.EXE используется для вызова DllRegisterServer. Для вызова DllUnregisterServer применяется опция командной строки /и. Я создал для всех наших DLL-серверов маленькие однострочные файлы регистрации (reg_xxxx.bat) и дерегистрации (unreg_xxxx.bat) серверов. Таким образом, вы можете выполнять регистрацию и дерегистрацию простым двойным щелчком на соответствующем файле в окне Windows Explorer. Реализация IClassFactory Для использования ATL-реализации IClassFactory мы делаем наш реализуемый класс порожденным от ATL-класса ccomCoClass. Параметр шаблона определяет реализуемый класс и CLSID. // account.h class ATL_NO_VTABLE CAccount : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAccount, &CLSID__Account>, public IAccount { Для того чтобы cComModule мог поддерживать набор определений объектов класса, мы должны добавить записи в "карту объекта" для каждого СОМ-объекта с его CLSID. BEGIN_OBJECT_MAP(ObjectMap) OBJECT_ENTRY(CLSID_Account, CAccount) END_OBJECT_MAP()
Множественные интерфейсы и IDL В этом разделе мы рассмотрим, как добавить поддержку множественных интерфейсов, используя ATL. Нам придется отредактировать IDL-файл (это удобный случай познакомиться с ним поближе). Мы увидим, каким образом определяется библиотека типов, как она используется в Visual Basic и в чем заключается роль соклас- сов. Для завершения реализации второго интерфейса мы должны будем добавить некоторый код C++ — сделав реализуемый класс наследником нового интерфейса и добавив новые записи в карту объекта. Добавление второго интерфейса в IDL Мы начнем работу с редактирования файла bank.idl. Если хотите, вы можете выполнять описываемые действия в каталоге Chap7\Demos\BankWiz или Chap7\BankWiz\Stepl. 1. Создайте копию части файла, определяющей интерфейс I Account, и измените имя на iDisplay. 2. Измените uuid (воспользовавшись инструментом guidgen). 3. Удалите методы (временно у вас окажется пустой интерфейс). 4. Добавьте новый интерфейс к coclass Account (второй интерфейс не имеет атрибута default). 5. Постройте сервер. Теперь вы можете воспользоваться OLE/COM Object Viewer и просмотреть новую библиотеку типа (команда меню File«=>View TypeLib), чтобы убедиться, что новый интерфейс определен корректно. // Bank.idl : IDL source for Bank.dll [ uuid(42135D00-2F41-lldl-A01B-00A024D06632), helpstring("IDisplay Interface"), pointer_default(unique) ] interface IDisplay : IUnknown { }; [ uuid(0FFBDAA1-FCA7-11D2-8FF4-00105AA45BDC), version (1.0), helpstring("Bank 1.0 Type Library") ] library BANKLib { importlib("stdole32.tlb"); importlib("stdole2.tlb"); [ uuid(0FFBDAAE-FCA7-11D2-8FF4-00105AA45BDC), helpstring("Account Class") ]
coclass Account { [default] interface IAccount; interface IDisplay; }; }; Библиотека типов Оператор library в IDL файле определяет библиотеку типов, которая содержит описание СОМ-классов, их интерфейсов и методов интерфейсов в бинарном виде. Библиотека может использоваться различным инструментарием, например Visual Basic. Когда вы открываете диалоговое окно References в Visual Basic (из меню Project), в выпадающем списке доступных ссылок отображаются различные библиотеки типов, зарегистрированные в системе (под uuid, приведенном перед оператором library, с ключом TypeLib в системном реестре). Для вывода с выпадающем списке используется строка, определяемая оператором helpstring, так что класс Account, как мы убедились в главе 4, "Клиенты СОМ: концепции и программирование", может быть найден под именем "Bank 1.0 Type Library". Coclass и Visual Basic Оператор coclass используется в Visual Basic для определения класса, применяемого в программе-клиенте. Visual Basic не работает с интерфейсами непосредственно—у вас есть только класс. Методы, которые вы можете использовать, являются методами интерфейса по умолчанию (с атрибутом default), определенного в IDL. Если вы хотите использовать методы другого интерфейса в Visual Basic, то должны объявить ссылку на второй интерфейс и присвоить ей ссылку на первоначальный объект (неявно вызывая тем самым Querylnterface). С такого рода кодом мы уже встречались в главе 4, "Клиенты СОМ: концепции и программирование". Option Explicit Dim objAccount As New Account Private Sub cmdDeposit_Click() objAccount.Deposit txtAmount Dim balance As Long objAccount.GetBalance balance txtBalance = balance End Sub Private Sub cmdShow_Click() Dim objDisplay As IDisplay Set objDisplay = objAccount objDisplay.Show End Sub Private Sub cmdWithdraw_Click() objAccount.Withdraw txtAmount Dim balance As Long objAccount.GetBalance balance txtBalance = balance End Sub Private Sub Form_Load()
Dim balance As Long objAccount.GetBalance balance txtBalance = balance End Sub Код C++ для второго интерфейса Итак, продолжим создание нашего демонстрационного проекта. Давайте отредактируем код в файле account.h. 6. Добавьте к предкам класса CAccount класс iDisplay. 7. Добавьте в сом_мар запись com_interface_entry для iDisplay. 8. Теперь вы можете добавить метод Show, щелкнув правой кнопки мыши на IDisplay в дереве просмотра, что на панели Workspace (воспользуйтесь IDisplay внутри CAccount). Метод Show не имеет параметров. 9. Реализуем метод show выводом баланса в окне сообщений. В заголовке окна выводится имя интерфейса и метода: IDisplay: :Show. Проект теперь находится в состоянии, идентичном состоянию проекта в каталоге Chap7\BankWiz\Step2. В нашем проекте не хватает только метода withdraw, который при желании вы можете добавить самостоятельно. // Account.h : Объявление CAccount class ATL_NO_VTABLE CAccount : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAccount, &CLSID_Account>, public IAccount, public IDisplay { public: CAccount() : m_nBalance(100) { } DECLARE_REGISTRY_RESOURCEID(IDR_ACCOUNT) DECLARE_NOT_AGGREGATABLE(CAccount) DECLARE_PROTECT_FINAL_CONSTRUCT() BEGIN_COM_MAP (CAccount), COM_INTERFACE_ENTRY(IAccount) COM_INTERFACE_ENTRY (IDisplay) END_COM_MAP() public: // IDisplay STDMETHOD(Show)(); // IAccount STDMETHOD(Withdraw)(/*[in]*/ int amount); STDMETHOD(GetBalance) (/*[out]*/ int* pBalance); STDMETHOD(Deposit)(/*[in]*/ int amount); protected: int m_nBalance; };
// Account.cpp : Реализация CAccount STDMETHODIMP CAccount::Show() { char buf[80]; wsprintf(buf, "Balance = %d\n", m_nBalance); ::MessageBox(NULL, buf, "IDisplay::Show", MB_OK); return S_OK; } Проект сервера на этой стадии содержится в каталоге chap7\BankWiz\step2. Он практически идентичен проекту, описанному в главе 4, "Клиенты СОМ: концепции и программирование", хотя в нем и не хватает интерфейса iGreet, проект с которым вы можете найти в каталоге Chap7\BankWiz\Step3. Классы-оболочки ATL ATL предоставляет ряд классов-оболочек, которые могут еще больше упростить некоторые аспекты СОМ-программирования. Как мы увидим в следующей главе, Visual C++ предоставляет ряд своих собственных классов с подобными возможностями. Эти ATL-классы определены в файле atlbase.h. CComBSTR В главе 4, "Клиенты СОМ: концепции и программирование" мы узнали, что СОМ использует для передачи строковых аргументов Unicode, а многие функции работают со специальной разновидностью строк Unicode — BSTR. Напомним, что BSTR (Basic String) имеет 16-битовый префикс. На уровне Win32 память для BSTR выделяется функцией SysAllocString, а освобождается с помощью вызова SysFreeString. ATL обеспечивает класс-оболочку CComBSTR, который хранит строку BSTR, выделяя память для нее в конструкторе и освобождая в деструкторе. Класс имеет ряд функций для копирования, конкатенации строк и т.п. Имеется также неявный оператор преобразования типа, конвертирующий CComBSTR в BSTR. Пример использования CComBSTR можно найти, рассмотрев третий шаг построения сервера банковского счета, в котором реализуется второй класс, Greet, со свойством Greeting, представляющим собой BSTR. Следующий код реализует метод get_Greeting. STDMETHODIMP CGreet::get_Greeting(BSTR *pVal) { CComBSTR bstr("Welcome to Fiduciary Bank"); *pVal = bstr.Detach(); return S_OK; } Первая строка создает CComBSTR из обычной строки. Память для строки при этом выделяется автоматически. Вторая строка присваивает bstr выходному значению. Без вызова Detach строка bstr была бы удалена в деструкторе при выходе за пределы области видимости, что, конечно, далеко не то, чего мы ожидаем. Вызов Detach решает эту проблему, поскольку после этого bstr больше не является частью объекта CComBSTR и не удаляется деструктором. Visual C++ предоставляет подобный класс-оболочку _bstr_.
Интеллектуальные указатели Непосредственная работа с обычными указателями на интерфейсы часто приводит к ошибкам, поскольку вы должны сами вызывать AddRef и Release. ATL предоставляет класс "интеллектуального указателя" ccomPtr, который "интеллектуально" вызывает AddRef при присвоении и Release при выходе из области видимости. Класс CComQlPtr имеет дополнительную возможность, обеспечиваемую перегрузкой оператора присвоения, которая обеспечивает вызов Querylnterface при присвоении интеллектуального указателя одного типа указателю другого типа. Visual C++ предоставляет собственный класс-оболочку _com_ptr_t, который несколько более тяжеловесен, чем ccomPtr. Поддержка "интеллектуальных указателей" компилятором Visual C++ сводится к тому, что код для их поддержки автоматически включается в проект при импортировании библиотеки типов. Мы рассмотрим эту возможность в следующей главе. Резюме В этой главе мы изучили Active Template Library (ATL), представляющую собой мощную и гибкую библиотеку шаблонов классов C++, которая помогает в построении СОМ-объектов. Используя ATL и соответствующих мастеров Visual C++, вы легко сможете создать СОМ-сервер "с нуля". ATL автоматизирует реализацию фабрик классов и саморегистрацию. Ключевым моментом при работе с Active Template Library является возможность применения IDL, определяющего интерфейсы, библиотеки типов и соклассы. Библиотеки типов и соклассы приобретают особую важность при использовании Visual Basic. ATL также обеспечивает полезные классы-оболочки указателей на интерфейсы и некоторых типов данных СОМ, таких как BSTR. В следующей главе мы рассмотрим аналогичные классы, предоставляемые Visual C++, и поддержку импортирования библиотек типов.
Глава 8 Поддержка СОМ в Visual C++ В главе 7, "Active Template Library"мы узнали, как использовать ATL для упрощения реализации СОМ-сервера. В этой главе рассматриваются различные возможности Visual C++, которые облегчают создание клиентов. Одна очень важная возможность — применение "интеллектуальных указателей ", берущих на себя большую часть рутинной работы при создании клиента. Например, интеллектуальный указатель автоматически вызывает Release при выходе из области видимости, тем самым упрощает жизнь программисту. Интеллектуальные указатели, кроме того, незаметно для программиста вызывают функцию Querylnterface при присвоении. Visual C++ поддерживает возможность "импортирования", при которой читаются библиотеки типов и автоматически включается поддержка интеллектуальных указателей. Visual C++ предоставляет ряд классов для упрощения работы с типами данных СОМ, такими как BSTR. Конечный результат заключается в программной среде C++, обеспечивающей такие удобства, упрощающие создание С О М-приложений, какие были доступны только программистам на Visual Basic. Перед вами — короткая глава, которая сделана отдельной от главы 7, "Active Template Library" с тем, чтобы вы помнили, что поддержка СОМ- компилятором Visual C++ не зависима от ATL. Visual C++ и клиенты СОМ Visual C++ значительно упрощает процесс создания СОМ-клиентов, обеспечивая: ■ директиву импорта, которая позволяет импортировать библиотеки типов и автоматически создавать соответствующие классы-оболочки C++; ■ интеллектуальные указатели, которые могут использоваться для инкапсуляции указателей на интерфейсы, автоматически вызывая AddRef И Release; ■ механизм исключений C++ используется для облегчения обработки ошибок.
Демонстрационная программа-клиент на Visual C++ В этом разделе представлена полная демонстрационная программа, использующая поддержку интеллектуальных указателей Visual C++ для создания программы-клиента СОМ. ■ Проект программы-клиента находится в каталоге Chap8\Deraos\SmartClient (с копией в Chap8\SmartClient\Prelim). ■ Каталог сервера Chap7\Demos\BankWiz или Chap7\BankWiz\Stepl. ■ Окончательный вариант демонстрационной программы находится в каталоге Chap8\SmartClient\StepO. Стартовый проект 1. Рассмотрите начальный проект, обеспечивающий графический интерфейс пользователя для применения объекта Account. Использование библиотеки типов 2. Скопируйте banks. tlb из каталога сервера в каталог клиента. 3. Добавьте в stdafx.h приведенные ниже директивы. Последняя директива специфична для поддержки Visual C++ СОМ-клиентов. #include <afxwin.h> tinclude <afxext.h> #include <a£xdisp.h> #import "bank.tlb" no_namespace 4. Постройте ваш проект, чтобы убедиться, что он компилируется после внесенных изменений. Использование интеллектуальных указателей 5. Проведите простой тест использования СОМ сервера, выполнив следующие операции в OnlnitDialog. • Создайте указатель на СОМ объект. • Вызовите метод GetBalance. • Выведите баланс в окне сообщения. SetDlgltemlnt(IDC_AMOUNT, 25); IAccountPtr pAccount("Bank.Account.1"); int balance; pAccount->GetBalance(&balance); CString strBal; strBal.Format("%d", balance); MessageBox(strBal, "Balance"); Класс интеллектуального указателя IAccountPtr был создан при импортировании библиотеки типов. Идентификатор программы Bank.Account. 1 передается конструктору в качестве аргумента.
Тестирование и обработка ошибок 6. Постройте и запустите приложение. Программа должна немедленно аварийно завершить работу, не выводя никакой^информации. 7. Поместите наш код в блок try и создайте обработчик catch. В случае сбоя будет сгенерировано исключение с типом ссылки на _com_error. Этот класс имеет функцию-член ErrorMessage, которая возвращает строковое сообщение об ошибке. SetDlgltemlnt(IDC_AMOUNT, 25); try { IAccountPtr pAccount("Bank.Account.1"); int balance; pAccount->GetBalance(Sbalance); CString strBal; strBal.Format("%d", balance); MessageBox (strBal, "Balance"); } catch (_com_error &ex) { MessageBox(ex.ErrorMessage()); } 8. Постройте и выполните приложение после внесения этих изменений. Теперь вы получите сообщение, поясняющее, почему произошло аварийное завершение работы (рис. 8.1; мы не инициализировали OLE). 9. Поскольку наш клиент представляет собой MFC-приложение, OLE можно инициализировать вызовом AfxOlelnit в Initlnstance. BOOL CBankClientApp::Initlnstance() { if (!AfxOlelnit()) { AfxMessageBox("AfxOlelnit failed"); return FALSE; } 10. Постройте и запустите приложение еще раз. Теперь все должно корректно работать, как показано на рис. 8.2. Рис. 8.1 Сообщение об ошибке Рис. 8.2. Окно сообщения с информацией о начальном балансе
Завершение программы-клиента Эта демонстрационная программа выполнила минимальный тест по подключению к серверу. Отсюда идет прямой путь к разработке программы, полностью использующей сервер — взгляните на программу, находящуюся в каталоге Chap8\SmartClient\step2, которая работает с сервером из каталога Chap7\BankWiz\step2. (Наиболее интересен в данном случае процесс инициализации интеллектуального указателя. При этом нет вызова CoCreatelnstance или Queryinterfасе — все происходит в конструкторе класса диалога.) CSmartClientDlg::CSmartClientDlg(CWnd* pParent /*=NULL*/) : CDialog(CSmartClientDlg::IDD, pParent), mjpAccount("Bank.Account.1") { injpDisplay = mjpAccount; m_hIcon = AfxGetAppO->LoadIcon(IDR_MAINFRAME)/ } Пространства имен В нашем примере при импортировании библиотеки типов мы воспользовались д и- рективой no_namespace. Это несколько упростило наш код. Однако, если мы используем два или больше серверов СОМ в одной и той же программе, могут возникнуть конфликты имен. Пример "Hello" из главы 3, "Полигон для испытаний Windows DNA" дает нам соответствующую иллюстрацию. Программа-клиент HelloClientvc вызывает сервер VB и сервер VC. Если игнорировать вопросы пространства имен, наша программа в силу множественности определения имен компилироваться не будет. Простейшим решением этой проблемы являются пространства имен. Не будем отменять их использования в директиве #import. В коде C++ после этого вы можете применить оператор using namespace. void CHelloClientVCDlg::OnVb() { using namespace HelloVB; try { _GreetPtr spGreet("HelloVB.Greet")/ _bstr_t greeting = spGreet->GetGreeting(); SetDlgltemText(IDCJ3REETING, (const char*) greeting); } catch (_com_error &er) { MessageBox(er.ErrorMessage()); } } Устранение неоднозначностей в Visual Basic Подобные вопросы возникают и в программах-клиентах СОМ на Visual Basic. В Visual Basic нет пространств имен, а потому решение состоит в использовании полностью определенных имен, в которых в качестве префикса используется имя библиотеки. Вот соответствующий код из EarleClientVB из главы 3, "Полигон для испытаний Windows DNA".
Private Sub cmdVB_Click() On Error GoTo ErrorHandler Dim objGreet As New HelloVB.Greet txtGreeting = objGreet.Greeting Exit Sub ErrorHandler: MsgBox Err.Description End Sub Private Sub cmdVC_Click() On Error GoTo ErrorHandler Dim objGreet As New HELLOLib.Greet txtGreeting = objGreet.Greeting Exit Sub ErrorHandler: MsgBox Err.Description End Sub Классы поддержки COM в Visual C++ Visual C++ предоставляет ряд классов поддержки СОМ. Мы уже рассмотрели класс _com_ptr_t, а в главе 11, "Автоматизация и программирование СОМ на Visual Basic" познакомимся с _variant_t. В этом разделе мы обсудим классы _bstr_t и _com_error. Jbstrjt Класс _bstr_t является оболочкой для типа bstr. Он подобен классу ATL CComBSTR, но несколько более тяжеловесен. Этот класс применяется для упрощения кодирования ситуаций, в которых метод СОМ или вызов API возвращает строку bstr. Конструктор инициализирует _bstr_t строкой bstr. Затем вы можете конвертировать строку bstr в обычную с помощью оператора преобразования типа (const char *). Освобождение строки происходит при выходе _bstr_t из области видимости автоматически. На примере уже рассматривавшейся в главе 3, "Полигон для испытаний Windows DNA" программы HelloClientvc мы покажем, как можно использовать _bstr_t. void CHelloClientVCDlg::OnVb() { using namespace HelloVB; try { _GreetPtr spGreet("HelloVB.Greet"); _bstr_t greeting = spGreet-XSetGreeting(); SetDlgltemText(IDC_GREETING, (const char*) greeting); } catch (_com_error &er) { MessageBox(er.ErrorMessage()); } }
_com_error COM не генерирует исключения. О возникновении ошибок СОМ сообщает посредством возвращаемого значения hresult. Кроме того, имеется несколько специальных интерфейсов, которые могут поддерживать получение расширенной информ а- ции об ошибках. Механизм обработки ошибок СОМ будет рассмотрен в главе 12, "Обработка ошибок и отладка". Visual C++ поддерживает классы, которые при возникновении ошибки генерируют исключения типа _com_error. Кроме того, если класс сервера поддерживает СОМ интерфейсы для работы с ошибками, дополнительная информация об ошибке сохраняется в объекте исключения. Метод ErrorMessage возвращает строку, описывающую произошедшую ошибку. Только что рассмотренный код иллюстрирует, помимо применения пространства имен и _bstr_t, использование _com_error. void CHelloClientVCDlg::OnVb() { using namespace HelloVB; try { _GreetPtr spGreet("HelloVB.Greet"); _bstr_t greeting = spGreet->GetGreeting(); SetDlgltemText(IDC_GREETING, (const char*) greeting); } catch (_com_error &er) { MessageBox(er.ErrorMessage()); } } Резюме В этой главе показано, как можно использовать предоставляемую Visual C++ поддержку СОМ для упрощения реализации программ-клиентов СОМ. Ряд совместно используемых возможностей облегчает жизнь программиста. Импортирование библиотеки типа сервера дает доступ к интеллектуальным указателям. Вы не работаете с классом интеллектуального указателя _com_ptr_t непосредственно, а применяете классы с именами, порожденными от имен из библиотеки типов. Конфликты имен могут быть разрешены с помощью пространств имен C++. Интеллектуальные указатели исключают необходимость явных вызовов CoCreatelnstance или Querylnstance. Visual C++, кроме того, обеспечивает классы поддержки типов данных, таких, например, как _bstr_t, который является оболочкой BSTR. Все классы поддержки СОМ в Visual C++ генерируют исключительные ситуации при возникновении ошибок СОМ, информация о которых передается через возвращаемое значение hresult. Таким образом для обработки ошибок СОМ может применяться механизм исключений C++. Классы поддержки Visual C++ подобны классам ATL, но классы Visual C++ более легки в использовании, а классы ATL ориентированы на меньший код и большую производительность. Так, классы ATL не генерируют исключений. Итак, теперь "заручившись" поддержкой СОМ в Visual C++ и ATL, мы можем перейти к изучению некоторых дополнительных вопросов программирования СОМ, включая создание ЕХЕ-сервера и применение DCOM.
Глава 9 ЕХЕ-серверы В предыдущих главах мы рассмотрели базовый С О М-протокол, построили СОМ-сервер, работающий в контексте приложения, и познакомились с ATL. Теперь у нас имеются все необходимые для следующего шага инструменты. Следующий шаг будет состоять в реализации СОМ-сервера, работающего как отдельное приложение, вне контекста приложения-клиента. Созданные в виде отдельного ЕХЕ СОМ-серверы представляют особый исторический интерес в связи с сопряжением СОМ и OLE, которое осуществлялось с помощью ЕХЕ-серверов. Мы начнем с обсуждения различных подходов к интеграции приложений, включая DDE и OLE. Затем мы обсудим вопросы, возникающие при создании ЕХЕ-серверов, и рассмотрим, в чем состоит отличие последних от DLL-серверов. Затем мы покажем, каким образом реализуются ЕХЕ-серверы объектов, поддерживающих стандартные интерфейсы — как на уровне простого СОМ, так и с применением ATL. После этого мы рассмотрим применение IDL, его компиляцию RPC компилятором MIDL для создания прокси и заглушек, что даст нам возможность построить ЕХЕ-сервер и пользовательский интерфейс. Интеграция приложений и OLE Windows изначально создавалось как рабочее окружение с графическим интерфейсом пользователя поверх DOS. Первоначально приложения Windows были автономны и не могли обмениваться информацией друг с другом (за исключением данных, передаваемых через системный буфер обмена). Вскоре стало понятно, насколько полезна была бы возможность программного обмена информацией между приложениями. Динамический обмен данными (Dynamic Data Exchange — DDE) явился первой попыткой Microsoft создать стандартный протокол, позволяющий приложениям Windows общаться друг с другом. DDE основывался на передаче сообщений Windows. OLE 1.0 представлял собой более сложный протокол для интеграции приложений, поддерживающий составные документы, но его связь между процессами была основана на DDE. В OLE 2.0 были внесены существенные улучшения (по сравнению с OLE 1.0) и введен СОМ. Во всех этих случаях между собой общались приложения, т.е. ЕХЕ-модули. В этом разделе мы вкратце рассмотрим вопросы интеграции ЕХЕ-приложений. Сообщения Windows и DDE Рассмотрим два отдельных приложения Windows, которым требуется общение между ними. Каким образом может быть реализовано такое общение? Для этого имеется ряд технологий, включающих примитивы one-
рационной системы типа отображенных в память файлов, каналов и т.п. В среде Win32 базовым видом межпроцессного общения служат сообщения Windows, поскольку каждое приложение Windows имеет очередь сообщений и одно приложение может отсылать сообщения окну другого приложения. Для иллюстрации того, каким образом два приложения Windows могут общаться с помощью системы сообщений, рассмотрим сервер банковского счета в каталоге Chap9\Wsrv. Здесь имеется два проекта: сервер "hello" и клиент "wcli". Оба они представляют собой ЕХЕ-модули. Построим оба приложения и запустим сервер, а затем клиент. Разместим их окна рядом, и щелкнем на кнопке Get Balance. Мы получим стартовое значение баланса 100, при этом в окне клиента появится сообщение "Get Balance", как показано на рис. 9.1. Рис. 9.1. Связь с помощью сообщений Windows Этих два приложения связываются между собой с помощью частного протокола, использующего три зарегистрированных сообщения Windows, и который понимают оба приложения. Приложения могут использовать любой частный протокол — следует только отдавать отчет в том, что такой протокол не является протоколом общего назначения. Для обмена данными между приложениями Windows был разработан стандартный протокол DDE, реализованный во множестве приложений Windows. Этот протокол сложен для программирования и не слишком надежен. Например, в зависимости от объема потока сообщений могут возникнуть проблемы тайм-аутов и им подобные. Второе ограничение заключается в том, что оба приложения должны знать формат используемых данных, что затрудняет их взаимодействие. OLE 1.0 Технология OLE 1.0 была представлена Microsoft в июне 1991 года в качестве первой попытки обеспечить объектно-ориентированный механизм для интеграции приложений. В OLE 1.0 введена концепция составного документа, который мог бы содержать объекты других приложений. Эта технология была развита из работ над PowerPoint, чьи разработчики добивались возможности внедрения элементов Microsoft Graph в свои документы. В OLE 1.0 была предпринята попытка преодолеть ограничения предыдущих интеграционных технологий. Используя системный буфер обмена, пользователи могут вставлять в свой документ статический снимок данных из другого приложения. Редактирование таких данных — очень сложная и громоздкая задача — данные должны редактироваться исходным приложением, а затем быть снова вставлены в документ. Не существует способа заставить системный буфер поддерживать связи, чтобы изменения в оригинальных данных автоматически отражались на данных в документе-контейнере. Использование DDE позволяет поддерживать связи с исходными данными, но контейнеру требуется наличие собственного кода для представления данных в корректном формате.
Связывание и внедрение объектов OLE 1.0 позволяет внедрить4 объект в контейнер. Объект содержит статический рисунок и оригинальные данные, необходимые для редактирования объекта (для чего вызывается исходное приложение). Обычно, чтобы отредактировать объект, необходимо дважды щелкнуть на нем мышью. При этом исходное приложение запускается в отдельном окне. По окончании работы пользователь может сохранить изменения, и данные в документе-контейнере будут обновлены. Несмотря на преимущества перед предыдущими технологиями, OLE 1.0 имеет ряд ограничений. ■ Технология OLE 1.0 реализована на основе DDE, который по природе своей асинхронен. При вызове функции возврат происходит немедленно, и требуется ожидание в цикле сообщений с постоянной проверкой флага состояния. ■ OLE 1.0 при передаче данных между приложениями опирается исключительно на разделяемую глобальную память. Большие блоки данных перед пересылкой должны копироваться в память (откуда они тут же могут быть отправлены на диск — в файл подкачки). ■ Связи OLE 1.0 легко разрываются при перемещении файлов. ■ Пользователю неудобно редактировать внедренные объекты в отдельном окне. OLE 2.0 Технология OLE 2.0 была представлена в мае 1993 года. Первоначальная ее цель состояла в улучшении OLE 1.0, однако в процессе разработки технология перешагнула поставленные перед ней рамки поддержки составных документов (OLE больше не является аббревиатурой, используемой для обозначения связывания и внедрения объектов). В OLE 2.0 (по сравнению с OLE 1.0) были внесены значительные улучшения. ■ DDE был заменен на более мощный и менее громоздкий протокол удаленного вызова процедур. ■ Новый механизм унифицированной передачи данных (uniform data transfer — UDT) предоставляет ряд альтернатив разделяемой памяти для эффективной передачи данных. ■ Новый механизм имен обеспечивает отслеживание связей. ■ Визуальное редактирование или активизация "на месте" (in-place activation) позволяют редактировать внедренные или связанные объекты в контексте приложения-контейнера. ■ Кроме всего прочего, OLE 2.0 основывается на строго определенной модели компонентных объектов СОМ. Основанная на COM, OLE 2.0 является расширяемой технологией. В результате новые возможности могут добавляться инкрементально, просто определением дополнительных интерфейсов. В результате номер версии перестал иметь особое значение, и технология теперь называется просто OLE. Демонстрация OLE Детальное рассмотрение OLE выходит за рамки данной книги. Нас интересует другая грань технологии СОМ, приводящая к разработке многоуровневых приложений с применением СОМ+. Для того чтобы понять, на что похожа технология OLE, 4 Для разрядки сообщим, что один из предлагавшихся в качестве перевода аббревиатуры OLE вариантов звучал так- ПИВО — Привязывание И Внедрение Объектов — Прим. перев.
достаточно потратить несколько минут на изучение примера приложения, использующего ее. В качестве примера выступают две стандартных MFC-программы из поставки Visual C++ — hiersvr и oclient. Оба они находятся в разделе OLE программ-примеров MFC. Третья программа — STR из каталога Chap9. 1. Скомпилируйте hiersvr и запустите эту программу в автономном режиме. Ее запуск автоматически зарегистрирует сервер. Попытайтесь добавить несколько узлов и сохранить выполненную работу в файле hierl .hie. 2. Скомпилируйте программу STR и запустите ее. Введите несколько символов и при желании измените цвет. Сохраните созданное вами в файле strl. str. 3. Скомпилируйте программу oclient и запустите ее. Воспользуйтесь командой меню Edit«=>lnsert New Object, для того чтобы вызвать диалоговое окно Insert New Object. Выберите Insert from file и перейдите к файлу hierl.hie. Вставьте одну копию как внедренный объект, а вторую — как связанный объект (обратите внимание на соответствующий переключатель). Вставьте файл strl. str как внедренный объект. 4. Дважды щелкните на внедренном объекте. Оба сервера поддерживают активацию "на месте", так что вы увидите изменение меню и панели инструментов для поддержки редактирования внедренного объекта в окне контейнера с использованием сервера (рис. 9.2). Рис. 9.2. Внедренные и связанные объекты, один из которых был активизирован 5. Внесите некоторые изменения в редактируемый объект и сохраните документ- контейнер. Закройте файл документа-контейнера и вновь откройте его. Вы должны увидеть, что внесенные вами изменения в действительности были приняты. Теперь откройте исходный файл strl.str и убедитесь, что оригинальные данные в нем остались без изменений. В документе-контейнере вы работали со внедренным объектом, заполненным собственными данными, не зависящими от данных в файле. Файл использовался только один раз — как первоначальный источник данных для объекта. 6. Теперь дважды щелкните на связанном объекте (он окружен пунктирной л и- нией). Приложение-сервер откроет его в отдельном окне. Внесите некоторые изменения и сохраните их. Теперь вы работаете с исходным файлом, поскольку пользуетесь связью.
Интеграция приложений и ЕХЕ-серверы И str, и hiersvr представляют собой ЕХЕ-серверы. Это — автономные приложения, каждое из которых может работать как обычное отдельное приложение или вызываться приложением-клиентом, таким как oclient. При их запуске в качестве серверов возникает межпроцессная связь, которая и является основной темой этой главы. OLE 1.0 для связи между процессами использует сообщения Windows (DDE). Такая связь изначально ограничена применением в пределах одной машины, так как сообщения Windows не могут пересылаться приложению, расположенному на другом компьютере (иногда вы можете услышать о "сетевом DDE", но в этом случае сообщения Windows для связи между машинами сети не применяются). OLE 2.0 использует в качестве протокола СОМ, а тот для связи процессов — удаленные вызовы процедур (remote procedure call — RPC), которые не ограничены применением на одном компьютере и могут передаваться по сети. Интеграция в стиле OLE дает возможность пользователю прозрачно работать со многими приложениями, благодаря чему повышается производительность его труда. Редактирование в контексте основного приложения более логично и удобно, чем открытие специального окна. Технология OLE ознаменовала переход от "приложениецен- тричности" к "документоцентричности". Пользователь работает не с приложением, а с документом, а уж во время этой работы могут вызываться самые различные приложения (работающие в качестве серверов). Однако в результате мы имеем множество огромных приложений с богатым набором возможностей, доступных через интерфейсы СОМ. Эта технология работает, но не оптимальна, так как не является программным обеспечением, основанным на компонентах. Компоненты (управляющие элементы ActiveX, иногда именуемые управляющими элементами OLE) являются не приложениями, а DLL- серверами. ЕХЕ-серверы и суррогаты У ЕХЕ-серверов есть еще одна особенность, позволяющая клиенту делать вызовы по сети. Поскольку вы не можете непосредственно вызвать сервер контекста приложения из другого адресного пространства, а тем более из другого компьютера, вы должны использовать удаленный вызов процедур. Возможно, вы считаете, что, для того чтобы сервер был доступен в сети, он должен быть реализован как ЕХЕ-сервер, но это не так. В СОМ имеется элемент, называемый суррогатом (surrogate), который представляет собой ЕХЕ, служащий в качестве внешней оболочки DLL. Таким образом, клиент может вызвать по сети суррогат, который передаст вызов DLL в адресном пространстве суррогата. В части III, "Windows DNA и СОМ+" мы увидим, что СОМ+ обеспечивает DLL-серверы суррогатами. Интерфейсы для OLE-сервера Сервер составного документа OLE должен реализовывать множество интерфейсов для поддержки интеграции, обеспечиваемой OLE. Чтобы увидеть эти интерфейсы, можно воспользоваться OLE/COM Object Viewer. На рис. 9.3 показаны интерфейсы простейшего сервера STR. Найти нужную информацию можно в разделе All Objects, но проще в поисках Str Answer Document просмотреть раздел Embed- dable Objects. В следующем разделе мы рассмотрим важность некоторых записей в системном реестре.
Рис. 9.3. Сервер составного документа OLE Структура ЕХЕ-сервера В этом разделе мы рассмотрим структуру ЕХЕ-сервера. Хотя у него очень много общего с DLL-сервером, тем не менее между ними имеется и немало отличий. ЕХЕ-сервер представляет собой приложение со своими собственными правами, и мы должны побеспокоиться, например, о том, чтобы при запуске приложения в качестве сервера его окно не выводилось на экран. Кроме всего прочего, ЕХЕ-сервер и система времени выполнения СОМ должны обеспечить маршалинг данных между сервером и клиентом. Этот маршалинг СОМ предоставляет всем стандартным интерфейсам. В этом разделе мы рассмотрим механизм, используемый ЕХЕ-сервером для реализации простого демонстрационного объекта, поддерживающего только пару стандартных интерфейсов. В следующем разделе поговорим об использовании RPC-компилятора MIDL для создания прокси и заглушек для маршалинга пользовательских интерфейсов. Имеется ряд различий между СОМ-серверами, реализованными как локальные ЕХЕ-сервера и как серверы контекста основного приложения (DLL). ■ Записи системного реестра с ключом LocalServer32 указывают на ЕХЕ- файл, в котором реализован объект. ■ ЕХЕ должен создавать экземпляры всех своих фабрик классов при запуске и регистрировать их . ■ В процессе завершения работы ЕХЕ-сервер должен уничтожить свои фабрики классов. ■ ЕХЕ имеет главное окно и цикл обработки сообщений. Главное окно не должно выводиться при запуске ЕХЕ-системой СОМ. ■ ЕХЕ должен выгрузить сам себя при обнулении счетчиков объектов и блокировок.
Маршалинг Маршалинг представляет собой механизм, который позволяет клиенту в одном процессе прозрачно вызывать методы объектов, реализованные в другом процессе. Клиент всегда выполняет вызов некоторого объекта в контексте приложения. Объект может быть полной реализацией (сервер контекста приложения), дескриптором объекта, обеспечивающим частичную реализацию, или прокси. Примером дескриптора объекта может служить составной документ OLE. Если объект активен, запускается сервер и пользователь может редактировать объект. Но даже если объект неактивен, он все равно виден в окне контейнера. На самом деле сервер может даже не быть установлен в системе пользователя — например, пользователь может получить по электронной почте составной документ от другого пользователя. Тем не менее пользователь будет видеть рисунок внедренного объекта. Если просмотреть записи системного реестра для Str Answer Document (см. рис. 9.3), то можно заметить, что ключ LocalServer32 указывает, как и ожидалось, на str.exe. Это — приложение, запускаемое, когда сервер активен. Имеется также ключ lnprocHandler32, который указывает на ole32.dll. Это дескриптор объекта, обеспечиваемый стандартной DLL ole32.dll и, следовательно, доступный в любой системе. Этот дескриптор объекта обеспечивает частичную реализацию функциональности объекта. Он не может очень многого, но в состоянии вывести метафайл, который хранится в составном документе как часть внедренного объекта. Если объект находится в другом процессе, прокси (proxy) обеспечивает межпроцессную связь с заглушкой (stub) в процессе объекта, которая осуществляет вызов объекта в контексте процесса. В случае стандартных интерфейсов прокси и заглушки предоставляет СОМ; в случае пользовательских интерфейсов о них должен позаботиться программист. Позже мы увидим, что прокси и заглушки могут генерироваться компилятором MIDL. Таким образом, все, что нам требуется — это обеспечить наличие корректного IDL. Локальный сервер Demo В качестве примера локального сервера рассмотрим объект, который поддерживает только один интерфейс iunknown. Единственная цель этого сервера — продемонстрировать создание объекта локальным сервером. Вы можете расширить ваш объект для поддержки еще одного стандартного интерфейса iPersist. Чтобы посмотреть все это в действии, выберите проект в каталоге Chap9\DemoSdk\stepl и запустите регистрационный файл demosdk.reg. Если вы откроете объект Demo Object SDK EXE в OLE/COM Object Viewer, то увидите, что выведено главное окно приложения, но приложение не будет в ы- гружено даже после освобождения указателя на интерфейс (см. рис. 9.4). Прокси Для СОМ объекта со стандартными интерфейсами система времени выполнения СОМ самостоятельно обеспечивает прокси для сервера. Прокси запускается в контексте процесса и обеспечивает ряд дополнительных интерфейсов, помимо интерфейсов, поддерживаемых объектом. Откройте объект Demo Object SDK EXE в OLE/COM Object Viewer (обратитесь вновь к рис. 9.4). Помимо интерфейса iunknown, поддерживаемого объектом, система времени выполнения маршалинга СОМ предоставляет в прокси интерфейсы iMarshal, IClientSecurity, IMultiQI и IProxyManager. Маршалинг будет более подробно рассмотрен в следующей главе.
Рис. 9.4. Некорректная работа ЕХЕ-сервера Регистрация фабрики классов Как и DLL-сервер, ЕХЕ-сервер должен реализовывать фабрику классов. Однако механизм получения клиентом указателя на фабрику классов в последнем случае будет несколько иным. ЕХЕ не может экспортировать функции подобно DLL. Вместо этого часть программы инициализации приложения создает экземпляр объекта фабрики классов и регистрирует его в СОМ. Система времени выполнения СОМ поддерживает структуру данных, известную как таблица активных объектов (Active Object Table), в которой хранятся все CLSID и связанные с ними указатели на фабрики классов. Затем, когда клиент вызывает CoGetclassObject, система времени выполнения СОМ ищет указанный CLSID в этой таблице. Если он найден, возвращается соответствующий указатель на фабрику классов. Если CLSID в таблице активных объектов не найден, система времени выполнения СОМ в поисках CLSID просматривает системный реестр и ищет значение ключа LocalServer32 этого CLSID, после чего запускает указанный ЕХЕ-сервер. Как часть процесса инициализации ЕХЕ-сервера создаются и регистрируются его фабрики классов, после чего система времени выполнения СОМ предпринимает вторую попытку поиска указателя на фабрику классов в таблице активных объектов. Ниже представлен код инициализации demosdk. Обратите внимание: первое, что должна сделать подпрограмма инициализации — инициализировать СОМ вызовом Colnitialize. BOOL InitO { HRESULT hr; hr = Colnitialize(NULL) ; if (FAILED(hr)) { MessageBox(NULL, "Colnitialize failed", "DemoSDK", MB_OK); return FALSE; }
g_pClassFactory = new CDemoClassFactory; if (g_pClassFactory == NULL) return FALSE; // Поскольку мы храним этот указатель, // для него требуется вызов AddRef. g_pClassFactory->AddRef(); hr = CoRegisterClassObject(CLSID_DemoSDK, gjpClassFactory, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_dwRegister) ; if (FAILED(hr)) return FALSE; return TRUE; } В случае успешной регистрации возвращается "ключ" g_dwRegister, представляющий значение типа dword, которое может использоваться при завершении работы программы для проведения дерегистрации фабрики классов. Значения REGCLS Четвертым параметром в вызове CoRegisterClassObject служит одна из переменных перечислимого типа данных, определяющая, каким образом регистрируется класс. Два чаще всего употребляемых значения — regcls_singleuse regcls_multipleuse. В первом случае для обслуживания многих клиентов будут запущены многие экземпляры ЕХЕ-сервера — по одному для каждого клиента. Во втором случае один экземпляр ЕХЕ-сервера в состоянии обслужить многих клиентов. Аннулирование фабрики классов По завершении программы фабрики классов должны быть аннулированы, т.е. соответствующие записи должны быть удалены из таблицы активных объектов. Если приложение пренебрегает выполнением этой обязанности, система времени выполнения СОМ может вернуть указатель на несуществующую фабрику классов. Обратите внимание на то, что один ЕХЕ-сервер может поддерживать ряд различных СОМ-классов и, соответственно, регистрировать ряд различные фабрики классов. void Unlnit() { // Противоположность вызову CoRegisterClassObject if (g_dwRegister != 0) CoRevokeClassObject(g_dwRegister); // Release освобождает фабрику классов if (g_pClassFactory != NULL) g_pClassFactory->Release() ; CoUninitialize(); }
Улучшенный ЕХЕ-сервер В проекте, расположенном в каталоге chap9\DemoSdk\stepl, представлены основные элементы ЕХЕ-сервера. Содержащийся здесь код реализует фабрику классов, регистрирует ее при запуске программы и дерегистрирует при завершении работы. Однако при запуске сервера мы получаем излишне навязчивый сервис в виде окна приложения. Сокрытие главного окна приложения С помощью OLE можно скрыть главное окно приложения при его запуске посредством. При таком запуске используется опция командной строки /Embedding или - Embedding. В приложениях Windows проверять наличие этого флага нужно до вызова ShowWindow. int APIENTRY WinMain (HINSTANCE hlnstance, HINSTANCE hPrevInstance, LPSTR IpszCmdLine, int nCmdShow) { hwnd = CreateWindow ("DemoClass", "SDK Demo", WS_OVERLAPPEDWINDOW, CWJJSEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hlnstance, NULL) ; g_hWnd = hwnd; // MessageBox(NULL, IpszCmdLine, "Command line", MB_OK); // Проверка параметров командной строки if (!strstr(IpszCmdLine, "/Embedding") && !strstr(IpszCmdLine, "-Embedding")) { ShowWindow (hwnd, nCmdShow) ; UpdateWindow (hwnd) ; } while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } Соберите вторую версию сервера (из каталога chap9\DemoSdk\step2) и зарегистрируйте ее с помощью файла demosdk.reg. Теперь воспользуйтесь OLE/COM Object Viewer для создания объекта. Вы увидите окно сообщения о создании объекта (все-таки от надоедливого главного окна приложения нам удалось избавиться). Освободите объект (при этом появится очередное окно сообщения об освобождении объекта). Казалось бы, все хорошо? Не совсем. Вызовите Task Manager (или Process Viewer). Процесс сервера hello.exe продолжает работать (рис. 9.5). Прекратите его работу.
Выгрузка приложения Для обработки выгрузки приложения следует создать вспомогательную функцию, которая будет закрывать приложение при достижении счетчиками объектов и блокировок нулевых значений. Выгрузка реализована в третьем шаге (каталог Chap9\DemoSdk\Step3), где, кроме всего прочего, имеется интерфейс IPersist, который обсудим мы в следующем разделе. Рис. 9.5. Не выгруженный из памяти ЕХЕ- сервер I/ Механизм выгрузки mechanism // Вызывайте эту функцию по достижении счетчиками // объектов и блокировок нулевых значений void ObjectDestroyed() { if (g_cObj == 0 && g_cLock == 0 && IsWindow(g_hWnd)) PostMessage(g_hWnd, WM_CLOSE, 0, 0); } Вызывайте эту функцию В IUnknown: : Release И IClassFactory: :LockServer. STDMETHODIMP_(ULONG) CDemo::Release() { if(—m_nRef == 0) { delete this; —g_cObj; ::MessageBox(NULL,"Demo object destroyed", "Info",MB_OK); ObjectDestroyed(); return 0; } return m nRef; }
STDMETHODIMP CDemoClassFactory::LockServer(BOOL fLock) { if (fLock) g_cLock++; else g_cLock—; if (g_cLock == 0) ObjectDestroyed(); return NOERROR; } Создание ЕХЕ-сервера с помощью ATL При использовании ATL создание ЕХЕ-сервера существенно упрощается. Выберите в ATL COM AppWizard опцию Executable (EXE) и добавьте объекты и методы так же, как вы это делали в случае с DLL. Создайте и зарегистрируйте прокси и заглушки для пользовательских интерфейсов. Демонстрационный ЕХЕ-сервер Итак, приступим к разработке простейшего демонстрационного ЕХЕ-сервера со стандартными интерфейсами с помощью ATL. Этот сервер подобен только что созданному без привлечения специализированного инструментария. Сервер будет поддерживать интерфейс I Unknown, а для того чтобы пример был более интересным, мы реализуем стандартный интерфейс I Persist, который проще других стандартных интерфейсов. У него имеется только один метод, GetclasslD, который просто возвращает CLSID объекта (сам по себе интерфейс I Persist бесполезен и служит в качестве основы для ряда других интерфейсов, включая IPersistStorage, IPersistStream и IPersistFile). Вы можете работать в каталоге Demos; полное решение содержится в каталоге Chap9\DemoATL. 1. Воспользуйтесь ATL COM AppWizard для создания нового проекта "DemoATL". На первом шаге выберите тип сервера Executable (EXE), как показано на рис. 9.6. 2. Вставьте новый ATL объект Demo. Измените тип на DemoATL Class (Без точки), а идентификатор программы (не зависящий от версии) — на DemoATLObject (С точкой). Измените интерфейс на iPersist. ProgID станет DemoATLObject. 1 (рис. 9.7). Атрибуты объекта: Single Threading Model, Custom Interface и No Aggregation. 3. Добавьте метод GetclasslD с выходным параметром CLSID *pClasslD. 4. Создайте код GetclasslD, возвращающий CLSID_Demo. 5. Удалите интерфейс IPersist (включая атрибуты) из IDL файла. Соберите проект и протестируйте его с помощью OLE/COM Object Viewer. He забудьте, что вас интересует имя DemoATL Class. Вы можете также провести тест с помощью нашей программы из каталога Chap4\UseCmd. Используйте в качестве ProgID "DemoATLObject.1".
Рис. 9.6. ЕХЕ-проект в ATL COMAppWizard Рис. 9.7. Свойства нового объекта Саморегистрация ЕХЕ-сервера Как и DLL-сервер, ЕХЕ-сервер должен поддерживать саморегистрацию. В отличие от применяемых для этой цели экспортируемых функций DLL, ЕХЕ-сервер получает два параметра командной строки /RegServer — для регистрации сервера; /UnregServer — для дерегистрации сервера. Проект ATL COM AppWizard автоматически реализует саморегистрацию ЕХЕ-сервера, как это делалось для DLL-серверов. Вы можете проверить, как выполняются приведенные выше команды в отношении только что созданного ЕХЕ-сервера. Вначале дерегистрируйте сервер и убедитесь в том, что это выполнено, с помощью OLE/COM Object Viewer, затем зарегистрируйте его снова. В каталоге Chap9\DemoATL имеются однострочные .bat-файлы для регистрации и дерегистрации.
Прокси и заглушки В заключение нашего рассказа об ЕХЕ-серверах поговорим о прокси и заглушках. Они не обязательны в DLL-сервере, поскольку и клиент, и сервер в этом случае находятся в одном адресном пространстве. Однако в случае ЕХЕ-сервера они необходимы, так как вызовы передаются через границы адресных пространств. Для стандартных интерфейсов СОМ делает это автоматически; однако в случае пользовательских интерфейсов для осуществления маршалинга мы должны сами создать прокси и заглушки. Visual C++ и ATL упрощают эту задачу — RPC компилятор MIDL генерирует весь необходимый код. Более того, Visual C++ ATL COM AppWizard создает управляющий файл (makefile). И нам остается только — запустить этот файл и зарегистрировать полученный результат. Мы продемонстрируем данный процесс на примере простого ЕХЕ-сервера, который возвращает имя компьютера, на котором выполняется. Этот же пример будет использоваться и в следующей главе — при обсуждении DCOM. Пример пользовательского интерфейса Мы создадим сервер Name.exe, который содержит СОМ-класс с пользовательским интерфейсом machine. Этот интерфейс имеет единственный метод, GetName, возвращающий строку с именем машины, на которой работает сервер. В приведенной ниже инструкции описаны шаги создания такого сервера. Вы можете следовать инструкции и создать свой собственный проект в каталоге Chap9\Demos; окончательный вариант проекта сервера находится в каталоге Chap9\Name. 1. Воспользуйтесь ATL COM AppWizard для создания нового проекта Name. Выберите тип сервера Executable (EXE). 2. Вставьте новый объект ATL Machine. Примите все назначенные по умолчанию имена. Атрибуты объекта: Single Threading Model, Custom Interface и No Aggregation. 3. Добавьте метод GetName с выходным параметром BSTR *pName. 4. Введите в метод GetName приведенный ниже код. Мы используем функцию Win32 GetComputerName и класс-оболочку ATL CComBSTR. STDMETHODIMP CMachine::GetName(BSTR *pName) { char buf [MAX_COMPUTERNAME_LENGTH + lb- DWORD size = MAX_COMPUTERNAME_LENGTH + 1; ::GetComputerName(buf, &size); CComBSTR bstr(buf); *pName = bstr; return S_OK; } 5. Добавьте две дополнительные команды для построения прокси/заглушек и регистрации соответствующей DLL, как показано на рис. 9.8. Соответствующее диалоговое окно вызывается командой меню Project^Settings. nmake Nameps.mk regsvr32 Nameps.dll 6. Соберите проект. Теперь вы можете изучить записи в системном реестре, воспользовавшись OLE/COM Object Viewer. Тестовую программу для нашего проекта вы найдете в каталоге Chap9\NameTest. Для применения тестовой программы
к вашему собственному серверу скопируйте файлы name. h и name_i. с в тестовый каталог. Тестовая программа настроена для работы с сервером из каталога Chap9\Name. На рис. 9.9 показан вывод работающей тестовой программы. Рис. 9.8. Добавление команд для создания прокси/заглушек Рис. 9.9. Тестовая программа для получения имени компьютера сервера Файлы ЕХЕ-сервера Кроме файлов с исходным кодом, в проекте ATL ЕХЕ-сервера имеется ряд других важных файлов. Вот эти файлы для проекта Name. Файлы, созданные мастерами Name. rgs Файл сценария реестра Name. idl Определения интерфейсов Nameps .mk Файл создания DLL прокси/заглушек Файлы, создаваемые компилятором MIDL Name. h Определения интерфейсов Name_i.c Определения всех GUID (используется только в одном компилируемом модуле) Name_p. с Реализация кода прокси/заглушек dlldata. с Код маршалинга параметров интерфейсов Файлы, создаваемые при сборке проекта Name.tlb Бинарная библиотека типов, описывающая объекты и их интерфейсы (при наличии операторов library в IDL) Name. exe Выполнимый файл сервера Nameps . dll DLL прокси/заглушек
Резюме ЕХЕ-серверы — первые серверы, в которых была реализована современная технология OLE, основанная на СОМ. У OLE были предшественники, такие как DDE, но OLE — первая технология, обеспечившая интеграцию приложений. Однако такие ЕХЕ-серверы представляют собой не совсем то, что мы сегодня подразумеваем под понятием "компонентное программное обеспечение". Они скорее обеспечивают своеобразный API к существующему приложению, чем повторно используемые примитивные компоненты. Тем не менее, только разобравшись с архитектурой и принципами работы таких ЕХЕ-серверов, можно понять архитектуру и принципы работы распределенных приложений в целом, а также то, реализуются они программистом или предоставляются операционной системой, как СОМ+. Мы рассмотрели некоторые детали структуры ЕХЕ-серверов и реализовали на низком уровне простой ЕХЕ-сервер, поддерживающий только стандартные интерфейсы. Наибольшее структурное отличие от DLL-сервера состоит в том, что ЕХЕ-сервер создает все фабрики классов в начале работы программы и регистрирует их в СОМ. Другое отличие заключается в сокрытии главного окна приложения и реализации корректной процедуры выгрузки сервера. Кроме этого, мы говорили о применении ATL для создания ЕХЕ-сервера. Пользовательские интерфейсы могут поддерживаться с помощью MIDL для создания DLL прокси/заглушек. В следующей главе мы уделим внимание локальной/удаленной прозрачности и рассмотрим распределенную СОМ, или DCOM. Зная то, каким образом создается и функционирует ЕХЕ-сервер, работающий локально, нам будет легче разрабатывать DCOM-серверы.
Глава 10 Введение в DCOM Наконец-то мы переходим к распределенным вычислениям. Одна из приятных сторон СОМ состоит в том, что поддержка распределенных вычислений — не привнесенное, а исконное свойство объектной модели. В результате все, что было изучено вами к этому моменту, остается справедливым и для распределенных вычислений. Процедура вызова объекта клиентом одна и та же, что и вызова объекта по сети, в локальном ЕХЕ- сервере и даже в DLL в адресном пространстве клиента. Эта особенность СОМ называется локальной/удаленной прозрачностью (local/remote transparency). В этой главе мы познакомимся с DCOM. Более детально DCOM будет описана в части III, "Windows DNA и СОМ+", в которой будут рассмотрены жизненно важные вопросы, касающиеся, например, безопасности, с упором на многие мощные возможности СОМ+ по поддержке распределенных вычислений. DCOM представляет собой столь интегрированную часть СОМ, что имеет смысл познакомиться с нею при рассмотрении основ. Вначале мы расскажем, каким образом сделать существующие приложения распределенными, не внося каких- либо изменений в имеющийся код. Крайне замечателен тот факт, что это может быть сделано, и к тому же без особых усилий. Ключом к этому служит системный реестр. Затем мы поговорим о программировании возможностей DCOM. Имеются некоторые дополнительные структуры данных, интерфейсы и функции API, которые обеспечивают большую гибкость и высокую производительность. Мы опишем также, каким образом инфраструктура DCOM способствует эффективности работы. И, наконец, мы рассмотрим архитектурную модель DCOM в целом, включая такие вопросы, как роль менеджера управления сервисами, способы передачи данных по сети, роль суррогатов в доступе к DLL-серверам, сетевая архитектура DCOM, а также некоторые вопросы, связанные с многопоточностью. Работа существующего СОМ- объекта в удаленном режиме Для демонстрации отношений СОМ и DCOM сделаем удаленным существующий ЕХЕ-сервер. Это сервер, который просто возвращает имя машины, на которой он работает. Нам надо внести некоторые записи в системный реестр на локальной и удаленной машинах, но сам сервер останется неизмененным. Мы также не будем вносить никаких изменений в программу-клиент.
Существующий СОМ-сервер Нашим СОМ-сервером является программа Name, разработанная в главе 9, "ЕХЕ- серверы", и находящаяся в каталоге Chap9\Name. СОМ-класс имеет единственный интерфейс, 1Маchine, с единственным методом, GetName, возвращающим строку с именем компьютера, на котором работает сервер. Программа-клиент находится в каталоге Chap9\NameTest и имеет вид простой формы с кнопкой, на которой следует щелкнуть для получения имени машины. Демонстрация DC ОМ 1. На удаленной машине создайте каталог Test с подкаталогом Debug. Скопируйте программу Name.exe в каталог Debug, а файлы Nameps.dll и NameTest.exe — в каталог Test. Скопируйте также в этот каталог и четыре регистрационных .bat-файла. 2. Зарегистрируйте сервер, запустив reg_name.bat, а прокси/заглушки — запустив reg_nameps.bat. Запустите на удаленной машине NameTest.exe. Вы должны получить имя удаленной машины, запустив клиент. Это еще не DCOM — пока что вы просто запускаете клиент и сервер вместе на другой машине. 3. Теперь вернитесь на свою локальную машину и убедитесь, что сервер и прокси/заглушки зарегистрированы. Запустите NameTest.exe и убедитесь в том, что вы в состоянии получить имя локального компьютера. 4. Теперь мы настроим локальный компьютер для запуска удаленного сервера вместо локального. Для этого запустите программу dcomcnfg.exe, что можно сделать, например, с помощью команды меню Start^Run. В выводящемся программой диалоговом окне найдите в списке приложений Name, как показано на рис. 10.1. Рис. 10.1. Программа dcomcnfg
5. Выбрав в списке Name, щелкните на кнопке Properties. Снимите отметку опции Run Application on this computer и отметьте опцию Run Application on the following computer. Щелкните на кнопке Browse и найдите удаленный компьютер с зарегистрированным сервером (рис. 10.2) Щелкните на кнопке ОК, и затем еще раз — на кнопке ОК (но уже в другом диалоговом окне). Рис. 10.2. Настройка сервера для удаленного запуска 6. Теперь запустите программу-клиент NameTest.exe на локальном компьютере и щелкните на кнопке Get Name. После небольшой паузы вы должны увидеть имя удаленного компьютера. Посредством DCOM вы вызвали сервер на удаленной машине (рис. 10.3). Рис. 10.3. Вывод имени удаленного компьютера Вопросы безопасности При использовании Windows 2000 в качестве сервера для Name. ехе могут возникнуть вопросы, связанные с настройкой по умолчанию системы безопасности, и приведенная демонстрационная программа не станет работать. Если у вас возникли трудности с работой программы, вновь обратитесь к программе dcomcnfg.exe и в ее диалоговом окне выберите третью вкладку, Default Security, как показано на рис. 10.4.
Рис. 10.4. Настройка системы безопасности по умолчанию с применением dcomcnfg. exe Щелкните на кнопке Edit Default в области Default Access Permission. При этом перед вами появится окно Registry Value Permissions со списком групп и пользователей, по умолчанию имеющих доступ к DCOM-серверам (рис. 10.5). Если вы ничего не увидите в данном окне, то это может быть связано с тем, что вы работаете с Windows 2000 Professional и не имеете доступа к административным инструментам. Попробуйте проделать то же самое на машине с Windows 2000 Server. Рис. 10.5. Пользователи и группы с доступом к сети по умолчанию В этом списке должна быть учетная запись пользователя, запускающего программу-клиент. Если вы работаете на машине с Windows 2000, то может оказаться, что доступ по умолчанию не предоставлен Administrators (или Domain Administrators). Если это так, щелкните на кнопке Add и добавьте необходимую группу или пользователя. Для того чтобы изменения вступили в силу, требуется перезагрузить машину. Точно так же вы должны проверить Default Launch Permissions и при необходимости добавить пользователя или группу.
Помимо доступа по умолчанию, система безопасности DCOM позволяет вам настроить доступ к отдельному приложению сервера. При вызове диалогового окна свойств приложения, помимо установок во вкладке Location, вы можете изменить настройки во вкладке Security. Вы можете использовать настройки по умолчанию либо задать собственные параметры доступа. Детально вопросы безопасности рассматриваются в главе 17, "Windows 2000 и безопасность СОМ+". Записи в системном реестре Фактически, вся описанная работа производится в системном реестре; .bat-файлы вносят записи в системный реестр путем вызова возможностей саморегистрации ЕХЕ- сервера и DLL прокси/заглушек. Некоторые изменения в системный реестр вносит программа dcomcnfg. Мы начнем с рассмотрения системного реестра на удаленной машине, где сервер настраивается обычным способом, и, как всегда, в этом нам будет помогать OLE/COM Object Viewer. Затем мы опишем записи в системном реестре локальной машины, которые приводят к перенаправлению запроса к объекту на удаленный сервер. Записи реестра для прокси /заглушек Первое, в чем следует разобраться, — это каким образом вызываются про- кси/заглушки. Прокси/заглушки соответствуют идентификаторам интерфейса. Откройте OLE/COM Object Viewer, в нем — Interfaces (в самом конце списка на левой панели окна), и вы увидите упорядоченный по алфавиту список интерфейсов. Найдите в нем IMachine. Просмотрите ключ ProxyStupClsid32 и найдите числовое значение CLSID. Это CLSID DLL-сервера — DLL прокси/заглушек. Для доказательства этого запустите редактор системного реестра и посмотрите на этот CLSID. Под ним вы найдете ключ lnProcServer32, указывающий DLL прокси/заглушек nameps.dll. Записи системного реестра для локального ЕХЕ-сервера Теперь приступим к рассмотрению CLSID ЕХЕ-сервера (на удаленной машине, которая не настроена для работы с DCOM). В OLE/COM Object Viewer найдите Machine Class. Здесь вы увидите знакомые CLSID, ProgID, TypeLib и прочие записи, а также новый вид записей — идентификатор приложения AppiD. Если мы покопаемся в этой записи, то не найдем ничего особо интересного. Это просто строка Name с числовым значением AppiD. Вы можете найти эту строку в списке AppiD в OLE/COM Object Viewer. На самом деле идентификаторы приложений представляют собой одну из основных групп в OLE/COM Object Viewer (рис. 10.6). Однако сейчас это не более чем зарезервированное место, используемое для DCOM. Записи системного реестра для DCOM Теперь мы можем рассмотреть, какую роль AppiD играет в настройке удаленного ЕХЕ-сервера. Вы уже настроили вашу клиентскую машину для удаленного запуска Name.exe, воспользовавшись для этого программой DCOMCNFG. Запустите ее опять. Первое окно выведет список приложений, показав представляющие их строки. Это просто список всех записей с ключом AppiD в системном реестре, в чем вы можете убедиться, сравнив список, показанный dcomcnfg с записями в Application IDs в OLE/COM Object Viewer.
Рис. 10.6. Четыре основных типа GUID в OLE/COM Object Viewer Теперь рассмотрим здписи нашего приложения Name. Вы увидите поименованное значение "Remote Server Name", присвоенное имени удаленного компьютера (в моей сети - MICRON) (рис. 10.7). Рис. 10.7. ApplD используется для идентификации компьютера, на котором запускается удаленный сервер Программирование DCOM Мы только что увидели, как легко сделать существующий СОМ-объект запускаемым удаленно, без какого бы то ни было специфичного кода DCOM. Однако, если вы знаете, что работаете в среде DCOM, можете воспользоваться оптимизацией, которая позволит вашему распределенному приложению функционировать лучше. Кроме того, "прозрачность" между локальным и удаленным запуском означает, что оба они работают, но не определяет, каким образом. В этом разделе мы изучим различные вопросы программирования DCOM, среди которых будут следующие: ■ определение сервера клиентом; ■ оптимизация сетевого трафика; ■ реализация безопасного доступа со стороны сервера и клиентй; ■ многопоточность сервера.
Определение сервера клиентом Клиент может решать, использовать хранящуюся в системном реестре информацию о размещении сервера, или определять его положение самостоятельно. Для иллюстрации этой возможности вернемся к программе dcomcnfg и восстановим настройки для нашего сервера Name, для того чтобы он всегда запускался локально (рис. 10.8). Рис. 10.8. Восстановление установок системного реестра для локального запуска сервера Теперь выберем версию клиентского приложения в каталоге ChaplO\NameTest и запустим ее. Переключатели Local/Remote, показанные на рис. 10.9, определяют, клиент должен положиться на информацию, приведенную в системном реестре, или самостоятельно определить сервер. Рис. 10.9. Использование информации из системного реестра для определения сервера Давайте поработаем с этой программой при различных установках. Введите имя удаленного сервера и выберите опцию Local — вы получите имя локального компьютера, поскольку это именно та машина, на которой работает сервер. Затем выберите опцию
Use Server — теперь программа выполнит код, который принимает решение о том, какой сервер должен быть запущен, исходя из введенного вами значения. Имя, полученное в результате работы программы, в этом случае должно совпадать с введенным вами именем удаленного компьютера. Следующим введите имя компьютера, на котором сервер не установлен (или просто случайное имя, не соответствующее ни одному компьютеру в сети) — при этом при выполнении функции CoCreatelnstanceEx произойдет сбой. И, наконец, испытайте опцию Remote — при этом при выполнении функции CoCreatelnstanceEx также произойдет сбой, так как системный реестр не настроен для удаленного запуска. Для последнего эксперимента вновь измените настройки системного реестра. Вернитесь в программу dcomcnfg, отметьте как опцию "Run application on this computer", так и опцию "Run application on the following computer", и определите имя удаленного компьютера (рис. 10.10). Рис. 10.10 Настройки системного реестра как для локальных, так и для удаленных операций Теперь вы сможете выполнить все три варианта установок Local/Remote тестовой программы. Первая опция, Local, вернет вам имя локального компьютера. Вторая опция, Remote, вернет имя компьютера, определенного в системном реестре, а третья, Use Server, вернет имя компьютера, который будет указан вами в качестве удаленного сервера. Если у вас есть такая возможность, установите программу-сервер на еще одном компьютере и проверьте корректность его работы. Очень важной способностью DCOM, продемонстрированной в этом примере, является возможность определения компьютера, на котором выполняется приложение- сервер, во время работы. Возникает естественная мысль о том, что вместо внесения компьютера в системный реестр или определения сервера клиентским приложением, во многих случаях было бы предпочтительнее задачу определения местоположения сервера отдать системе. Было бы логично, если бы система сама могла определять наименее загруженный сервер и обращаться к нему, повышая тем самым общую производительность. Такой вид балансировки загрузки является одной из возможностей СОМ+ и рассматривается в главе 23, "СОМ+ и масштабируемость".
Реализация DCOM Было бы весьма поучительно рассмотреть код только что рассмотренной клиентской программы. В ней использовано множество интересных решений. Первым сво й- ством является новый контекст выполнения. Если клиент должен иметь возможность удаленного запуска объекта, в качестве одного из флагов параметра контекста должен использоваться clsctx_remote_server. Следующей особенностью является структура данных coserverinfo, которая может использоваться для определения имени удаленной машины. Третья особенность заключается в расширенной версии функции API, применяемой для создания экземпляров объектов, — CoCreatelnstanceEx. Вместо возврата одного указателя на интерфейс эта функция возвращает массив указателей на интерфейсы, определенные в другой структуре данных — multi_qi. Контекст выполнения Имеется четыре возможных контекста выполнения. clsctx_inproc_server Код этого объекта выполняется как DLL в адресном пространстве клиента clsctx_inproc_handler Код является дескриптором объекта, представляя собой DLL с частичной реализацией функций объекта clsctx_local_server Код этого объекта выполняется как ЕХЕ на той же машине, что и клиент clsctx_remote_server Код этого объекта выполняется как ЕХЕ на машине, отличной от машины клиента Чаще всего применяются три серверные опции (и именно они будут рассматриваться в этой книге). Флаг clsctx_server представляет собой их комбинацию. Контекст выполнения используется клиентом в качестве параметра при вызове CoGetClassObject, CoCreatelnstance и CoCreatelnstanceEx. Он также используется в качестве параметра ЕХЕ-сервером при вызове CoRegisterClassObject. COSERVERINFO Эта структура данных используется для идентификации удаленной машины. Она также может использоваться для изменения установок активации сервера по умолчанию. Вот определение этой структуры: typedef struct _COSERVERINFO { DWORD dwReservedl; LPWSTR pwszName; COAUTHINFO *pAuthInfo; DWORD dwReserved2; } COSERVERINFO; Интересующее нас поле — pwszName — используется для определения имени удаленной машины. CoCreatelnstanceEx Ключевая для контроля DCOM со стороны клиента функция представляет собой расширение базовой API-функции CoCreatelnstance. WINOLEAPI CoCreatelnstanceEx( REFCLSID Clsid, // CLSID создаваемого объекта IUnknown *punkOuter, // Указатель на интерфейс
DWORD dwClsCtx, // Контекст выполнения COSERVERINFO *pServerInfо,// Имя удаленной машины DWORD dwCount, // Количество интерфейсов MULTI_QI *pResults // Массив структур MULTI_QI ); В качестве второго параметра мы передаем null, поскольку мы не выполняем агрегацию. Пятый и шестой параметры позволяют нам получить массив указателей на интерфейсы, что, по сути, представляет собой оптимизацию для уменьшения сетевого трафика. WIN32 DCOM Для использования расширенных функций DCOM, таких как CoCreatelnstanceE; вы должны определить константу препроцессора _WIN32_DC0M Это можно сделать, н; пример, посредством команды меню Project^Settings во вкладке C/C++ диалогового oki в категории Preprocessor. s MULTI_QI Эта структура данных позволяет вам получить массив указателей на интерфейсы за одно обращение по сети. Первый параметр является входящим и служит для передачи идентификатора требуемого интерфейса. Второй параметр является выходящим и служит для получения соответствующего указателя на интерфейс. Третий параметр не что иное, как показатель успешности выполненного запроса на указанный интерфейс. typedef struct _MULTI_QI { const IID *pIID; IUnknown *pltf; HRESULT hr; } MULTI_QI; Пример кода клиента После всех приведенных пояснений у вас не должно возникнуть никаких затруднений с пониманием кода клиента. Это — диалоговое приложение на базе MFC (см. файл NameTestDlg.cpp, расположенный в каталоге ChaplO\NameTest). Для выбора опций кнопками-переключателями имеется свой перечислимый тип данных, а вся интересующая нас работа выполняется в обработчике кнопки Get Name. // NameTestDlg.cpp enum ServerTypes {Local, Remote, CoServerlnfo}; void CNameTestDlg::OnGetname() { IMachine* pMachme; HRESULT hr; COSERVERINFO serverinfo; COSERVERINFO* pServerlnfo; DWORD dwContext; MULTI_QI qi = {&IID_IMachme, NULL, 0};
UpdateDataO ; if (m_LocalRemote == Local) { pServerlnfo = NULL; dwContext = CLSCTX_LOCAL_SERVER; } else if (m_LocalRemote == Remote) { pServerlnfo = NULL; dwContext = CLSCTX_REMOTE_SERVER; } else if (m_LocalRemote == CoServerlnfo) { serverinfo.dwReservedl = 0; serverinfo.dwReserved2 = 0; serverinfo.pwszName = m_ServerName.AllocSysString(); serverinfo.pAuthlnfo = NULL; pServerlnfo = Sserverinfo; dwContext = CLSCTX_REMOTE_SERVER; } else return; hr = CoCreatelnstanceEx(CLSID_Machine/ NULL, dwContext, pServerlnfo, 1, &qi); if (SUCCEEDED(hr) && SUCCEEDED(qi.hr)) { pMachine = (IMachine* )qi.pltf; BSTR bstr; hr = pMachine->GetName(&bstr); if (SUCCEEDED(hr)) { _bstr_t name(bstr); SetDlgltemText(IDC_NAME, (const char*) name); } else { MessageBox("GetName failed"); SetDlgltemText(IDC_NAME, "??"); } pMachine->Release(); } else { MessageBox("CoCreatelnstanceEx failed"); SetDlgltemText(IDC_NAME, "??"); } } DCOM и системный реестр Как и в СОМ, в данном случае расположение сервера в файловой системе определяется в системном реестре. Какая именно машина запускает сервер, определяется либо системным реестром, либо клиентом при вызове CoGetClassObject либо CoCreatelnstanceEx.
Различные опции системы безопасности объекта также могут определяться в системном реестре (права запуска, права доступа и др.)- Безопасность — важный вопрос, и ей посвящена отдельная глава 17, "Windows 2000 и безопасность СОМ+". Системный реестр содержит "главный выключатель", который может при желании использоваться для отключения DCOM на вашей машине. Установите EnableDCOM в hkey_local_machine\software\microsoft\ole равным N. Установка значения Y снова позволит использовать DCOM. Этот флаг можно также установить в программе dcomcnfg (вкладка Default Properties) на первом же экране, появляющемся при запуске программы. Оптимизация сетевого трафика Очень важным вопросом в DCOM является оптимизация сетевого трафика. Имеется два аспекта такой оптимизации. Первый из них — кодирование прикладным программистом, для которого DCOM предоставляет ряд расширений, позволяющих минимизировать путешествия по сети Второй аспект — оптимизация, выполняемая самой системой DCOM. Оптимизация программистом Общим аспектом программирования СОМ является получение указателей на интерфейс. Начальный указатель на интерфейс можно получить, вызвав CoCreatelnstance. Как мы знаем, имеется расширенный вариант этой функции — CoCreatelnstanceEx, — с ее помощью можно получить несколько указателей на интерфейсы посредством лишь одного обращения по сети. В качестве примера рассмотрим DCOM версию примера сервера банковского счета. Наш объект поддерживает два пользовательских интерфейса, lAccount и I Display. Семантика метода Show интерфейса I Display изменена, дабы он возвращал баланс, вместо того чтобы выводить его в окне сообщения (что было бы некорректно при использовании удаленного сервера). Тестовое клиентское приложение позволяет вам выбрать Prog ID в процессе работы, так что с помощью этого приложения вы можете протестировать массу различных серверов на разных узлах сети. Вы можете выбрать как удаленный, так и локальный сервер При подключении к серверу выводится начальное значение баланса. При вкладывании или снятии суммы со счета изменения баланса автоматически не отображаются — для этого следует воспользоваться кнопкой Show (рис. 10.11). Рис. 10.11. Программа-клиент для DCOM-eepcuu сервера банковского счета
DCOM-версию сервера можно найти в каталоге ChaplO\Accdcom\Server, а клиента — в каталоге ChaplO\Accdcom\client. Вот фрагмент кода клиента (файл dcomcDlg.cpp). void CDcomcDlg::OnConnect() { CLSID clsid; HRESULT hr; UpdateDataO ; hr = AfxGetClassIDFromString(m_progid, &clsid); if (FAILED(hr)) { MessageBox("Could not get class id"); return; } COSERVERINFO serverinfo; COSERVERINFO* pServerlnfo; DWORD dwContext; MULTI_QI qi[2] = {{&IID_IAccount, NULL, 0}, {&IID_IDisplay, NULL, 0}}; if (m_nLocalRemote == 0) { pServerlnfo = NULL; dwContext = CLSCTX_LOCAL_SERVER; } else { serverinfo.dwReservedl =0; serverinfo.dwReserved2 = 0; serverinfo.pwszName = m__strServer.AllocSysString(); serverinfo.pAuthlnfo = NULL; pServerlnfo = &serverinfo; dwContext = CLSCTX_REMOTE_SERVER; } hr = CoCreatelnstanceEx(clsid, NULL, dwContext, pServerlnfo, 2, qi); if (SUCCEEDED(hr) ) { m_j>Account = (IAccount* )qi[0].pltf; m—pDisplay = (IDisplay* )qi[l].pltf; int nBalance = -1; m_pAccount->GetBalance(&nBalance); SetDlgltemlnt(IDC_BALANCE, nBalance); } else MessageBox("Could not connect to server.", "OnConnect"); }
IMultiQI Воспользовавшись при создании объекта функцией CoCreatelnstanceEx, можно получить несколько интерфейсов за один раз. После создания объекта новый интерфейс можно получить, вызвав Query Inter face, который дает вам один интерфейс при каждом вызове. Однако, если воспользоваться интерфейсом IMultiQI (который имеет единственный метод QueryMultiplelnterfaces), то можно будет получить одновременно несколько интерфейсов. Этот вызов возвращает интерфейсы в массиве MULTI_QI. Самое приятное в интерфейсе IMultiQI то, что его не надо реализовывать при разработке объекта, в отличие, например, от интерфейса iunknown, о котором должен заботиться программист. В случае IMultiQI об этом позаботились другие. Каждый прокси СОМ-объекта предоставляет такой интерфейс. Вы можете увидеть его в OLE/COM Object Viewer, открыв ЕХЕ-сервер — взгляните на рис. 10.12, на котором представлена информация об ЕХЕ-сервере банковского счета. Рис. 10.12. Интерфейс IMultiQI всегда доступен Оптимизация инфраструктурой DCOM Кроме предоставления структур данных и функций, которые вы можете использовать для оптимизации сетевого трафика в DCOM, системная инфраструктура автоматически выполняет определенную оптимизацию. Рассмотрим вопросы счетчиков ссылок. DCOM кэширует все вызовы Release клиента и реально не пересылает вызовы Release по сети до тех пор, пока клиент не доведет значение счетчика до нуля. В результате реальное значение счетчика ссылок на сервере отличается от текущего, но это не важно, поскольку единственное назначение этого счетчика — удалить объект, когда он становится ненужным. Второй вопрос связан с удалением объектов. Что случится, если клиент прекратит работу, не освободив свои объекты? Это может случиться, например, при сбое в работе сети или аварийном завершении работы клиента. Объект на сервере никогда не получит освобождения от клиента и поэтому никогда не будет уничтожен. Такие объекты засоряют память сервера и могут вызывать проблемы. Решение, предоставляемое DCOM, состоит в том, что клиент периодически обращается к серверу, давая знать, что он находится в работоспособном состоянии.
Если три таких последовательных обращения пропущены, система считает, что клиент завершил работу и освобождает объект вместо клиента. Трафик, связанный с такими извещениями об активности клиента, невелик, к тому же он оптимизируется системой DCOM, собирающей извещения ото всех клиентов на одной машине в один пакет. Безопасность Безопасность является очень важным вопросом в DCOM. Здесь имеется два основных вопроса. Тот ли человек пользователь, за которого себя выдает? Имеет ли право этот пользователь делать то, что он хочет сделать? Поскольку система безопасности встроена в Windows NT, проверка клиента может быть проведена при запуске DCOM. После запуска сервер всегда может осуществить вызов системы безопасности NT по сети. DCOM умеет работать с системой безопасности NT для аутентификации и авторизации пользователей. Кроме того, права доступа могут определяться в системном реестре. Проверка также может осуществляться для каждого вызова отдельно. Настройка системы безопасности может производиться как на сервере, так и на клиентской машине. Поскольку Windows 95/98 не имеют внутренней системы безопасности, вы не можете автоматически запускать серверы DCOM; Windows 2000 имеет расширенную систему безопасности, включающую систему Kerberos. Детально вопросы безопасности будут рассматриваться в главе 17, "Windows 2000 и безопасность СОМ+". Архитектура DCOM Мы видели, что сделать удаленным существующий СОМ-объект не сложно — нужно выполнить всего лишь несколько настроек системного реестра, что упрощается применением программы dcomcnfg. Мы также познакомились с тем, как DCOM-программа может явным образом использовать специфичные для DCOM структуры данных, функции и интерфейсы. В этом последнем разделе данной главы мы рассмотрим некоторые элементы архитектуры DCOM, что позволит лучше понимать процессы, происходящие при обращении клиента к удаленному серверу посредством DCOM. Запуск сервера по сеты Напомним, что Service Control Manager (SCM) представляет собой системный сервис, отвечающий за запуск СОМ на компьютере. SCM играет жизненно важную роль в DCOM. SCM локальной машины обращается к SCM удаленной машины для запуска сервера. Если сервер на удаленной машине является сервером контекста приложения, SCM запускает процесс-суррогат, работающий между локальной машиной и сервером контекста приложения на удаленной машине. DCOM поставляется с суррогатом по умолчанию dllhost.exe. Этот суррогат используется и СОМ+, как мы увидим в части III, "Windows DNA и СОМ+". Для конкретных объектов вы можете написать свои суррогаты. На машине под управлением Windows NT SCM запускается после вызова CoRegisterClassObject. Вы можете также запустить SCM (rpcss.exe) и вручную или добавить его К HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ RunServices системного реестра для запуска при старте Windows.
Сетевые операции сервера DCOM определяет, активен ли клиент или сервер и производит соответствующее оповещение. Определение активности клиента выполняется с помощью механизма "пингования", описанного выше. DCOM также ответственен за вызов соответствующего протокола RPC. Клиент и сервер ничего не знают о том, каким образом осуществляются сетевые соединения и передача информации. Пересылка данных между машинами При работе данные от клиента должны передаваться серверу (и наоборот). На этапе пересылки возникают два важных вопроса. Первый из них касается кода для мар- шалинга данных и пересылки их с помощью механизма RPC. Для этого используются специальные DLL, которые на стороне клиента называются прокси, а на стороне сервера — заглушками. RPC-механизм скрыт за прокси и заглушками. Имеются некоторые следы такой обработки с помощью интерфейса marshal, что можно увидеть в OLE/COM Object Viewer (см. рис. 10.12). Так же, как и в случае iMultiQi, вы не должны реализовывать интерфейс iMarshal самостоятельно; впрочем, при необходимости выполнения некоторой специальной работы вы, конечно же, можете реализовать его самостоятельно. Второй вопрос заключается в описании данных, необходимом при перемещении сложных структур данных. Данные описываются с помощью IDL, независимо от используемой вычислительной платформы и языка программирования. Microsoft IDL компилятор (MIDL) генерирует на его основе код прокси и заглушек для беспрепятственного маршалинга данных. MIDL генерирует также библиотеки типов. Сетевая архитектура DCOM Концептуально сетевая архитектура DCOM очень проста. Клиент осуществляет вызов интерфейса через прокси, который обращается к RPC. RPC использует сетевой протокол (например, TCP/IP) для обращения к другой машине. На сервере RPC обращается к заглушке, которая передает вызов интерфейса серверу. С точки зрения клиента прокси представляет собой объект, зарегистрированный на машине клиента. С точки зрения сервера клиентом является заглушка. Объекты сервера должны быть зарегистрированы на машине сервера. Это базовая архитектура изображена на рис. 10.13. Вопросы многопоточности Что неверно в приведенном фрагменте? ULONG Imylnterface::Release() { m_lRef-; if (m_lRef == 0) { delete this; return 0; } else return m_lRef; }
НЕТ гарантии, что уменьшение m_lRef — атомарная операция. Возникающие проблемы зависят от того, как СОМ-объект выполняет различные потоки. Избежать проблем можно, воспользовавшись API-функциями Interlockedlncrement и interlockedDecrement вместо операций увеличения и уменьшения. Это — всего лишь простейший пример проблем, которые могут возникнуть при работе с многопоточностью. Клиент Сервер ISomelnterface ISomelnterface Прокси Заглушка Сеть RPC RPC Рис. 10.13. Сетевая архитектура D СОМ Важность многопоточности для DCOM Компоненты DCOM должны быть масштабируемыми. Сервер должен иметь возможность работать со многими клиентами, не блокируя их. Методы компонентов DCOM могут вызываться различными потоками. DCOM в состоянии компенсировать отсутствие поддержки многопоточности в компонентах маршалингом и упорядочением вызовов методов, но это приводит к слишком большим потерям производительности. Более подробно о многопоточности в СОМ рассказывается в главе 13, "Многопоточность в СОМ".
Резюме DCOM является составной частью СОМ. Объекты СОМ могут быть автоматически настроены для удаленного вызова клиентом по сети без изменения кода сервера или клиента. Ключом к этой замечательной возможности является системный реестр и система времени выполнения СОМ. С помощью системного реестра вы указываете, на кокой машине должен работать сервер. DCOM также обеспечивает программиста рядом структур данных, функций API и интерфейсов, предназначенных для работы с DCOM. Благодаря их применению достигается большая гибкость и производительность приложений. Сетевая архитектура DCOM очень проста и использует RPC для прозрачного вызова объектов через прокси. DCOM — один из фундаментальных протоколов, используемых СОМ+ (другими являются HTTP и MSMQ). В части III, "Windows DNA и СОМ+" мы рассмотрим распределенное программирование более детально, и там же продолжим обсуждение DCOM. В этой главе вы получили только базовые сведения, которые помогут вам в дальнейшем изучении СОМ+.
Глава 11 Автоматизация и программирование СОМ на Visual Basic Ряд важных концепций программирования СОМ адресован программистам на Visual Basic. Как правило, сценарий СОМ-разработки включает создание компонента в одной среде, например, в такой как Visual C++ (с использованием A TL), и его применение программой-клиентом, созданной с помощью других языков программирования, например Visual Basic или языков сценариев. К языкам сценариев относятся VBScript и JavaScript. Применение языков сценариев вызывает определенные сомнения, поскольку они должны работать с СОМ, не используя преимуществ компиляции, а значит, программа-клиент должна иметь возможность выполнять позднее связывание с сервером. Для решения этой проблемы СОМ предоставляет интерфейс IDispatch. Однако ранее связывание гораздо более эффективно, и большинство компонентов СОМ реализует и интерфейс IDispatch, и виртуальную таблицу интерфейсов— так называемый "двойной интерфейс". Механизм позднего связывания в СОМ обеспечивается технологией, известной как "автоматизация" (automation), дающая возможность пользователям приложений "автоматизировать " часто выполняемые задачи. Автоматизация связана с потребностями программистов Visual Basic и включает базовую модель методов, свойств и событий. Автоматизация также имеет отношение ко многим специализированным типам данных, включая VARIANT, Currency и уже рассмотренный нами тип BSTR Автоматизация также обеспечивает простой списочный механизм, известный под названием "коллекция ". В этой главе пять разделов. В первом рассматриваются основные принципы автоматизации, включая позднее связывание и тип данных variant. Во втором разделе рассматривается использование сервера автоматизации СОМ, реализованного с применением ATL из страницы HTML, запрограммированной с помощью VBScript. Это классический случай, когда автоматизация необходима, поскольку VBScript — интерпретируемый язык. В третьем разделе речь пойдет о низкоуровневой инфраструктуре автоматизации с помощью программы-клиента на Visual C++, работающей с интерфейсом IDispatch непосредственно. Здесь же рассматривается пример программы, иллюстрирующий создание контроллера автоматизации на C++ с применением класса-оболочки ATL CComDispatchDriver. В четвертом разделе мы познакомимся с базовой моделью свойств, методов и событий Visual Basic и увидим, каким образом реализуются СОМ-классы, обеспечивающие свойства, методы и события. В заключение мы обсудим коллекции (включая вопросы реализации коллекций на сервере и доступа к ним клиента).
Автоматизация Приложения, как правило, обеспечивают подготовленных пользователей механизмом автоматизации часто выполняемых задач. Примером может служит макроязык, используемый для программирования некоторой функциональности в приложении. Но как автоматизировать задачу, включающую несколько приложений? Автоматизация обеспечивает стандартный способ, с помощью которого приложения могут предоставлять свою функциональность. Данные предоставляются через свойства, функции — через методы. Сервер называется "программируемым компонентом", или "объектом автоматизации", а клиент называется "контроллером автоматизации". Контроллер может быть запрограммирован на высокоуровневом языке, например на диалекте Visual Basic, и вызывать функции нескольких различных серверов. Свойства и методы Индивидуальные объекты автоматизации предоставляют свойства, которые позволяют работать с состоянием объекта с помощью функций установки, и методы, выполняющие некоторые действия. Свойства и методы имеют имена, известные за пределами объекта. Свойства имеют тип, а методы — сигнатуру, определяющую возвращаемый тип, количество и типы параметров. Свойства также могут иметь параметры. Позднее связывание Для обеспечения возможности программирования с помощью макросов объекты автоматизации должны поддерживать позднее связывание (late binding). Приложение общего назначения (например, текстовый редактор) не может быть скомпилировано, опираясь на знания определенных СОМ-объектов (что требуется для вызова пользовательских интерфейсов — так называемое раннее связывание). Точно так же не может быть скомпилирован и интерпретируемый язык типа VBScript. При позднем связывании контроллер автоматизации использует СОМ-интерфейс, который позволяет обратиться к свойствам и методам, предоставляемым объектом. IDispatch Фундаментальным интерфейсом СОМ, поддерживающим позднее связывание, является IDispatch. Этот интерфейс не имеет индивидуальных методов для свойств и методов объекта автоматизации. Вместо этого IDispatch обеспечивает доступ к свойствам и методам с помощью одного метода invoke. Этот метод работает со свойствами, методами и параметрами, определяемыми числовыми идентификаторами, называемыми диспетчерскими идентификаторами (Dispatch ID, или, сокращенно, dispid). Метод GetlDsOfNames преобразует имя свойства или метода, связывая передаваемые в качестве параметра имена в числовые значения. Два других метода предоставляют доступ к информации о типах. GetTypelnfoCount определяет, доступна информация о типах (1) или нет (0). Если информация доступна, ее можно получить, вызвав GetTypelnfo. Информация о типах Информация о типах является стандартом автоматизации для описания предоставляемых объектов, их свойств и методов. Кроме передачи информации о типах с помощью методов интерфейса IDispatch, COM предоставляет для этой цели специаль-
ные интерфейсы. iTypeinfo предоставляет информацию о членах объекта, описанную в библиотеке типа. Интерфейс iTypeLib предоставляет информацию об объектах в библиотеке типов. iCreateTypelnfo вносит информацию о типе в библиотеку типов, которая может быть создана интерфейсом iCreateTypeLib. Хотя информация о типах создается автоматизацией, в настоящее время рассматривается стандарт предоставления информации о типах (обычно посредством библиотеки типов) для других СОМ-объектов. Мы уже видели, как ATL помогает создать библиотеку типов при компиляции IDL кода; мы также рассматривали, как с помощью оператора New Visual Basic использует библиотеку типов и как с ней работает Visual C++, использующий интеллектуальные указатели. Двойные интерфейсы При использовании диспетчерского интерфейса и интерфейса виртуальных таблиц СОМ наблюдается определенный компромисс. Диспетчерский интерфейс может применяться разнообразными клиентами, в том числе интерпретируемыми окружениями, такими как VBScript. Интерфейс виртуальных таблиц более эффективен, так как разрешает непосредственный вызов объекта без привлечения механизма диспетчеризации. Двойной интерфейс происходит от IDispatch и является лучшим из двух вариантов. Свойства и методы реализуются как дополнительные к четырем методам IDispatch методы СОМ. Предоставляется также диспетчерский интерфейс, свойства и методы которого реализуются путем вызова соответствующих методов СОМ. Таким образом, класс, поддерживающий двойной интерфейс, может быть вызван с использованием как раннего, так и позднего связывания. Кроме небольшого увеличения размера кода, вызванного поддержкой обоих видов, имеется еще и ограничение на типы параметров. Параметры не могут быть произвольными типами данных C/C++, они являются строго ограниченными, разрешенными автоматизацией. VARIANT Ключевым моментом для понимания автоматизации является то, что автоматизация была создана для поддержки Visual Basic. Изначально Visual Basic был исключительно интерпретируемым языком и, следовательно, требовал позднего связывания. Кроме того, Visual Basic, по сути — нетипизированный язык. В нем совершенно корректной операцией является использование переменных без описания их типа (современная практика программирования на Visual Basic рекомендует использовать Option Explicit для того, чтобы описание типов переменных было необходимо). Переменная без определенного типа рассматривается как variant. IDispatch: : Invoke должен передавать в качестве аргументов и получать в результате выполнения методов и свойств автоматизации различные типы данных. IDispatch: : Invoke использует для этого структуру VARIANT, определенную так: typedef struct tagVARIANT VARIANT; typedef struct tagVARIANT VARIANTARG; Поле vartype определяет тип данных, хранимых в структуре. СОМ предоставляет ряд функций для гарантированного корректного использования типа данных variant, включающих VariantClear, VariantCopy, Variantlnit и VariantChangeType. ATL для работы с типом данных variant предлагает класс-оболочку CComVariant; Visual C++ имеет свой собственный класс variant t.
Структура tagVARIANT Вот каким образом в структуре поддерживаются различные типы данных: struct tagVARIANT { union { struct tagVARIANT { VARTYPE vt; WORD wReservedl; WORD wReserved2; WORD wReserved3; union { LONG lVal; /* VT_I4 */ BYTE bVal; /* VT_UI1 */ SHORT iVal; /* VT_I2 */ FLOAT fltVal; /* VT_R4 */ DOUBLE dblVal; /* VT_R8 */ VARIANT_BOOL boolVal; /* VT_BOOL */ _VARIANT_BOOL bool; /* (obsolete) */ SCODE scode; /* VT_ERROR */ CY cyVal; /* VT_CY */ DATE date; /* VT_DATE */ BSTR bstrVal; /* VT_BSTR */ IUnknown * punkVal; /* VT_UNKNOWN */ IDispatch * pdispVal; /* VT_DISPATCH */ SAFEARRAY * parray; /* VT_ARRAY */ BYTE * pbVal; /* VT_BYREF|VT_UI1 */ SHORT * piVal; /* VT_BYREF|VT_I2 */ LONG * plVal; /* VT_BYREF|VT_I4 */ FLOAT * pfltVal; /* VT_BYREF|VT_R4 */ DOUBLE * pdblVal; /* VT_BYREF|VT_R8 */ VARIANT_BOOL *pboolVal; /* VT_BYREF|VT_BOOL */ _VARIANT_BOOL *pbool; /* (obsolete) */ SCODE * pscode; /* VT_BYREF|VT_ERROR */ CY * pcyVal; /* VT_BYREF|VT_CY */ DATE * pdate; /* VT_BYREF|VT_DATE */ BSTR * pbstrVal; /* VT_BYREF|VT_BSTR */ IUnknown ** ppunkVal; /* VT_BYREF|VT_UNKNOWN */ IDispatch ** ppdispVal; /* VT_BYREF|VT_DISPATCH */ SAFEARRAY ** pparray; /* VT_BYREF|VT_ARRAY */ VARIANT * pvarVal; /* VT_BYREF|VT_VARIANT */ PVOID byref; /* Generic ByRef */ CHAR cVal; /* VT_I1 */ USHORT uiVal; /* VT_UI2 */ ULONG ulVal; /* VTJJI4 */ INT intVal; /* VT_INT */ UINT uintVal; /* VT_UINT */ DECIMAL * pdecVal; /* VT_BYREF|VT_DECIMAL */ CHAR * pcVal; /* VT_BYREF|VT_I1 */ USHORT * puiVal; /* VT_BYREF|VT_UI2 */ ULONG * puiVal; /* VT_BYREF|VT_UI4 */ INT * pintVal; /* VT_BYREF|VT_INT */ UINT * puintVal; /* VT_BYREF|VT_UINT */ struct tagBRECORD { PVOID pvRecord; IRecordlnfo * pRecInfo;
} VARIANT_NAME_4; /* VT_RECORD */ } VARIANT_NAME_3; } VARIANT_NAME_2; DECIMAL decVal; } VARIANT_NAME_1; }; Многие типы данных в структуре не требуют пояснений. Краткое имя CY представляет тип данных Visual Basic Currency. Автоматизация с использованием ATL и VBScript Мы можем проиллюстрировать многие идеи автоматизации и использование различных языков разработки с помощью небольшого СОМ-сервера, реализованного с помощью Visual C++ и ATL и развернутого на HTML-странице, запрограммированной с применением VBScript. COM-класс поддерживает двойной интерфейс, так как для VBScript необходимо позднее связывание. Сервер автоматизации (с использованием ATL) Наш пример сервера находится в каталоге Chapll\BankDual. Он создан как ЕХЕ- сервер с использованием ATL COM AppWizard и ATL Object Wizard. ProgID в нашем примере — Account.Answer.l, а пользовательское имя — Account Answer. На странице атрибутов тип интерфейса определен как двойной (Dual). Соберите проект, который зарегистрирует сервер. Просмотреть класс можно в OLE/COM Object Viewer. Создайте экземпляр, для того чтобы увидеть поддерживаемые интерфейсы, среди которых есть IDispatch (рис. 11 1). Рис. 11.1. Сервер автоматизации поддерживает интерфейс IDispatch
"Тонкий " клиент (с применением VBScript) Пример клиента находится в каталоге Chapll\BankHtml и представляет собой HTML-страницу bank.htm. Если в качестве броузера по умолчанию у вас установлен Internet Explore, вы можете просто дважды щелкнуть мышью на этом файле и открыть страницу. Заметьте, что вам не надо ничего компилировать, чтобы запустить клиент, который выглядит так, как показано на рис. 11.2. Рис. 11.2. Пример работы "тонкого" клиента с сервером автоматизации Щелкните на кнопке Create. Если у вас стоят настройки Internet Explorer по умолчанию, то при этом вы увидите предупреждение о потенциальной опасности использования управляющего элемента ActiveX, показанное на рис. 11.3. Щелкните на кнопке Yes для продолжения работы. Рис. 11.3. Internet Explorer предупреждает о потенциально опасных действиях При этом должен быть создан объект Account, а вы должны получить возможность проверить работоспособность методов Deposit и Withdraw. Для вывода текущего состояния баланса используется свойство Balance. По окончании работы щелкните на кнопке Destroy. Теперь рассмотрим исходный код HTML. Код VBScript размещен внутри комментария HTML, для того чтобы этот код был проигнорирован броузером, не поддерживающим VBScript. Объект account объявлен с помощью оператора Dim. Заметьте, что его тип не указан, так как VBScript — нетипизированный язык. Каждая переменная в VBScript имеет тип variant, включая ссылки на объект. Объект создается функцией CreateObject, которой передается ProglD. <!— bank.htm —> <HTML> <HEAD>
<TITLE>Bank test page for Account object</TITLE> <SCRIPT LANGUAGE="VBScript"> <! — dim account Sub btnCreatejDnClick set account = createobject("Account.Answer.1") Document.Forml.txtAmount.Value = 25 Document.Forml.txtBalance.Value = account.Balance End Sub Sub btnDestroyJDnClick set account = Nothing Document.Forml.txtAmount.Value = "" Document.Forml.txtBalance.Value = "" End Sub Sub btnDeposit_OnClick account.Deposit(Document.Forml.txtAmount.Value) Document.Forml.txtBalance.Value = account.Balance End Sub Sub btnWithdraw_OnClick account.Withdraw(Document.Forml.txtAmount.Value) Document.Forml.txtBalance.Value = account.Balance End Sub —> </SCRIPT> <FORM NAME = "Forml" > Amount <INPUT NAME="txtAmount" VALUE="" SIZE=8> <P> Balance <INPUT NAME="txtBalance" VALUE="" SIZE=8> <P> <INPUT NAME="btnCreate" TYPE=BUTTON VALUE="Create"> <INPUT NAME="btnDestroy" TYPE=BUTTON VALUE="Destroy"> <INPUT NAME="btnDeposit" TYPE=BUTTON VALUE="Deposit"> <INPUT NAME="btnWithdraw" TYPE=BUTTON VALUE="Withdraw"> </FORM> </BODY> </HTML> Автоматизация и VBScript Весьма полезно рассмотреть процессы, лежащие в основе приведенного кода VBScript. Здесь мы будем говорить только о том, что происходит в обработчиках кнопок Create и Destroy. Итак, создаем экземпляр объекта, получаем значение свойства Balance (используя имя Balance, а не непосредственный вызов с помощью виртуальной таблицы интерфейсов) и по окончании работы уничтожаем объект путем присвоения значения Nothing.
1. В системном реестре для получения CLSID найдите значение ProgID Account. Answer. 1, после чего с помощью фабрики классов (CoCreatelnstance) получите указатель на интерфейс объекта. 2. Для определения идентификатора диспетчера Balance используйте вызов GetlDsOfNames. 3. Вызовите метод invoke, которому в качестве параметра передается найденный идентификатор. 4. Если все прошло успешно, введите значение баланса; в противном случае VBScript сгенерирует ошибку на основе исключения, возвращаемого invoke. 5. По окончании работы посредством указателя на интерфейс вызовите метод Release. Еще немного об IDispatch IDispatch: : Invoke является функцией, вызывающей объект автоматизации. Свойства, как и методы, реализуются в виде вызовов функций. Обычно для каждого свойства имеется две функции (для получения и присвоения значения). Параметры Invoke включают: ■ идентификатор диспетчера свойства или метода; ■ указатель на массив аргументов свойства или метода; ■ указатель на возвращаемый результат; ■ указатель на структуру, хранящую информацию об исключениях (это — информация об исключениях автоматизации, не зависящая от обработки исключений C++ или NT). Контроллеры автоматизации на Visual C++ Рассматривая код контроллера автоматизации C++, мы сможем дополнительно узнать о низкоуровневых процессах автоматизации. Первый пример иллюстрирует прямой вызов интерфейса диспетчера; во втором примере используется класс-оболочка ATL CComDispatchDriver. Вполне вероятно, что вам никогда не придется вызывать интерфейсы диспетчера из C++ (большинство современных объектов поддерживает двойной интерфейс). Однако эта информация может пригодиться, например, при разработке среды сценариев. Вторая причина появления в книге этого раздела состоит в том, чтобы дать вам более глубокие знания о том, как работает механизм диспетчеризации. Прямой вызов IDispatch Использование интерфейса IDispatch на уровне простого СОМ не представляет сложностей (хотя этот процесс достаточно утомителен). Вы должны заполнить несколько структур данных и вызвать метод invoke. Код примера должен помочь вам разобраться, что и как делается, и показать, насколько громоздко обращение к IDispatch по сравнению с применением виртуальной таблицы. Полностью код можно найти в каталоге Chapll\UseDispCom; ниже приведены только его основные фрагменты.
void main () { HRESULT INVARIANT var; EXCEPINFO ei; UINT err; DISPPARAMS di = {NULL, NULL, 0, 0}; var.vt = VT_EMPTY; // Используем invoke для получения свойства Balance hr = pDisp->Invoke ( 1, // dispid IID_NULL, // зарезервировано LOCALE_SYSTEM_DEFAULT, // информация локализации DISPATCH_PROPERTYGET, // получение свойства &di, // параметры &var, // возвращаемое значение &ei, // информация об исключении &егг); // индекс ошибки if (FAILED(hr)) { cout « "IDispatch:Invoke failed" « endl; pUnk->Release() ; pDisp->Release() ; goto bottom; } long balance; balance = var.lVal; // VT_I4 cout « "Balance = " « balance « endl; } Использование драйвера CComDispatchDriver Для вызова интерфейса диспетчера ATL предоставляет класс-оболочку CComDispatchDriver. Полностью программа с применением этого класса находится в каталоге Chapll\UseDispAtl, а ниже приведены самые важные фрагменты этой программы. Этот пример иллюстрирует также применение класса-оболочки CComVariant. #include "stdafx.h" void exercise(CLSID clsid); char progid[80]; void main() {
cout « "ProgID: "; cin » progid; cout « "You entered " « progid « endl; // Преобразование progid в Unicode и получение CLSID CLSID clsid; MultiByteToWideChar(CP_ACP, 0, progid, -1, wbuf, 80); hr = CLSIDFromProgID(wbuf, &clsid); if (FAILED(hr)) { cout « "ProgID not found: " « progid « endl; goto bottom; } // Работа с объектом с полученным clsid exercise(clsid); bottom: CoUninitialize(); } void exercise(CLSID clsid) { // Для создания экземпляра объекта // используется фабрика классов // Применяется интеллектуальный указатель ATL CComPtr<IDispatch> pAccount; HRESULT hr = CoCreatelnstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **) &pAccount); if (FAILED(hr)) { cout « "CoCreatelnstance failed" « endl; return; } // Применение класса ATL CComDispatchDriver // для получения интерфейса диспетчера CComDispatchDriver pDisp(pAccount); CComVariant varResult; pDisp.GetProperty(1, SvarResult); long balance = varResult.lVal; cout << "Balance = " << balance « endl; Автоматизация и Visual Basic В этом разделе мы рассмотрим основы модели программирования на Visual Basic и увидим, каким образом эта модель отображается на элементах автоматизации. Каждая программа Visual Basic использует свойства, методы и события. Внутри форм вы определяете свойства форм и управляющих элементов, а также вызываете методы и создаете обработчики событий, например, таких как щелчок кнопки.
Свойства Мы уже знакомы с методами как с функциями, связанными с интерфейсами СОМ. Свойства могут рассматриваться как определенный тип методов, который используется для получения и установки значений. Свойства могут иметь параметры; со свойствами очень просто работать в Visual Basic. Для иллюстрации различного синтаксиса рассмотрим пример баланса на сервере банковского счета. В первом случае мы используем методы GetBalance и PutBalance. В примере предполагается, что текстовое поле txtBalance используется не только для вывода значения баланса, но и для передачи данных на сервер. 1 Получение баланса с сервера Dim balance as Currency objAccount.GetBalance balance txtBalance = balanse 1 Передача баланса на сервер objAccount.PutBalance txtBalance Альтернативой служит использование Balance как свойства: ' Получение баланса с сервера txtBalance = objAccount.Balanse 1 Передача баланса на сервер objAccount.Balance = txtBalance Свойства по умолчанию Этот пример иллюстрирует возможности свойств Visual Basic и автоматизации. Свойство, идентификатор диспе