Text
                    ТЕХНОЛОГИЯ
ОСНОВЫ И
ПРОГРАММИРОВАНИЕ
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 и автоматизации. Свойство, идентификатор диспетчера которого равен нулю, считается свойством по умолчанию. Вы можете ссылаться на свойства по умолчанию, используя ссылки на объект. В приведенном выше примере встроенный текстовый управляющий элемент имеет свойство по умолчанию Text, а потому следующие две строки кода эквивалентны: txtBalance.Text = balance 'Полное указание свойства txtBalance = balance 'Text - свойство по умолчанию Свойства по умолчанию должны использоваться с осторожностью. Они и делают код более кратким, но — и менее понятным. Ни в одном из наших серверов ни одно свойство не является свойством по умолчанию. Реализация свойств на Visual Basic Visual Basic предоставляет специальную версию процедуры Property, которую можно использовать в модуле класса для реализации свойства: Dim gBalance as Currency Public Property Get Balance() As Currency Balance = gBalance End Property Public Property Let Balance(ByVal vNewValue As Currency) gBalance = vNewValue End Property
Свойства "только для чтения" и "только для записи" Вы можете сделать свойство доступным только для чтения, удалив Property Let. Удалив Property Get, вы получите свойство, доступное только для записи (что встречается нечасто). IDL для интерфейса диспетчера Вы можете использовать Type Library Viewer из OLE/COM Object Viewer для просмотра кода IDL сервера автоматизации. Вы увидите, что сервер автоматизации имеет интерфейс диспетчера (dispinterface). На рис. 11.4 показан IDL-код для нашего примера сервера. Обратите внимание: свойство Balance — "только для чтения" (оно имеет атрибут propget, но не имеет атрибута propput). Рис. 11.4. IDL интерфейса диспетчера События Очень важной особенностью Visual Basic является возможность работать с событиями. Фундаментальное свойство графического интерфейса пользователя состоит в том, что во главу ставится пользователь, а не программа. В традиционном пользовательском интерфейсе программа находится все время под контролем. Она предоставляет пользователю определенный набор опций в каждый момент времени, и пользователь может осуществлять свой выбор с помощью команд меню, вводя определенный текст или нажимая на определенные клавиши, после чего программа отрабатывает запрос пользователя и вновь возвращается в состояние ожидания ввода. Программы такого типа, по сути, структурно последовательны. Программы с графическим интерфейсом пользователя существенно отличаются друг от друга. Пользователь может осуществить ввод команд различными способами: с помощью меню, щелчков мышью, клавиатуры и т.п. В результате программа должна постоянно опрашивать различные устройства и всегда быть готова отреагировать на ввод пользователя. Операционная система Windows для работы графического интерфейса пользователя предоставляет систему сообщений. Различные действия пользователя генерируют сообщения Windows, которые помещаются в очередь сообщений. Программа Windows должна иметь оконную процедуру, которая получала бы сообщения из очереди и выполняла бы их обработку. Программисты на С пишут процедуры Windows вручную; а программисты на C++ для реализации обработчика сообщений обычно используют библиотеку классов, например, такую как MFC.
Visual Basic предоставляет абстракцию события. Действия пользователя, такие как щелчок мышью, выбор элемента из списка и другие, запускают событие. Программа на Visual Basic предоставляет процедуры обработки таких событий. Так, например, следующий код вызывает окно сообщения по щелчку кнопкой мыши (полностью с проектом можно ознакомиться в каталоге chapll\Button). Private Sub cmdButton_Click() MsgBox "Button was clicked!" End Sub События в COM-серверах Обычно клиент СОМ вызывает СОМ-сервер, используя так называемый "входной" интерфейс. Однако иногда информация о некотором происшествии на сервере должна быть передана клиенту. Можно говорить о том, что сервер порождает событие. Сервер определяет специальный тип "выходного" интерфейса, который должен быть реализован клиентом. Visual Basic упрощает предоставление событий сервером. Вы объявляете одну или несколько процедур событий и при необходимости порождаете события в коде. Чтобы все это вам стало понятнее, обратитесь к программе, содержащейся в каталоге Chapll\BankEvent. Когда вы пытаетесь снять со счета больше, чем на нем находится, порождается событие Overdrawn. Обратите внимание: событие может иметь параметр. Option Explicit Private gBalance As Currency Public Event Overdrawn(ByVal Balance As Currency) Public Sub Deposit(ByVal amount As Long) gBalance = gBalance + amount End Sub Public Sub Withdraw(ByVal amount As Long) gBalance = gBalance - amount If gBalance < 0 Then RaiseEvent Overdrawn(gBalance) End If End Sub Private Sub Class_Initialize() gBalance = 100 MsgBox "Account object created" End Sub Private Sub Class_Terminate() MsgBox "Account object destroyed" End Sub Public Property Get Balance() As Currency Balance = gBalance End Property Программа-клиент, обрабатывающая события Для реализации клиентской программы на Visual Basic, способной обрабатывать события, вы должны объявить ссылку на ваш объект как withEvents. В этом случае вы не сможете включить New в качестве части оператора Dim. Если в вашей программе имеется
явное объявление Dim WithEvents, в окне кода будут отображены все события для данной ссылки на объект, и вы можете добавлять обработчики событий, как показано на рис. 11.5. Рис. 11.5. События в окне кода В каталоге Chapll\ClientBankEvent имеется код программы Visual Basic, обрабатывающей описанное событие и выводящей при попытке снять со счета сумму, большую, чем находящаяся там, соответствующее окно сообщения. Option Explicit Dim WithEvents objAccount As Account Attribute objAccount.VB_VarHelpID = -1 Private Sub cmdDeposit_Click() objAccount.Deposit txtAmount txtBalance = objAccount.Balance End Sub Private Sub cmdWithdraw_Click() objAccount.Withdraw txtAmount txtBalance = objAccount.Balance End Sub Private Sub Form_Load() Set objAccount = New Account txtAmount = 25 txtBalance = objAccount.Balance End Sub Private Sub objAccount_Overdrawn(ByVal Balance As Currency) MsgBox "Account is overdrawn!" & vbNewLine _ & "Balance = " & Balance End Sub IDL для интерфейса события Мы можем использовать Type Library Viewer из OLE/COM Object Viewer для изучения IDL COM-сервера BankEvent. Вот его полный текст: // Generated .IDL file (by the OLE/COM Object Viewer) // // typelib filename: BankEvent.dll [ uuid(EA8166A5-4369-llD3-9041-00105AA4 5BDC), version(2.0) ] library BankEvent {
// TLib : // TLib : OLE Automation : {00020430-0000-0000-C000-00000000004 6} importlib("STDOLE2.TLB"); // Forward declare all types defined in this typelib interface _Account; dispinterface Account; [ odl, uuid(80670697-442B-11D3-9042-00105AA45BDC), version(1.0) , hidden, dual, nonextensible, oleautomation ] interface _Account : IDispatch { [id(0x60030001)] HRESULT Deposit([in] long amount); [id(0x60030002)] HRESULT Withdraw([in] long amount); [id(0x68030000), propget} HRESULT Balance([out, retval] CURRENCY* ); }; [ uuid(EA8166A7-4369-llD3-9041-00105AA45BDC), version(1.0) ] coclass Account { [default] interface _Account; [default, source] dispinterface Account; }; [ uuid(80670698-442B-HD3-9042-00105AA45BDC) , version (1.0), hidden, nonextensible ] dispinterface Account { properties: methods: [id(0x00000001)] void Overdrawn([in] CURRENCY Balance); }; }; Ели вы взглянете на IDL-код для coclass, то увидите, что там есть два интерфейса. Первый интерфейс, _Account, представляет собой двойной интерфейс и, таким образом, может быть вызван как СОМ-клиентом с ранним связыванием, так и клиентом с поздним связыванием типа сценария VBScript. Второй интерфейс, Account, имеет атрибут source и является специфичным "диспинтерфейс" (диспетчерским интерфейсом).
Коллекции Важным свойством СОМ являются коллекции (collections), позволяющие вам работать со списками объектов. Подобно другим возможностям автоматизации, коллекции отображаются на соответствующих возможностях Visual Basic. В этом разделе рассказывается о концепции коллекций, рассматриваются итераторы, лежащие в основе реализации коллекций, представлены примеры использования коллекций с применением ATL, а также пример клиента на Visual Basic, использующего коллекции. Коллекции и объектная модель С помощью объекта коллекции вы можете обеспечить доступ к нескольким экземплярам объекта. Объект коллекции управляет другими объектами, в частности, поддерживая итерацию по всем управляемым объектам. Интерфейс lEnumVARlANT предоставляет способ такой итерации. Подобно другим итераторам, этот интерфейс поддерживает методы Next, Skip, Reset и Clone. ATL упрощает реализацию итераторов. Сложные серверы автоматизации предоставляют свою функциональность с помощью модели объектов. Модель определяет иерархию объектов, некоторые из которых могут быть коллекциями. В современном программном обеспечении Microsoft, с примерами такой модели объектов мы встречаемся везде — в офисных приложениях (Word и Excel), в приложениях для работы в Internet (Internet Explorer, Active Server Pages), в приложениях доступа к базам данных (Data Access Objects, ActiveX Data Objects) и во многих других. Итераторы Многие СОМ-приложения имеют коллекции указателей и других данных. Примеры коллекций включают упомянутую выше модель объектов, потоки в памяти и многое другое. Итераторы предоставляют стандартную схему для предоставления приложениями своих коллекций. Поскольку точный формат итератора зависит от того, что именно служит объектом итерации, СОМ предусматривает описанную ниже схему. IEnumXXX Эта схема определятся интерфейсом IEnumXXX. Наличие отдельного объекта итератора отделяет возможности пользователя работать с объектами от способа их хранения сервером. Все итераторы имеют следующие методы: ■ Next — извлекает следующий в коллекции элемент; ■ Reset — возвращает последовательность итерации к началу. Эта операция может не сохранить элементы в том порядке, в котором они находились до ее вызова; ■ Skip — пропускает определенное число элементов; ■ Clone — возвращает копию текущего итератора. Каждый итератор определяет последовательность элементов, возвращаемых методами итератора. Итерируемая коллекция данных может быть статической или динамической, а сами данные, например, включать указатели на интерфейсы. Итератор обычно может быть получен посредством вызова метода другого объекта.
Классы итераторов ATL ATL имеет несколько классов, которые помогают построить итераторы. CComEnum определяет итератор, CComiEnum — абстрактный класс, который, в свою очередь, определяет интерфейс итератора. CComEnumimpl реализует интерфейс итератора и используется классом CComEnum. Применение всех этих классов проиллюстрировано в приведенном ниже примере ZooColl. Реализация коллекций Коллекции делают возможным использованием итераторов контроллерами автоматизации. Объект коллекции предоставляет лежащий в его основе объект-итератор через интерфейс IDispatch. Если это имеет смысл, объект коллекции может вставить и удалить элемент из любой позиции. Как и в случае интерфейса lEnumXXX, в данном случае имеется шаблон, которому следует определенный объект коллекции. Каждый элемент в коллекции хранится как тип данных variant, а, следовательно, коллекция может использоваться с разнообразными типами данных. Для прохождения цикла по содержащимся в ней объектам коллекция использует итератор lEnumVARiANT. Кроме того, коллекция предоставляет средства доступа к определенному ее элементу. Требования к коллекциям Для того чтобы коллекции удовлетворяли стандартной схеме, к ним предъявляется несколько требований. ■ Свойство или метод, возвращающий коллекцию, называется именем элемента коллекции во множественном числе (так, коллекция объектов Animal имеет имя Animals). ■ Коллекция должна иметь скрытое свойство _NewEnum для получения итератора IEnumVARIANT. ■ Коллекция должна поддерживать индексирование с помощью метода item. ■ Коллекция должна иметь свойство Count. ■ Если имеет смысл добавление объектов в коллекцию, реализуется метод Add. ■ Если имеет смысл удаление объектов из коллекции, реализуется метод Remove. Пример коллекции Полный пример использования коллекций представлен в каталоге Chapll\ZooColl. Сервер реализован с применением ATL и находится в каталоге Server. Клиент Visual Basic можно найти в каталоге VbClient, а клиент Visual C++ — в каталоге VcClient. Постройте сервер и запустите клиент Visual Basic. Начальная коллекция состоит их четырех животных. Вы можете добавлять и удалять животных, а также изменять их имена. Заметьте, что индексы в коллекции начинаются с 1. На рис. 11.6 показан вид коллекции после добавления Elephant и изменения третьего элемента с Bears на Grizzly Bears. Для того чтобы понять, как программа работает с коллекциями, обратитесь к исходному коду Visual Basic. В частности, цикл For Each представляет собой элегантную языковую конструкцию для итерации коллекции.
For Each Animal in Animals AnimalsList.Addltem Animal Next Animal Рис. 11.6. Программа тестирования коллекции Цикл For...Next иллюстрирует свойство Count и применение индексов. Dim I As Long For I = 1 To Animals.Count AnimalsList.Addltem Animals(i) Next i Резюме Автоматизация представляет собой важную технологию, обеспечивающую возможность клиенту ("Тонкому" клиенту, с помощью VBScript на HTML-странице) подключаться к СОМ-серверу с помощью позднего связывания. Автоматизация поддерживает работу с нетипизированными языками с помощью "всеобъемлющего" типа variant. Позднее связывание основано на методе общего назначения Invoke интерфейса IDispatch. Этот интерфейс делает сервер доступным для языков сценариев, но он менее эффективен, чем интерфейс виртуальных таблиц СОМ. Позволяя работать с обоими вариантами связывания, двойной интерфейс предоставляет пользователю лучший из (двух) методов. Цена такого улучшения — небольшое увеличение размера кода и ограничение используемых типов данных теми, которые поддерживаются автоматизацией. Создание двойного интерфейса упрощается благодаря применению ATL. Архитектура автоматизации несет на себе отпечаток требования быть как можно ближе к модели Visual Basic, включая поддержку свойств, методов и событий. Коллекции обеспечивают стандартный путь работы со списками объектов в СОМ. Мы все еще не рассмотрели такую важную особенность автоматизации, как механизм исключительных ситуаций. Это будет темой нашей следующей главы 12, "Обработка ошибок и отладка". Обработка ошибок играет важную роль в многоуровневых приложениях, так как в них слишком много потенциальных источников ошибок.
Глава 12 Обработка ошибок и отладка До сих пор мы практически не уделяли внимания обработке ошибок. Мы проверяли возвращаемое функциями значение HRESULT, но не более того. По сути, мы просто игнорировали возникающие ошибки. Это происходило не потому, что они были не важны, а для того, чтобы иметь возможность сосредоточиться на концепциях СОМ и не отвлекаться на другие вопросы. Однако, чем ближе мы подбираемся к части этой книги, в которой освещаются вопросы непосредственно касающиеся СОМ+, тем более важной становится проблема обработка ошибок. Здесь мы обсудим и вопросы отладки, которую можно рассматривать как поиск и удаление ошибок в логике программы перед ее распространением. Обработка ошибок представляет собой дополнительный код, который работает с ошибками времени выполнения, которые могут возникнуть уже при работе распространенного вами приложения. Очень важно, чтобы программа работала с возникающими ошибками предельно корректно и изящно, а не просто "затухала ", даже не потрудившись сообщить, что и где произошло. Это особенно важно в случае компонентов многоуровневых приложений уровня предприятия, где маленькая ошибка может стать причиной больших неприятностей. Более детально вопросы обработки ошибок будут освещены в части III, "Windows DNA и СОМ+ ". Мы начнем с рассмотрения hresult, включая стандартные средства и коды ошибок, а также технологию для вывода этой информации. Затем мы изучим стандартные интерфейсы для ошибок СОМ, которые могут использоваться для получения дополнительной информации об ошибках, чем та, которая может быть возвращена одним 32-битовым числом. Затем рассмотрим поддержку этого механизма СОМ-библиотекой ATL. После этого поговорим об исключениях автоматизации и о том, каким образом этот механизм может отображаться на соответствующие механизмы языка реализации. В частности, мы изучим обработку исключений в C++ и Visual Basic. И в заключение мы рассмотрим вопросы отладки, включая трассировку и остановы в процессе выполнения программы. Использование HRESULT Большинство API-функций и методов СОМ возвращает hresult, представляющий собой 32-битовую величину, имеющую следующий формат (31-й бит — наибольший из значащих):
Бит 31 30-27 26-16 15—0 Значение - — Бит наличия ошибки (0 — ошибки нет, 1 — произошла ошибка) Зарезервированы Область, ответственная за ошибку Код, идентифицирующий ошибку Успех или неудача определяется проверкой бита наличия ошибки, для чего вы можете использовать макрос succeeded или failed. HRESULT hr = pInterface->SomeMethod(); { // обработка ошибки Коды областей Microsoft резервирует коды областей, поскольку они должны быть уникальны. В настоящее время определены следующие коды. Значение о 1 3 Обозначение FACILITYJTOLL FACILITY_RPC FACILITY_DISPATCH FACILITY_STORAGE FACILITY_ITF FACILITY_WINDOWS FACILITY_WIN32 Описание Общие коды состояния (типа s_ok) Ошибки RPC Ошибки интерфейса IDispatch Ошибки IStorage и IStream Большинство кодов состояния, возвращенных методами интерфейсов (различные интерфейсы имеют разные адресные пространства hresult) Дополнительные коды ошибок интерфейсов, определенных Microsoft Для обработки кодов ошибок функций Win32, представленных как hresult Коды ошибок и соглашения по именованию Для определения кодов ошибок вашего интерфейса используйте facility_itf. Эти коды ошибок (биты 15-0) уникальны для конкретного интерфейса, так что вам нет нужды беспокоиться о конфликтах с другими кодами ошибок, hresult имеет следующие соглашения об именовании: <Fac ility>_< Severi ty>_<Reason> ■ <Facility> представляет собой имя области или другой различающим идентификатор (исключается для facility_null).
■ <Severity> — буква s или Е, означающая успех или ошибку. ■ <Reason> — идентификатор, поясняющий код. Вот несколько примеров: S_OK (FACILITY_NULL) STG_E_FILENOTFOUND (FACILITY_STORAGE) DISP E EXCEPTION (FACILITY DISPATCH) Просмотр кодов ошибок Получив ошибку в процессе выполнения, вы, естественно, захотите узнать ее описание. В большинстве случаев для этого вы можете использовать утилиту Error Lookup (из меню Visual C++ Tools), показанную на рис. 12.1. Рис. 12.1. Использование Error Lookup для значения HRESULT В некоторых случаях ошибка не может быть найдена с помощью Error Lookup. Тогда вы можете попытаться воспользоваться командой Find In Files меню Edit для поиска числового значения (в шестнадцатеричной записи) в различных включаемых файлах, как показано на рис. 12.2. Рис. 12.2. Применение Find In Files для поиска hresult Вывод описания ошибки Другой стратегией получения информации об ошибке может стать вызов функции Win32 API FormatMessage, которая автоматически обеспечивает получение строки с описанием ошибки. Подобно множеству других функций Win32 API, FormatMessage весьма не проста и имеет множество параметров, а потому для ее применения мы разработали более удобную в использовании вспомогательную функцию ShowError. Программа, содержащаяся в каталоге Chapl2\UseCmd, запрашивает у пользователя ProgID и пытается найти и вывести соответствующий CLSID. В случае ошибки (возникающей при вводе некорректного значения ProgID) в первом hresult вызывается функция ShowError, результат работы которой показан на рис. 12.3. Эта вспомогательная функция выводит значение hresult в шестнадцатеричном виде и описывающее сообщение об ошибке (заметьте, что консольная программа может выводить окно сообщения).
Рис. 12.3. Вспомогательная функция выводит сообщение об ошибке и значение HRESULT // use.cpp finclude "stdafx.h" void ShowError(HRESULT hr) { if (hr == SJDK) return; if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS) hr = HRESULT_CODE(hr); LPVOID lpMsgBuf; FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL ) ; char buf[1024]; wsprintf(buf, "%s\nhr = 0x%lx", (LPCTSTR)lpMsgBuf, hr); MessageBox( NULL, buf, "Error", MB_OK); LocalFree( lpMsgBuf ); } void main() { char progid[80]; WCHAR wbuf[80]; char buf[80]; HRESULT hr; cout « "ProgID: "; cin >> progid; cout « "You entered " « progid « endl; CLSID clsid; MultiByteToWideChar(CP_ACP, 0, progid, -1, wbuf, 80); hr = CLSIDFromProgID(wbuf, Sclsid); if (FAILED(hr)) { cout « "ProgID not found: " « progid << endl; ShowError(hr); return;; }
Интерфейсы ошибок СОМ Имеется ряд интерфейсов СОМ, которые могут быть использованы для получения расширенной информации об ошибке. iSupportErrorlnfo используется для получения информации об ошибке некоторого интерфейса объекта, если таковая имеется. iCreateErrorlnfo используется для создания объекта ошибки, содержащего расширенную информацию об ошибке. iErrorlnfo представляет собой интерфейс, чьи методы представляют расширенную информацию об ошибке. Объект ошибки ассоциирован с конкретным потоком выполнения, и, таким образом, в многопоточном приложении может содержаться несколько объектов ошибок. IErrorlnfo Объект ошибки поддерживает интерфейс IErrorlnfo, который имеет следующие методы: ■ GetDescription, возвращающий текстовое описание ошибки в виде BSTR; ■ GetGUiD, возвращающий GUID интерфейса, определившего ошибку; ■ GetHelpFile, возвращающий путь к файлу справки, содержащему информацию об ошибке; ■ GetHelpContext, возвращающий идентификатор контекста справки, определяющий запись в справочном файле; ■ GetSource, возвращающий ProgID объекта, вызвавшего ошибку. Получение информации об ошибке Для реализации получения расширенной информации об ошибке выполните следующие действия. 1. Реализуйте интерфейс ISupportErrorlnfo. 2. Вызовите API — функцию CreateErrorlnfo для создания объекта ошибки и получения указателя на интерфейс ICreateErrorlnfo. 3. Воспользуйтесь методами ICreateErrorlnfo для установки контекста объекта ошибки. 4. Вызовите API — функцию SetErrorlnfo для связи объекта ошибки с текущим потоком. Получение информации об ошибке Получение информации об ошибке включает следующие шаги. 1. Проверка hresult на наличие ошибки. 2. Вызов Querylnterface для получения указателя на ISupportErrorlnfo. 3. ВЫЗОВ ISupportErrorlnfo::InterfaceSupportErrorlnfо ДЛЯ выяснения того, поддерживается ли интересующим нас интерфейсом информация об ошибке.
4. Вызов GetErrorinfo для получения объекта ошибки для этого потока (вы получите указатель на iErrorlnfо). 5. Использование метода iErrorinfo для получения детальной информации об ошибке. Реализация ISupportErrorlnfo с использованием ATL Интерфейс ISupportErrorlnfo в ATL реализуется точно так же, как и любой другой интерфейс. Вначале вы добавляете этот интерфейс к списку наследования вашего класса реализации. class ATL_NO_VTABLE CAccount : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAccount, &CLSID_AccountError>/ public ISupportErrorlnfo, public IDispatchlmpKIAccount, &IID_IAccount, &LIBID_BANKERRORLib> { Затем вы обеспечиваете код для реализации методов интерфейса. Имеется только один метод — InterfaceSupportsErrorlnfо. Вы проверяете, соответствует ли переданный идентификатор какому-либо идентификатору интерфейса (хранящимся в массиве), для которого вы поддерживаете информацию об ошибках. В приведенном ниже коде вы поддерживаете информацию об ошибках для HD_lAccount. Теперь расширяем приведенный ниже код — если вы поддерживаете информацию об ошибках для других интерфейсов, просто добавьте их идентификаторы в инициализирующий список массива. STDMETHODIMP CAccount::InterfaceSupportsErrorlnfо(REFIID riid) { static const IID* arr[] = { &IID_IAccount, >; for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++) { if (InlineIsEqualGUID(*arr[i],riid)) return S_OK; } return S_FALSE; } Возвращение информации об ошибке с помощью ATL ATL существенно облегчает использование СОМ-сервером интерфейсов ошибок для возвращения расширенной информации об ошибках. В качестве примера рассмотрите программу, содержащуюся в каталоге Chapl2\BankError. Использование ATL Object Wizard Показанный код поддержки ошибок может быть размещен в вашем исходном файле автоматически — с помощью ATL Object Wizard. Все, что вы должны для этого сделать — отметить опцию Support ISupportErrorlnfo во вкладке Attributes диалогового окна ATL Object Wizard Properties, как показано на рис. 12.4.
Рис. 12.4. ATL Object Wizard может автоматически добавить код поддержки ошибок Установка объекта ошибки После добавления поддержки iSupportErrorinfo в ваш класс, вы можете использовать метод Error в CComCoClass для выполнения всех описанных выше шагов. В реализации вашего метода при возвращении hresult вы возвращаете вызов Error (buf), где buf — строковый буфер, хранящий описание ошибки. Пример интерфейсов ошибок СОМ Программа, содержащаяся в каталоге Chapl2\BankError, иллюстрирует детали использования интерфейсов ошибок СОМ. Этой же программой мы воспользуемся и в следующем разделе — при рассмотрении исключений автоматизации. Сервер реализует уже хорошо знакомый нам банковский счет с интерфейсом iAccount, который представляет собой двойной интерфейс. Выполняются следующие проверки ошибок: ■ в Deposit параметр должен быть положительным; ■ в withdraw параметр должен быть положительным и не превышать текущий баланс. Запуск программы Соберите проект сервера "BankError" и клиента "test" и запустите тестовую программу. Программа реализована таким образом, что может работать как с помощью пользовательского интерфейса (виртуальная таблица), так и при участии интерфейса IDispatch. Для начала попробуем поработать с пользовательским интерфейсом (рис. 12.5). Убедитесь, что и Deposit, и withdraw корректно работают с допустимыми величинами. Теперь несколько раз снимем со счета суммы, так, чтобы баланс стал отрицательным. Наша программа не допустит этого, выведя два окна сообщений. Первое, показанное на рис. 12.6, использует вспомогательную функцию showError для вывода значения hresult и системного описания ошибки. Обратите внимание на то, что системное описание ошибки предельно неконкретно: все, что оно может сказать нам — так это то, что произошла исключительная ситуация. Но "исключительная ситуация СОМ" означает, что может иметься и дополнительная информация об ошибке — которая и выводится вторым окном сообщения, приведенным на рис. 12.7.
Рис. 12.5. Тестовая программа Рис. 12.6. Системное Рис. 12.7. Расширенная может работать с пользова- описание ситуации не- информация об ошибке в телъским или диспетчерским ин- достаточно конкретно методе Withdraw терфейсом Теперь попробуем снять со счета отрицательную сумму. Вы должны получить новое окно сообщения об ошибке с другим сообщением, корректно описывающим произошедшую ситуацию. (Но если вы попытаетесь положить отрицательную сумму на счет, то в этом случае сбой выполнения произойдет "молча" — код проверки ошибки в Deposit в клиенте отсутствует.) Код сервера, обеспечивающий информацию об ошибке Мы уже видели код, который предоставляет ATL Object Wizard (при использовании опции "Supports ISupportErrorlnfo") при вставке нового ATL-объекта в ваш проект. Реализация обеспечивается методом interfaceSupportsErrorlnfo, который отвечает "да" на вопрос: "Поддерживает ли ваш интерфейс информацию об ошибке?". Однако код для обеспечения расширенной информации об ошибке вы должны создать самостоятельно. Рассмотрите файл account.cpp, содержащийся в проекте BankError, и взгляните, каким образом в нем реализованы методы Deposit и Withdraw, в которых может возникнуть ошибка. STDMETHODIMP CAccount::Deposit(long amount) { if (amount > 0) { m_nBalance += amount; return S_OK; } else { TCHAR buf[80]; wsprintf(buf, "amount %ld is not positive", amount); return Error(buf); } } STDMETHODIMP CAccount::Withdraw(long amount) { if (amount > 0 && amount <= m_nBalance) { m_nBalance -= amount; return S_OK; } else if (amount > m_nBalance) { TCHAR buf[80];
wsprintf(buf, "amount %ld exceeds balance %ld", amount, m_nBalance); return Error(buf); } else { TCHAR buf[80]; wsprintf(buf, "amount %ld is not positive", amount); return Error(buf); } } Вызов метода Error класса ccomCoClass выполняет низкоуровневый вызов COM, создающий объект ошибки СОМ и определяющий сообщение с описанием ошибки. Возвращаемое методом Error значение hresult — 0x80020009 (произошла исключительная ситуация), что и было отображено в первом окне сообщения. Код клиента, получающий информацию об ошибке Вы можете получить расширенную информацию об ошибке, если она доступна, с помощью соответствующих интерфейсов СОМ. Воспользуйтесь iSupportErrorlnfo, чтобы выяснить, доступна ли информация об ошибках. Вызовите GetErrorlnfo для получения указателя на iErrorlnfo и воспользуйтесь его методами для получения детальной информации об ошибке. Обратите внимание на то, что семантика функции API GetErrorlnfo и метода InterfaceSupportErrorlnfo требует возвращения s_OK для успешного завершения. Соответствующий код вы можете найти в обработчике OnWithdraw в тестовой программе testDlg.cpp. Использование ISupportErrorlnfo ISupportErrorlnfo* pSupport; // Запрос ISupportErrorlnfo hr = m_pAccount->QueryInterface(IID_ISupportErrorInfo, (void**) &pSupport); if (FAILED(hr)) { MessageBox("ISupportErrorlnfo interface not available"); return; } TRACE("ISupportErrorlnfo interface is available\n"); // Получение информации об ошибке hr = pSupport->InterfaceSupportsErrorInfo(IID_IAccount); if (hr != S_OK) { MessageBox("Error info not availble for IAccount"); pSupport->Release() ; return; } Использование IErrorlnfo // Получение объекта ошибки и описания IErrorlnfo* pError; hr = GetErrorlnfo(0, &pError);
if (hr != S_OK) { MessageBox("GetErrorInfo failed"); pSupport->Release() ; return; } TRACE("GetErrorInfo succeeded\n"); BSTR bstr; hr = pError->GetDescription(&bstr); if (FAILED(hr)) { MessageBox("GetDescription failed"); pSupport->Release(); pError->Release() ; return; } TRACE("GetDescription succeeded\n"); // Вывод описания USES_CONVERSION; MessageBox(0LE2CT(bstr), "Our Error Message"); SysFreeString(bstr); pSupport->Release() ; pError->Release() ; Поддержка ошибок интеллектуальных указателей Visual C++ Класс поддержки СОМ в Visual C++ _com_error представляет собой оболочку интерфейсов ошибок СОМ, упрощающую написание клиентских программ, получающих расширенную информацию об ошибках. Если при работе с интеллектуальными указателями Visual C++ возвращаемое значение hresult указывает на происшедшую ошибку, то это приведет к генерации исключения типа _com_error. В главе 8, "Поддержка СОМ в Visual C++" мы рассматривали пример использования этого класса для вывода описания ошибки с помощью метода ErrorMessage. Вот фрагмент кода из Chap8\SmartClient, демонстрирующий такое использование _com_error. void CSmartClientDlg::OnWithdraw() { int balance; int amount = GetDlgltemlnt(IDC_AMOUNT); try { m_jpAccount->Withdraw(amount); m_pAccount->GetBalance(&balance); SetDlgltemlnt(IDC_BALANCE, balance); } catch(_com_error &ex) { MessageBox(ex.ErrorMessage()); } } Метод ErrorMessage просто вызывает метод Win32 API FormatMessage. Таким образом, все, что мы сможем получить в результате— это системное описание происшедшего, т.е. при любом исключении СОМ описание будет одинаково — "Exception
occurred", в котором трудно найти информацию об ошибке, предоставляемую сервером. Но, к счастью, возможности _com_error не ограничиваются одним методом. Функция-член Errorlnfo возвращает указатель на интерфейс iErrorlnfo, и мы можем воспользоваться им. Имеется и еще более удобный метод Description, который вызывает метод IErrorlnfo: :GetDescription вместо нас. Описание возвращается нам в виде строки _bstr_ (класс-оболочка Visual C++ для типа данных bstr). Если объекта ошибки с расширенной информацией нет, будет возвращена пустая строка; в этом случае вы можете обратиться за пояснениями к ErrorMessage. Увидеть, как применяются описанные методов, сможете в проекте " SmartClient" в каталоге Chapl2\BankError. Эта тестовая программа использует только пользовательский интерфейс и интеллектуальные указатели Visual C++. Вот код метода Withdraw: void CSmartClientDlg::OnWithdraw() { long balance; int amount = GetDlgltemlnt(IDC_AMOUNT); try { m_spAccount->Withdraw(amount); m_spAccount->get_Balance(&balance); SetDlgltemlnt(IDC_BALANCE, balance); } catch(_com_error &ex) { _bstr_t bstr = ex.Description(); if (bstr.length() == 0) // расширенная информация отсутствует MessageBox(ex.ErrorMessage()); else MessageBox((const char*) bstr); } } Заметьте, что для метода Deposit мы так и не написали код обработки ошибки. В результате теперь вместо молчаливого сбоя мы получаем системное сообщение о фатальной ошибке — так как при использовании интеллектуальных указателей генерируется не перехватываемая исключительная ситуация. Исключения автоматизации Одной из особенностей автоматизации является наличие механизма исключений. Седьмой параметр в invoke представляет собой указатель на структуру excepinfo. Метод может сообщить о произошедшей ошибке двумя путями — либо возвращая значениям hresult, либо генерируя исключение. Для генерации исключения метод устанавливает возвращаемое значение равным disp_e_exception и заполняет структуру excepinfo детальной информацией о произошедшей ошибке. Затем программа-клиент работает с этой структурой тем способом, который в данном языке программирования наиболее применим для обработки исключительных ситуаций. Программа на C++ может сгенерировать собственное исключение. MFC предоставляет обработчик исключительных ситуаций, выводящий окно сообщения, так что не перехваченное исключение автоматизации не приводит к аварийному завершению программы. В следующем разделе мы обсудим, каким образом Visual Basic работает с исключениями автоматизации.
EXCEPINFO Структура excepinfo предоставляет детальную информацию об исключительной ситуации. Некоторые ее поля напомнят вам функции-члены iErrorinfo. typedef struct tagEXCEPINFO { unsigned short int wCode; // Код ошибки unsigned short int wReserved; BSTR bstrSource; // Источник ошибки BSTR bstrDescription; // Описание BSTR bstrHelpFile; // Файл справки unsigned long dwHelpContext; // ID контекста void far * pvReserved; // Указатель на функцию, заполняющую поля // справки и описания HRESULT ( stdcall *pfnDeferredFillln) (struct tagEXCEPINFO *); // Возвращаемое значение, описывающее ошибку SCODE scode; } EXCEPINFO, * LPEXCEPINFO; Поддержка исключения автоматизации в MFC MFC обеспечивает простую в использовании поддержку обработки исключений автоматизации. Применяя отладчик для пошагового прохода кода MFC, можно увидеть пример кода, который работает со структурой excepinfo и вызывает invoke непосредственно. Чтобы по достоинству оценить автоматически предоставляемый MFC-сервис, запустите программу-клиент BankError, но на этот раз воспользуйтесь опцией Dispatch. Установите отрицательное значение в поле Amount и щелкните на кнопке Deposit. В то время как при выборе опции Custom эта операция не приводила ни к какому результату и завершалась без каких-либо сообщений, теперь при обращении к диспетчерскому интерфейсу мы получим окно, показанное на рис. 12.8. Рис. 12.8. Окно сообщения, выводимое MFC для не перехваченного исключения автоматизации Это окно сообщений выводит не наша программа (которая использует заголовок выводимого окна "Our Error Message", как можно увидеть при вызове withdraw при заданном отрицательном параметре), a MFC. Для изучения этого явления запустите программу в отладчике и поставьте точку останова на вызове Deposit. Пройдите, шаг за шагом, до вызова invokeHelper. void DAccount::Deposit(long amount) { static BYTE parms[] = VTS_I4; InvokeHelper(0x2, DISPATCH_METHOD, VT_EMPTY, NULL, parms, amount); }
Пройдите, шаг за шагом, по функции invokeHelper, а затем — по COleDispatchDriver: : InvokeHelperV — и так до тех пор, пока не попадете в функцию invoke. Вы должны увидеть инициализацию структуры excepinfo и вызов Invoke. // Инициализация структуры EXCEPINFO EXCEPINFO excepinfo; memset(&exceplnfo,0,sizeof excepinfo); UINT nArgErr = (UINT) -1; // Вызов SCODE sc = m_lpDispatch->Invoke(dwDispID, IID_NULL, 0, wFlags, Sdispparams, pvarResult, fiexceplnfo, SnArgErr); Немного дальше вы встретитесь с кодом, который генерирует исключительную ситуацию. if (FAILED(sc)) { VariantClear(&vaResult) ; if (sc != DISP_E_EXCEPTION) { //He исключение AfxThrowOleException(sc); } // Заполняется структура excepinfo if (excepinfo.pfnDeferredFillln != NULL) excepinfo.pfnDeferredFillln(&exceplnfo); // Выделение нового исключения и заполнение COleDispatchException * pException = new COleDispatchException(NULL,0,excepInfo.wCode); ASSERT(pException->m_wCode == exceptlnfо.wCode); if (excepinfo.bstrSource != NULL) { pException->m_strSource = excepinfo.bstrSource; SysFreeString(excepinfo.bstrSource); } // Заполнение оставшихся полей // Генерация исключения THROW(pException); COleDispatchException COleDispatchException представляет собой класс исключения MFC (производный от CException), который используется для передачи детальной информации об автоматизации. Для генерации COleDispatchException применяется функция AfxThrowOleDispatchException. Данные-члены представляют информацию, предоставляемую IErrorlnfo. m_wCode Код ошибки IDispatch m_strDescription Текстовое описание ошибки
m_strHelpFile Файл справки, содержащий расширенную справочную информацию m_dwHelpContext Идентификатор контекста в файле справки m_strSource Приложение, сгенерировавшее исключение Использование COleDispatchException Программа-клиент, вызывающая автоматизацию с помощью вспомогательных функций MFC, может работать с исключениями автоматизации посредством стандартного синтаксиса C++ try .catch. Вот код, использованный в методе Withdraw для случая диспетчерского интерфейса. try { m_dispDAccount.Withdraw(amount); balance = m_dispDAccount.GetBalance(); SetDlgItemInt(IDC_BALANCE, balance); } catch(COleDispatchException * e) { MessageBox(e->m_strDescription, "Our Error Message"); e-Delete(); } Заметьте, что, поскольку передается указатель на исключение, вы должны вызвать метод Delete класса CException. Сравнивая обработчики Deposit и Withdraw, вы увидите, что они отличаются друг от друга. В случае withdraw мы явным образом перехватываем исключение и обрабатываем его (выводя окно сообщения об ошибке). В случае Deposit мы игнорируем код обработки исключения, что должно рассматриваться как дефект нашей программы (допущенный исключительно в учебных целях). MFC обеспечивает в этой ситуации определенную защиту, используя обработчик исключительной ситуации по умолчанию, выводит сообщение об ошибке на основе и н- формации, полученной из структуры excepinfo. Можно также сравнить реализации withdraw в программах-клиентах, содержащихся в каталогах Test и SmartClient. Код программы Test использует непосредственный вызов интерфейсов ошибки СОМ и, таким образом, более сложен. Такая обработка ошибок должен быть выполнена для каждой ошибки, что очень непрактично с точки зрения программирования. Данный пример был приведен исключительно для того, чтобы познакомить вас с этим способом обработки ошибок. Клиент SmartClient предоставляет в наше распоряжение гораздо более практичное решение. Концептуально оно сходно с подходом, принятым в MFC. В обоих случаях от непосредственного низкоуровневого программирования нас защищают классы C++, с применением которых задача упростилась — теперь она сводится к обычному перехвату исключений C++. В следующем разделе мы рассмотрим, каким образом Visual Basic справляется с обработкой ошибок. Обработка ошибок СОМ в Visual Basic Visual Basic имеет встроенную поддержку интерфейсов ошибок СОМ, что позволяет очень легко обрабатывать возникающие ошибки. При ошибке COM Visual Basic не возвращает значение hresult — вместо этого генерируется ошибка Visual Basic, которая может быть перехвачена оператором Visual Basic On Error. Любая не перехваченная таким образом ошибка обрабатывается системой времени выполнения Visual Basic, которая выводит окно с сообщением об ошибке (с расширенной информацией, если таковая имеется) и прекращает выполнение программы.
Обработка ошибок по умолчанию Сначала рассмотрим, что происходит, когда у нас нет никакой обработки ошибок в нашем коде. Вот минимальная программа Visual Basic для работы с сервером BankError, которую вы сможете найти в каталоге chapl2\BankClientVb\Stepl. Option Explicit Dim objAccount As AccountError Private Sub cmdDeposit_Click() objAccount.Deposit txtAmount txtBalance = objAccount.balance End Sub Private Sub cmdWithdraw_Click() objAccount.Withdraw txtAmount txtBalance = objAccount.balance End Sub Private Sub Form_Load() Set objAccount = New AccountError txtBalance = objAccount.balance txtAmount = 25 End Sub Соберите выполняемый файл BankClientVb.exe (с помощью команды меню File^Make) и запустите его с помощью Windows Explorer. Вначале "испытайте" выполнение корректных вкладов и снятия со счета, а затем попытайтесь снять со счета сумму, превышающую текущий баланс. Вы увидите сообщение о фатальной ошибке, выводимое системой времени выполнения Visual Basic, показанное на рис. 12.9. Обратите внимание на то, что Visual Basic без какого бы то ни было участия с вашей стороны обращается к интерфейсу СОМ за дополнительной информацией о произошедшей ошибке. Если программа запускается из среды Visual Basic, то выводится несколько иное окно сообщения об ошибке, показанное на рис. 12.10, которое позволяет вам при желании приступить к отладке программы. Рис. 12.9. В случае необработанных ошибок Visual Basic выводит сообщение о фатальной ошибке в программе Рис. 12.10. Сообщение об ошибке, выводимое Visual Basic Использование оператора On Error Visual Basic предоставляет оператор On Error, который вы можете поместить перед кодом, способным вызвать ошибку. Вы также можете указать оператор GoTo и метку. Если происходит ошибка, нормальное течение программы прерывается и вы-
полняется код по указанной метке. Если ошибки нет, выполнение программы происходит последовательно. Обычно перед меткой ставится оператор Exit, для того чтобы нормальное выполнение программы не привело к выполнению обработчика ошибки. Visual Basic предоставляет также встроенный объект Err, который представляет собой оболочку объекта ошибки СОМ. Для получение описания произошедшей ошибки вы можете воспользоваться свойством Description объекта Err. Это описание Visual Basic получает от сервера как часть расширенной информации об ошибке. Следующий код иллюстрирует применение On Error в Visual Basic. Как и в случае примера на Visual C++, в случае Deposit мы не обрабатываем ошибку (код закомментирован), а в случае Withdraw выводим окно сообщения об ошибке. Option Explicit Dim objAccount As AccountError Private Sub cmdDeposit_Click() On Error GoTo ErrorHandler objAccount.Deposit txtAmount txtBalance = objAccount.Balance Exit Sub ErrorHandler: 'MsgBox Err.Description End Sub Private Sub cmdWithdraw_Click() On Error GoTo ErrorHandler objAccount.Withdraw txtAmount txtBalance = objAccount.Balance Exit Sub ErrorHandler: MsgBox Err.Description, , "Our Error Message" End Sub Private Sub Form_Load() Set objAccount = New AccountError txtBalance = objAccount.Balance txtAmount = 25 End Sub Запустив программу BankClientVb.exe из каталога Step2 (первоначально собрав ее), вы должны получить ожидаемое поведение. При попытке снять больше, чем имеется на счету, вы получите сообщение о нефатальной ошибке, показанное на рис 12.11. Рис. 12.11. Окно сообщения, выводимое нашим кодом обработки ошибок Если вы запустите указанную программу из среды Visual Basic, то наверняка удивитесь, увидев сообщение о фатальной ошибке Visual Basic (по умолчанию) вместо созданного нами. Visual Basic имеет установку, что надлежит делать в случае возникновения ошибки. Поведение по умолчанию — остановка при любых ошибках (чтобы вы тут же могли перейти к нужной строке программы, щелкнув на кнопке Debug). При желании вы можете изменить это поведение, например, на остановку только в случае необработанных ошибок. Соответствующие опции имеются во вкладке General диалогового окна Options, вызываемого с помощью команды меню Tools^Options (см. рис. 12.12).
Рис. 12.12. Настройка останова Visual Basic при возникновении ошибки Трассировка и отладка В этом разделе мы рассмотрим некоторые возможности Visual Studio и SDK, которые помогают в отладке ваших СОМ-приложений. ATL предоставляет ряд полезных возможностей трассировки; кроме того, имеются различные способы останова работы сервера, предусмотренные для того, чтобы вы могли воспользоваться отладчиком. Поддержка трассировки ATL Макрос atltrace выводит отформатированную строку в окне вывода при отладке. Этот макрос работает подобно макросу trace в MFC. В нем вы можете использовать то же форматирование строки, что HBprintf. ATLTRACE("value = %d\n", value); Трассировка вызовов Querylnterface ATL предоставляет специальные средства для трассировки вызовов Querylnterface. Для этого в разделе Interfaces системного реестра должны содержаться все общие IID. ATL имеет для этого специальную утилиту findguid, которую вы должны запустить с параметром командной строки -insert. Это достаточно сделать только один раз. Затем в stdafx.h (до включения файла atlcom.h) вам следует определить символ _ATL_DEBUG_QI. // stdafx.h #define STRICT tdefine _WIN32_WINNT 0x0400 #define _ATL_APARTMENT_THREADED #include <atlbase.h> extern CComModule _Module; #define _ATL_DEBUG_QI #include <atlcom.h>
Вывод трассировки Благодаря использованию alttrace (на сервере), trace (в MFC-клиенте) и трассировка Querylntrface вы будете иметь полную картину работы СОМ в вашей программе. Приведенная далее трассировка показывает создание и использование объекта банковского счета, при котором происходит ошибка. Выделенные строки соответствуют автоматической трассировке ATL-вызовов Querylnterface; остальные строки представляют собой результат работы atltrace и trace. CComClassFactory - IUnknown CComClassFactory - IClassFactory CAccount object created CAccount - IAccount CAccount - IDispatch CAccount - ISupportErrorlnfo ISupportErrorlnfo interface is available Error info is available for IAccount GetErrorlnfo succeeded GetDescription succeeded CAccount object deestroyed Трассировка в SDK В основе трассировки ATL и MFC лежит функция Win32 OutputDebugString, которая посылает свой вывод текущему отладчику. В Visual C++ это означает окно вывода (Output) при запуске отладочной версии программы в среде разработки. Platform SDK предоставляет утилиту под названием DbMon, а та — отладочное окно, не зависящее от Visual Studio. Если SDK установлен в вашей системе, DbMon можно вызвать с помощью команды меню Start^Programs^Platform SDKoTools^DbMon. После этого все, что будет выводиться любой трассировкой с применением функции OutputDebugString, будет отображаться в окне DbMon, если программа запущена не из Visual Studio. Например, если вы запустите DbMon и test.exe (для сервера BankError), то получите вывод, показанный на рис. 12.13. Рис. 12.13. Отладочный вывод в окне DbMon Останов выполнения программы При отладке серверов лучше всего остановить работу сервера и воспользоваться отладчиком. Основная технология отладки приложения без применения современных отладчиков — продуманное применение функции printf. В предыдущем разделе вы увидели, каким образом можно выполнить трассировку и получить ее вывод в отладчике или в окне утилиты DbMon. Еще один способ заключается в ведении своего собственного журнала с использованием обычных средств файлового ввода-вывода. Если вы бу-
дете закрывать файл после каждой операции записи в него и открывать для добавления новой строки, то получите мощное средство записи активности вашего приложения, не зависящее от всякого рода неприятностей, которые могут возникнуть из-за не сброшенных на диск буферов. Таким образом, если вы не можете воспользоваться отладчиком, не забывайте об этом древнем и надежном способе. В следующем разделе мы рассмотрим простой компонент Logger, реализующий описанную технологию. Но все же неплохо иметь возможность воспользоваться отладчиком. Вот несколько советов. Точки останова в сервере контекста приложения Легче всего задача решается в случае, когда ваш сервер представляет собой DLL, работающую в контексте процесса клиента (не суррогата). Вы можете открыть проект DLL, установить точку останова и определить вашу программу-клиент как Executable for debug session (меню Project^Settings, вкладка Debug), как показано на рис. 12.14. Рис. 12.14. Определение выполняемого файла для отладки DLL Точки останова в ЕХЕ-сервере Технология, которая можно применить для ЕХЕ-серверов, заключается в определении точек останова ЕХЕ-сервера в сессии Visual C++. Затем следует запустить сервер из Visual C++, а после этого вызвать вторую сессию Visual C++ для запуска клиента. Таким образом, вы сможете устанавливать точки останова как в клиенте, так и в сервере. Но для того, чтобы эта технология сработала, требуется, чтобы клиент обратился именно к запущенному в Visual C++ серверу. Это не произойдет, например, если параметр REGCLS в CoRegisterClassObject будет равен REGCLS_SINGLEUSE. В таком случае запущенный из Visual C++ сервер останется заброшенным и забытым и будет безмятежно дожидаться достижения точки останова до самой перезагрузки машины. В случае составных документов OLE эта технология обычно хорошо работает с сервером, реализованным как MDI-приложение. Использование функции DebugBreak Еще одна технология для останова программы заключается в вызове функции Win32 DebugBreak в модуле, которому предстоит отладка. Перестройте ваш проект и запустите программу-клиент. При вызове DebugBreak будет выведено окно с сообщением об ошибке, показанное на рис. 12.15.
Рис. 12.15. Диалоговое окно сообщения об ошибке позволяет вам войти в отладчик Щелкнув на кнопке Cancel, вы окажетесь в отладчике. Причем посреди ассемблерного кода. Не огорчайтесь: два шага — и вы окажетесь в подпрограмме, вызвавшей DebugBreak. На экране исходный текст смешан с ассемблерным кодом. Например, если вы вызовете DebugBreak в начале функции get_Balance сервера BankError, то увидите примерно следующее: 36: STDMETHODIMP CAccount::get_Balance(long * pVaL) 37: { 10001840 push ebp 10001841 mov ebp, esp 10001843 sub esp, 40h 10001846 push ebx 10001847 push esi 10001848 push edi 38: DebugBreak(); 10001849 call dword ptr [ imp DebugBreak@0(100373e8)] 39: *pVal = m_nBalance; 1000184F mov eax, dword ptr [ebp + OCh] 10001852 mov ecx, dword ptr [ebp + 8] 10001855 mov edx, dword ptr [ecx + 8] 10001858 mov dword ptr [eax], edx 40: return S_OK; 1000185A xor eax, eax 41: } Теперь вы можете устанавливать точки останова в обычном окне с текстом программы и работать на уровне отладки исходных текстов. Компонент Logger В этом разделе мы разработаем простой компонент Logger, который выполняет запись в фиксированный файл c:\logfile.txt. Каждая запись включает открытие файла, добавление к нему информации и закрытие. Таким образом мы избегаем неприятностей, которые могли бы появиться, если бы буферы не были сброшены на диск. Если файла не существует, то он создается заново в первой записи. Метод Write может использоваться для записи произвольной строки, а метод Writehr — для записи hresult как в виде шестнадцатеричного числа, так и системной текстовой строки описания, связанной с этим номером ошибки. Являясь компонентом COM, Logger может быть вызван как из Visual Basic, так и из Visual C++. Компонент находится в каталоге Chapl2\Logger. Тестовая программа на Visual C++ размещена в подкаталоге test, а на Visual Basic — в каталоге Chapl2\TestLoggerVb. Соберите сервер, который зарегистрирует компонент Logger.
Работа из Visual C++ Хотя компонент и написан на Visual C++, вызывать его из программы на Visual Basic несколько легче, чем из программы на Visual C++, так как строка передается в виде данных типа BSTR. Поэтому мы воспользуемся классом-оболочкой CLogging. Обычно запись выполняется из компонента среднего уровня, написанного с применением ATL. Если ваш код C++ не использует ATL, вы должны включить заголовочный файл atlbase.h, для того чтобы иметь возможность воспользоваться упрощающим работу С BSTR KJiaCCOM-обоЛОЧКОЙ CComBSTR. Работать с компонентом Logger с помощью объекта m_log класса CLogging очень просто. void CTestDlg::OnWrite () { CString text; GetDlgltemText(IDCJTEXT,text); m_log.Write(text); } void CTestDlg::OnWritehr() { m_log.Writehr("success", 0) ; m_log.Writehr("failure 1",0x800401F3); m_log.Writehr("failure 2",0x80080005); } Тестовая программа выводит простое диалоговое окно, показанное на рис. 12.16, которое позволяет вам записать строку и несколько заранее заданных значений hresult. Рис. 12.16. Тестовая программа на Visual C++ для компонента Logger Введите несколько строк, затем щелкните на кнопке Write Some HRESULTs. После этого просмотрите файл c:\logfile.txt. Обратите внимание: программа создана так, что при hresult, обозначающем успешное завершение, в файл ничего не записывается. First line of sample text Second line of sample text failure 1, hr = 0x800401f3 Invalid class string failure 2, hr = 0x80080005 Access is denied Работа из Visual Basic Поскольку BSTR — "родной" тип Visual Basic, компонент Logger легко использовать непосредственно. Вот полный код тестовой программы Visual Basic.
Dim objLog As New Log Private Sub cmdWrite_Click() objLog.Write Textl End Sub Private Sub cmdWritehr_Click() objLog.Writehr "success", 0 objLog.Writehr "failure 1", &H800401F3 objLog.Writehr "failure 2", &H80080005 End Sub Резюме Основное средство, посредством которого СОМ сообщает о произошедшей ошибке, является возвращаемое значение hresult. hresult представляет собой 32-битовое число, содержащее указание на то, произошла ошибка или нет, и если произошла, то какая и где. Вы можете определять собственные коды ошибки, однако коды, определяющие класс ошибки, зарезервированы Microsoft. Имеется ряд интерфейсов СОМ, которые могут использоваться для получения расширенной информации об ошибке. ATL обеспечивает поддержку этих интерфейсов ошибок, облегчая их использование. Visual C++ упрощает работу с ошибками, предоставляя собственный класс _com_error. Автоматизация обеспечивает механизм исключений, которые MFC автоматически переводит в исключения C++. Visual Basic позволяет обрабатывать исключения автоматизации с помощью оператора On Error. Имеется ряд технологий, которые облегчают трассировку и отладку СОМ-серверов и останов в процессе работы сервера для входа в отладчик. К числу полезных при отладке технологий относится запись информации в журнальный файл.
Глава 13 Многопоточность в СОМ Параллельность вычислений представляет собой важный вопрос в компьютерном программировании. В Windows можно создавать параллельно работающие программы, используя множество потоков, благодаря чему можно достичь существенного повышения производительности программ (впрочем, ценой этому будет их усложнение, в связи с необходимостью обеспечения безопасной работы потоков). СОМ привносит новые подходы к использованию многопоточности, поскольку клиент и сервер могут создаваться независимо, и каждый из них может иметь собственный подход к проблемам многопоточности. Вызывает определенные сомнения возможность сосуществования серверов и клиентов с различными подходами к проблемам многопоточности, но эта задача вполне решаема. СОМ предоставляет архитектурный элемент, именуемый апартаментом5, служащий в качестве абстракции требований к потокам и их использования. Сервер может объявить модель апартаментов потоков для реализуемых им классов, и система времени выполнения СОМ при этом согласует их использование различными клиентами. Этот декларативный стиль программирования широко используется в СОМ+, и, таким образом, рассмотрение многопоточности СОМ поможет вам перейти к изучению СОМ+ в части III, "Windows DNA и СОМ+". Эта глава начинается с рассмотрения фундаментальных концепций параллельных вычислений, в том числе проблемы "условий гонок", которая иллюстрируется конкретными примерами. Система сообщений Windows обеспечивает программиста механизмом синхронизации, который может помочь вам избежать этой проблемы. После рассмотрения основ многопоточности мы приступим к изучению различных типов апартаментов и моделей многопоточности DLL-серверов. Затем мы приступим к программированию однопоточных апартаментов, после чего обсудим интерфейсы маршалинга между апартаментами. Последним, что мы рассмотрим в данной главе, будет вопрос многопоточных апартаментов. Параллельное программирование Одним из классических вопросов программирования являются параллельные вычисления. Если компьютер имеет несколько процессоров, то производительность можно увеличить, разбив вычисления на части, выполняемые разными процессорами одновременно. Даже если у вас имеется только один процессор, параллельность может оказаться весьма полезной, поскольку позволяет, например, не приостанавливать вычисления в процессе медленных аппаратных операций с использованием других ап- ^ В некоторых изданиях "Apartment" переводится как "комната, квартира". В данной книге используется более адекватный перевод "апартаменты " — Прим. перев.
паратных процессоров, например, операции обмена информацией с диском, которые в этом случае перестают блокировать работу основного процессора. Одним из главных вопросов параллельных вычислений является вопрос использования разделяемых данных. Если два вычислительных процесса обращаются к одним и тем же данным, ими могут быть получены различные результаты — в зависимости от момента обращения к данным, эта ситуация известна под названием "race condition" ("условие гонки"). Данная ситуация является настоящим вызовом программисту, возникая совершенно непредсказуемо. Для того чтобы избежать таких ситуаций, требуется большая внимательность и аккуратность. Пример условий гонки Неприятности особенно часто возникают в многопоточных приложениях, поскольку потоки принадлежат одному процессу с одним адресным пространством, а значит, и с одними разделяемыми данными. Рассмотрим два потока, осуществляющих вклады на счет. В данном случае операция вклада не атомарна, а состоит из следующих действий: ■ получение текущего баланса, ■ добавление к нему вносимой суммы; ■ сохранение значения баланса. А теперь рассмотрим следующую последовательность событий, приводящую к неверным результатам. 1. Начальное значение баланса равно 100. 2. Поток 1 кладет на счет 25, но он прерывается сразу при получении текущего значения баланса, до сохранения нового значения. 3. Поток 2 кладет на счет 5000 и завершает свои действия, сохраняя значение 5100 4. Поток 1 продолжает работу, сохраняя значение баланса, равное 125, перезаписывая значение, сохраненное потоком 1. Как вы думаете, с кого будут требовать потерянные $5000?... Упорядочение доступа к разделяемым данным Избежать условий гонки можно, упорядочив обращения к разделяемым данным. Предположим, что одновременно с величиной банковского счета может работать только один поток. Тогда первый поток полностью выполнил бы все необходимые действия, и только потом к работе с балансом приступил бы временно блокированный второй поток. Есть два пути упорядочения доступа к разделяемым данным: ■ автоматически — с помощью системы; ■ явно — с помощью соответствующего кода прикладной программы, использующего взаимоисключающие примитивы типа критических частей кода. Оба подхода имеют свои достоинства и недостатки. Так, программисту проще использовать автоматическое упорядочение, но при этом может теряться определенная параллельность, поскольку может оказаться, что запрещен любой доступ к разделяемым данным, даже в случае, когда один из методов обращается только к части данных.
Автоматическое упорядочение обращения к данным Windows упорядочивает обработку сообщений с помощью цикла GetMessage/DispatchMessage. Вы не должны писать безопасные с точки зрения многопоточности процедуры Windows. COM использует достоинства упорядочения, присущие обработке сообщений Windows, при маршалинге вызовов методов через границы процессов. Маршалинг реализуется посредством обработки сообщений в скрытом окне. Следовательно, один вызов метода СОМ-объекта будет полностью завершен до того, как начнет выполняться другой метод. При таком подходе при написании методов СОМ не обязательно строго придерживаться правил безопасности с точки зрения многопоточности. Демонстрация очереди сообщений Windows Демонстрационная программа, иллюстрирующая использование сообщений Windows для защиты от условий гонки, может быть найдена в каталоге Chapl3\BankMt\StepO, в котором содержатся два проекта — BankMt и test. BankMt — EXE- сервер объекта банка, a test — диалоговая тестовая программа, окно которой показано на рис. 13.1. Рис. 13.1. Тестовая программа, демонстрирующая автоматическое упорядочение DelayDeposit Метод DelayDeposit упрощает имитацию условий гонки. После вычисления нового значения баланса в локальной переменной метод, перед тем как сохранить это новое значение баланса, притормаживается на некоторое время. Во время задержки возможно выполнение метода Deposit, и в результате завершение работы метода DelayDeposit должно привести к потере результата работы метода Deposit. STDMETHODIMP CBankApt::DelayDeposit(long amount, long delay) { long balance = g_balance; balance += amount; Sleep(delay); g_balance = balance; return S_OK; } Тестовая программа 1. Соберите проект BankMt. Заметьте, что, как часть процесса построения, регистрируются как сам сервер, так и DLL прокси/заглушек. Последнее необходимо, поскольку сервер представляет собой ЕХЕ и прокси/заглушки обеспечивают маршалинг. 2. Постройте проект test. 3. Запустите два экземпляра test.exe. В обоих экземплярах щелкните на кнопке Initialize.
4. В обоих экземплярах щелкните на кнопке Deposit, а затем — на кнопке Get- Balance, чтобы увидеть текущее состояние счета. Заметьте, что оба экземпляра работают с одними и теми же глобальными данными. В связи с высокой скоростью выполнения метода Deposit вы не увидите никаких условий гонки и некорректности работы сервера. Создание условий гонки Теперь попытаемся воспроизвести сценарий условий гонки, приведенный ранее. В первом экземпляре оставьте величину Amount равной 25, а во втором установите ее значение равным 5000. Теперь щелкните на кнопке Delayed Deposit в первом экземпляре, а после этого (немедленно) — на кнопке Deposit во втором экземпляре. Что при этом происходит? Вызов Deposit блокируется СОМ до тех пор, пока не произойдет возврат из DelayedDeposit. Таким образом СОМ обеспечивает последовательный доступ методов и позволяет избежать условий гонки. При этом при реализации этих методов не требуется никакой специальный код. Проведем еще один маленький эксперимент. В первом экземпляре щелкнем на кнопке Delayed Deposit, а во втором — на кнопке Get Owner. И хотя данные о владельце не зависят от баланса, СОМ все равно будет блокировать выполнение get_Owner до тех пор, пока не выполнится DelayDeposit. Цена безопасности — потеря параллельности. Скрытое окно Если воспользоваться Spy++, можно увидеть скрытое окно, которое СОМ использует для маршалинга. Spy++ можно вызвать из меню Tools в Visual C++. Воспользуйтесь командой меню Spy^Processes и найдите BANKMT. Раскрывайте найденный узел дерева (рис. 13.2), пока не будет показано интересующее нас окно. Рис. 13.2. Скрытое окно, используемое для осуществления RPC через границы процесса на одной машине Щелчком правой кнопки мыши вы можете вызвать окно с сообщениями Windows, связанными с вызовами методов (рис. 13.3).
Рис. 13.3. Вызовы методовt переведенные в сообщения Windows Апартаменты и многопоточность в СОМ Мы видели, что использование системы сообщений Windows автоматически упорядочивает доступ к объекту, расположенному в другом процессе. Однако в случае объектов, расположенных в одном и том же процессе и двух потоках этого процесса, параллельно обращающихся к объекту, ничего нельзя гарантировать. Из этой ситуации есть два различных выхода. Первый состоит в простом программировании с гарантированной последовательностью доступа, как в случае пересечения границ процессов. Второй представляет собой высокопроизводительную, но сложную технологию — работу по устранению всех неприятностей, связанных с синхронизацией потоков, программист берет на себя. Апартаменты СОМ использует абстракции апартаментов и концепции "потоковой модели" класса. Апартаменты группируют объекты, которые разделяют одни и те же требования к многопоточности. Апартамент объекта является его отличительной чертой. В NT 4.0 имеется два вида апартаментов: однопоточный (single-threaded — STA) и многопоточный (multithreaded — МТА). В данном процессе может быть несколько STA и только один МТА. В STA может выполняться только один поток, в то время как в МТА могут выполняться параллельно несколько потоков. Поток "входит" в апартамент при вызове ColnitializeEx, в котором второй параметр указывает тип апартамента. STA создается, когда поток входит в него; МТА создается при первом вызове ColnitializeEx со вторым параметром, указывающим на МТА. Первый созданный STA называется главным (main). На рис. 13.4 показан процесс с четырьмя потоками и тремя апартаментами. Поток 1 создает главный STA, поток 2 — второй STA, поток 3 — МТА, поток 4 входит в уже существующий МТА. Однопоточные апартаменты STA приводит к упорядочению обращения к любому объекту, размещенному в нем. Для этого СОМ использует очередь сообщений Windows, связанную со скрытым окном, создаваемым каждым STA. В предыдущем разделе мы продемонстрировали такое скрытое окно в программе-примере. Это означает , что не может быть параллель-
ного доступа к методам и объектам, находящимся в STA, и вам не обязательно защищать данные экземпляра от параллельного доступа. В результате мы получаем существенное упрощение стоящей перед программистом задачи. Процесс 1 Главный STA 2 STA 3 4 МТА Рис. 13.4. Процесс с четырьмя потоками и тремя апартаментами Многопоточный апартамент МТА не предпринимает никаких действий по упорядочиванию доступа. Множественные потоки в МТА могут обращаться к одному и тому же объекту параллельно. Работающие в МТА потоки иногда называют "свободными потоками". Следовательно, код, реализующий методы класса, который может вызываться свободными потоками непосредственно, должен быть безопасен с точки зрения параллельного обращения. Должно выполняться явное кодирование синхронизации, такое, например, как защита разделяемых данных с помощью критических участков кода и т.п. Выгода от таких усилий заключается в потенциально более высокой производительности. ЕХЕ- и DLL-серверы Апартаменты не так важны для ЕХЕ-серверов, поскольку вызов каждого метода ЕХЕ-сервера переходит границу между процессами, а следовательно, осуществляется посредством механизма RPC или очереди сообщений, и СОМ предпринимает усилия по упорядочению вызовов. ЕХЕ-сервер указывает свой тип апартаментов передаваемым в ColnitializeEx параметром. В оставшихся разделах этой главы речь пойдет о DLL-серверах (см. также часть III, "Windows DNA и СОМ+"). Модели потоков Из-за разницы в требованиях к параллельности разных типов апартаментов, в обязанности сервера входит определение модели потоков каждого его класса. Модель потоков определяет тип апартамента, в котором может выполняться объект. Обратите внимание на тот факт, что сервер может реализовывать множество классов, а каждый класс имеет свою собственную модель. На рис. 13.5 показаны три класса, реализуемые сервером BankMt, с которым мы уже встречались в этой главе. Класс BankApt поддерживает апартаментную модель потоков, а классы BankFree и BankNoCS — свободную модель. DLL-сервер не вызывает ColnitializeEx — вместо него это делает клиент. DLL-сервер указывает модели потоков своих классов, размещая соответствующие записи в системном реестре, для чего используется именованное значение ThreadingModel в ключе inprocServer32. Если такое значение отсутствует, это значит, что модель однопоточная; в противном случае модель указывается значением Apartment, Free или Both. На рис. 13.6 показаны записи системного реестра для класса BankApt с апартаментной моделью потоков.
BankMt DLL Server BankApt BankFree BankNoCS Апартамент Свободная модель Свободная модель Рис. 13.5. Каждый класс DLL-сервера может иметь свою собственную модель потоков Рис. 13.6. Записи системного реестра для класса с апартаментной моделью потоков Однопоточная модель Классы в серверах-наследниках СОМ не имеют значения ThreadingModel в системном реестре, а потому считается, что их модель потоков — Single. Это означает, что все экземпляры объекта, принадлежащего данному классу, будут размещены в
главном STA. Следовательно, ни о какой параллельности в данном случае не может идти и речи. Даже статические или глобальные данные класса (такие как счетчик объектов или счетчик блокировок фабрики классов) не должны быть защищены от параллельного доступа. Но это означает, что только один поток может иметь доступ к СОМ-объекту. Как и в случае NT 3.5, в данном случае СОМ-объекты просто не могут существовать более чем в одном потоке. Апартаментная модель потоков В NT 3.51 была реализована концепция апартаментов и соответствующая модель потоков, и теперь СОМ-объекты могут существовать во многих потоках, но при этом каждый находится в своем собственном STA. Совершенно законно обращение клиента из одного потока к объекту из другого потока (находящемуся в другом апартаме н- те), но это обращение должно осуществляться через прокси. Прокси использует синхронизацию посредством очереди сообщений, и, таким образом, доступ к объектам СОМ упорядочен. Гениальность этого подхода состоит в том, что он позволяет существовать многопоточным СОМ-приложениям, не требуя от СОМ-классов никаких действий по обеспечению многопоточной безопасности их методов. Тем не менее DLL-сервер, который поддерживает классы с апартаментной моделью потоков, должен позаботиться о некоторых связанных с многопоточностью вопросах. Многие клиенты могут создавать экземпляры объектов параллельно, так что ему надо позаботиться о защите статических и глобальных данных, таких как счетчики объектов и блокировок, от параллельного доступа. Методы фабрики классов, DllGetClassObject и DllCanUnloadNow, должны быть "потокобезопасны". Как мы вскоре увидим, ATL предоставляет нам именно безопасную реализацию. Модель свободных потоков Хотя модель апартаментов и делает возможными многопоточные приложения СОМ, при таком подходе имеются определенные ограничения производительности. Прокси — достаточно грубый инструмент для обеспечения упорядоченности доступа, поскольку при его использовании блокируются любые параллельные обращения к объекту. Объект может состоять из многих частей инкапсулированных данных. В то время как один поток работает с одной частью данных, другой поток мог бы вполне безопасно работать с другой частью независимых данных, но блокировка на уровне объекта в модели апартаментов запрещает такой параллельный доступ. Поэтому в NT 4.0 введены модель свободных потоков и многопоточные апартаменты. Как мы видели, процесс может иметь один многопоточный апартамент (МТА) с несколькими потоками, параллельно обращающимися к одному и тому же объекту. В модели свободных потоков все экземпляры объектов находятся в этом МТА. Данная модель обеспечивает превосходную производительность клиента, создавшего МТА (посредством вызова ColnitializeEx с параметром СО IN IT_MULTI THREADED), и его рабочих потоков. Если эти потоки выполняют большое количество работы, то модель свободных потоков может дать существенный выигрыш в производительности, так как потоки имеют непосредственный доступ к объекту (без прокси) при отсутствии глобальных блокировок объекта (блокируются только необходимые данные). Что случится, если поток, находящийся в некотором STA, вызовет объект? Такой вызов пересечет границы апартамента и, следовательно, будет осуществлен посредством прокси. Модель "Both" Последняя модель потоков в NT 4.0 — "Both" ("Оба"). В этом случае созданный экземпляр СОМ-объекта может находиться в любом апартаменте — МТА или STA. Если экземпляр объекта создается любым потоком из МТА, объект будет находиться в
МТА. Если экземпляр объекта создается в некотором STA, он будет находиться в этом конкретном STA. Таким образом, в модели "Both" клиент из STA может вызывать объект непосредственно, без прокси, в отличие от модели свободных потоков, где объекты всегда находятся в МТА. Выбор модели потоков Выбор подходящей модели потоков для класса зависит от приложения. Совреме н- ные приложения не должны использовать однопоточную модель (она, конечно, проще в реализации, поскольку сама по себе DLL не является безопасной с точки зрения многопоточности, но применение ATL позволяет вам не задумываться об этом). Модель апартаментов пригодна во многих случаях. Вам не надо беспокоиться о безопасности, так как СОМ упорядочивает все вызовы посредством прокси. Кроме того, ваш клиент может реализовать множество потоков, обращающихся к объекту. Модель апартаментов представляет собой хороший компромисс между простотой ре а- лизации и разумной производительностью. Если вы реализуете ваш класс, используя Visual Basic, выбор делается за вас — Visual Basic поддерживает только модель апартаментов. Точно так же, если вы реализуете ваш класс, применяя классы пользовательских интерфейсов MFC, модель потоков должна быть моделью апартаментов. При использовании ATL для реализации ваших классов выбор моделей остается за вами. Вы можете применить модель свободных потоков или модель " Both". В обоих случаях вы должны будете написать безопасный с точки зрения многопоточности код для ваших методов классов. Если предвидится интенсивное обращение к вашим объектам как со стороны потоков STA, так и со стороны потоков МТА, вероятно, более предпочтительной будет модель "Both". Если же предполагается массовое обращение к объектам из рабочих потоков в клиенте, создавшем МТА, лучшим выбором будет модель свободных потоков. Нейтральная модель потоков Windows 2000 предлагает еще один тип апартаментов и соответствующую модель потоков, называемую нейтральной. Эту модель, предпочтительную при работе с СОМ+, мы рассмотрим в главе 16, "Параллельные вычисления в СОМ+". Пересечение границ апартаментов Ключевое свойство архитектуры апартаментов заключается в том, что любой вызов объекта, размещенного в другом апартаменте, должен производиться посредством прокси, как показано на рис. 13.7. Сервер, работающий вне контекста текущего процесса, представляет собой частный случай, потому что объект в другом процессе автоматически находится в другом апартаменте. Новой особенностью является пересечение границ апартаментов в одном процессе. Для разрешения межапартаментных вызовов в пределах процесса вам требуется DLL прокси/заглушек, как и в случае ЕХЕ-сервера. Апартамент А i— Клиент Прокси Заглушка Апартамент В Объект Рис. 13.7. При пересечении границ апартаментов используется прокси
Маршалинг указателя на интерфейс В большинстве случаев инфраструктура СОМ автоматически обрабатывает процессы маршалинга с помощью прокси/заглушек, при этом к вам как к разработчику какие- либо специальные программные требования (кроме генерации и регистрации DLL прокси/заглушек) не предъявляются. Однако при пересечении границ апартаментов в пределах процесса возникает ситуация, которая может потребовать особого внимания. Указатель на интерфейс объекта в одном апартаменте процесса доступен потоку в другом апартаменте процесса, поскольку апартаменты разделяют общее адресное пространство. Вы должны принять меры для того, чтобы ваша программа никогда не вызывала объект через указатель на интерфейс непосредственно. Вместо этого вы должны явно выполнить маршалинг указателя на интерфейс. СОМ предоставляет для выполнения межпоточного маршалинга в пределах одного процесса следующие API-функции. ■ CoMarshallnterThreadlnterfacelnStream, осуществляющую маршалинг указателя на интерфейс в объект потока (в данном случае имеется в виду не поток выполнения программы (thread), а поток передачи указателей на интерфейс (stream), — прим. перев.), из которого он может быть впоследствии извлечен. ■ CoGetlnterfaceAndReleaseStream, извлекающую указатель на интерфейс из объекта потока и освобождающую поток. Детально эти функции мы рассмотрим в следующем разделе, при рассмотрении вопросов реализации многопоточности. Реализация многопоточности в СОМ В этом разделе главы мы рассмотрим вопросы реализации многопоточности в СОМ. Хорошая новость заключается в том, что ATL берет на себя основную часть тяжелой работы. В случае апартаментной модели потоков гарантируется, что ваш DLL-сервер является безопасным с точки зрения многопоточности. СОМ берет на себя упорядочение доступа к вызову ваших методов, и поэтому вам не о чем беспокоиться. Если же вы работаете с моделью потоков "Both" или свободной, вы должны убедиться, что ваши методы безопасны с точки зрения многопоточности. Например, вам может понадобиться защитить данные экземпляра с помощью критических участков кода. Начнем мы рассмотрение с поддержки многопоточности, предоставляемой ATL. Остаток раздела будет посвящен демонстрационной программе, иллюстрирующей различные программные технологии и вопросы параллельных вычислений, рассмотренные нами ранее. Поддержка многопоточности в ATL Поддержка многопоточности в ATL начинается с ATL Object Wizard. Одним из устанавливаемых в нем атрибутов является модель потоков. Вы можете выбрать из четырех рассмотренных моделей самую подходящую (рис. 13.8). Сделанный вами выбор отражается в параметре шаблона класса, передаваемом в CComObjectRootEx. Например, при выборе модели Single или Apartment в качестве параметра используется ccomSingleThreadModel, а при выборе Free или Both — класс CComMultiThreadModel. Вы можете увидеть соответствующий код в файле определения класса. class ATL_NO_VTABLE CBankApt : public CComObjectRootEx<CComS±ngleThreadModel>, public CComCoClass<CBankApt, &CLSID_BankApt>,
public ISupportErrorlnfo, public IBank { Теперь ATL сделает всю необходимую работу вместо вас. Прежде всего будет обеспечена безопасная реализация функций, экспортируемых DLL, таких как DllGetClassObject и DllCanUnloadNow. Далее будет обеспечена безопасная реализация фабрик классов, включая защищенные переменные для хранения счетчиков объектов и блокировок. В случае выбора Free или Both, когда методы объекта должны быть безопасны с точки зрения многопоточности, безопасность обеспечивается в реализации интерфейса lUnknown. ATL, кроме того, предоставляет некоторые полезные классы-утилиты, например, таких как CComAutoCriticalSection, которые позволяют упростить реализацию критических участков кода. Пример DLL-сервера, шаг 1 В оставшейся части этой главы мы рассмотрим процесс разработки сервера BankMt, иллюстрирующего ряд свойств многопоточности СОМ, рассмотренных нами. Шаг 0 представляет собой ЕХЕ-сервер, демонстрирующий автоматическое упорядочение обращений к объекту посредством прокси, — это основная концепция, во многих случаях обеспечивающая безопасную многопоточную работу СОМ. На остальных шагах 1—4 рассматривается DLL-сервер. Вначале выберем проект в каталоге Chapl3\BankMt\stepl. Обратите внимание на то, что регистрация DLL прокси/заглушек выполняется как часть построения сервера BankMt (это действие — пользовательский шаг при построении проекта). Теперь запустите два экземпляра тестовой программы. В первом экземпляре внесите некоторую сумму на счет, после этого проверьте состояние счета во втором экземпляре. Вы получите исходное значение 100, как показано на рис. 13.9. Что же произошло? Рис. 13.8. ATL Object Wizard обеспечивает возможность выбора модели потоков Рис. 13.9. Почему баланс не был обновлен при внесении вклада? В Win32 DLL отображается в адресное пространство ЕХЕ, который загружает ее. Два экземпляра тестовой программы представляют собой различные процессы, каждый — в своем собственном пространстве. Создаются два отдельных объекта, каждый из которых — со своими собственными данными. Пример DLL-сервера, шаг 2 Следующим рассмотрим случай с одним экземпляром тестовой программы, запускающим множественные потоки, каждый из которых обращается к одному и тому же объекту. Соберем два проекта из каталога Chapl3\BankMt\step2 и выполним следующие действия.
1. Запустите тестовую программу и щелкните на кнопке Initialize. Кнопка New Thread становится доступной, и вы можете запустить из первого потока столько потоков, сколько хотите. 2. Щелкните на кнопке New Thread. При этом запустится новый поток пользовательского интерфейса (вы увидите его на панели задач). 3. Щелкните на пиктограмме, расположенной на панели задач, для того, чтобы вывести второе диалоговое окно. Разместите окна так, чтобы одновременно видеть их оба. Заметьте, что отличить экземпляры можно по наличию двух дополнительных кнопок в первоначальном окне (рис. 13.10). 4. Установите Amount во втором окне равным 5000. Рис. 13.10. Два потока, обращающихся к одному и тому же объекту 5. Щелкните на кнопке Delayed Deposit в первом окне. 6. Щелкните на кнопке Deposit во втором окне. Вызов не блокируется и немедленно возвращает значение обновленного баланса, равное 5100. 7. Ждите завершения Delayed Deposit в первом окне. Вы увидите, что баланс стал равен 125. 8. Щелкните на кнопке Get Balance во втором окне. Вы увидите, что баланс действительно равен 125. Ваши $5000 потеряны... Использование указателя на интерфейс По щелчку на кнопке Initialize создается один экземпляр объекта банка. Указатель на интерфейс хранится как статический член-данное класса диалога. class CTestDlg: public CDialog { // Реализация protected: void UpdateBalance(); HICON m_hIcon; static IBank* m_pBank; BOOL ir\_bFirst; Когда второй поток вызывает метод Deposit, он непосредственно использует указатель на интерфейс.
void CTestDlg::OnDeposit() { UpdateDataO ; m__pBank->Deposit (in_amount) ; UpdateBalance(); } Пример DLL-сервера, шаг З Для того чтобы воспользоваться преимуществами синхронизации доступа к методам посредством очереди сообщений Windows в СОМ, мы должны применить марша- линг указателя на интерфейс. Маршалинг выполняется автоматически, когда сервер находится вне контекста процесса. СОМ предоставляет API-функции (с очень длинными именами) для межпоточного маршалинга в пределах процесса. ■ CoMarshallnterThreadlnterfacelnStream выполняет маршалинг указателя на интерфейс в объект потока, из которого он может быть извлечен. ■ CoGetlnterfaceAndReleaseStream извлекает указатель на интерфейс из объекта потока и освобождает поток. Шаг 3 проекта иллюстрирует использование этих API-функций. Перед изучением кода соберите и запустите проект, содержащийся в каталоге Chapl3\BankMt\step3. В этот раз, когда вы запустите тестовую программу, пометьте Marshal Interface Pointer, перед тем как щелкнуть на кнопке Initialize. Затем запустите новый поток и попробуйте воспроизвести условия гонки, рассмотренные в предыдущем разделе. Щелкните на кнопке Delayed Deposit в первом окне и тут же — на кнопке Deposit во втором окне. Вызов вторым потоком метода Deposit будет блокироваться до тех пор, пока не завершится предыдущий вызов DelayDeposit. Теперь вновь щелкнем на кнопке Delayed Deposit в первом потоке, и тут же во втором потоке — на кнопке Get Owner. Хотя два этих вызова обращаются к разным данным и могут выполняться параллельно и независимо, вызов из второго потока блокируется до окончания выполнения вызова из первого потока. Синхронизация СОМ работает с объектами целиком. Теперь можно приступить к изучению кода в файле testDlg.cpp. Указатель на интерфейс "маршализуется" в поток в OnNewthread. void CTestDlg::OnNewthread() { // Маршалинг указателя на интерфейс // для каждого нового потока if (m_bMarshal) { HRESULT hr = CoMarshalInterThreadInterfaceInStream( IID_IBank, (IUnknown*) m_pBank, &mjpStream) ; if (FAILED(hr)) { MessageBox("Marshal failed"); return; } } CClient* pThread = new CClient; pThread->CreateThread(); }
Указатель на интерфейс может быть извлечен при создании диалога для нового потока в OnlnitDialog. BOOL CTestDlg::OnInitDialog() { if (m_bMarshal) { HRESULT hr = CoGetInter£aceAndReleaseStream( m_j>Stream, IID_IBank, (void**) &m_pBank); if (FAILED(hr)) { ::MessageBox(NULL, "Unmarshal failed", "Error", MB_SYSTEMMODAL); return TRUE; } } else { // Копируем интерфейс и вызываем AddRef m_pBank = g_pBank; m_pBank->AddRef(); } } return TRUE; } Пример DLL-сервера, шаг 4 К достоинствам синхронизации, обеспечиваемой СОМ при применении апарта- ментной модели потоков, можно отнести то, что она проста в использовании. Она автоматически выполняется при пересечении границ процессов и реализуется двумя вызовами функций API при пересечении границ потоков в пределах одного процесса. Самый главный недостаток апартаментной модели потоков заключается в потере параллельности вычислений при обращении различных методов к независимым данным. Свободные потоки зачастую обеспечивают большую производительность, но при этом требуют от программиста большей работы. СОМ при этом не обеспечивает никаких блокировок. Вы сами ответственны за многопоточную безопасность ваших методов, которая может быть достигнута, например, путем определения критических участков кода для модификации разделяемых данных. Соберите и запустите демонстрационную программу, содержащуюся в каталоге Step4. В этот раз Threading Model установите равным Free (No CS). Вновь запустите второй поток и щелкните на кнопке Delayed Deposit в первом потоке. Немедленно после этого щелкните на кнопке Get Owner во втором потоке. Вы тут же получите ответ, без какой-либо блокировки (рис. 13.11). Однако при использовании Delayed Deposit в первом потоке и Deposit во втором вы вновь получите условие гонки. Вновь запустите демонстрационную программу, на этот раз выбрав в качестве модели Free. Вновь щелкните на Delayed Deposit в первом потоке и сразу же — на кнопке Deposit во втором потоке. При этом второй вызов будет (вполне корректно!) заблокирован до завершения первого вызова. Еще раз щелкните на кнопке Delayed Deposit
в первом потоке и сразу же — на кнопке Get Owner во втором потоке. В этом случае вы получите немедленный ответ без блокировки вызова (что также корректно, поскольку используемые данные независимы). Рассмотрим самые важные фрагменты кода данного проекта. Рис. 13.11. При обращении к независимым данным блокировки от- сутствуют Клиент: инициализация СОМ Клиент выбирает необходимую модель потоков при инициализации СОМ. void CTestDlg::Onlnitialize () { UpdateData(); switch (m_model) { case ID_MODEL_APARTMENT: g_dwCoInit = COINIT_APARTMENTTHREADED; break; case ID_MODEL_FREE: case ID_MODEL_FREENOCS: g_dwCoInit = COINIT_MULTITHREADED; break; default: break; } HRESULT hr = CoInitializeEx(NULL, g_dwCoInit); Клиент: создание объекта Сервер реализует три различных СОМ-класса, иллюстрируя различные пути обработки потоков (апартаменты, свободные потоки и свободные потоки без критических разделов кода). Клиент создает объект, принадлежащий соответствующему классу, в зависимости от выбора пользователя. CLSID clsid; switch (mjnodel) { case ID_MODEL_APARTMENT:
clsid = CLSIDJBankApt; break; case ID_MODEL_FREE: clsid = CLSID_BankFree; break; case ID_MODEL_FREENOCS: clsid = CLSID_BankNoCS; break; default: break; } hr = CoCreatelnstance(clsid, NULL, CLSCTX_SERVER, IID_IBank, (void**) &m_pBank); Сервер: классы C++ Для каждого СОМ-класса сервер имеет отдельные реализующие классы C++. Единственное отличие в объявлении классов состоит в аргументах шаблонов CComObjectRootEx И CComCoClass. class ATL_NO_VTABLE CBankApt : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CBankApt, &CLSID_BankApt>, public ISupportErrorlnfo, public IBank class ATL_NO_VTABLE CBankFree : public CComObjectRootEx<CComMultiThreadModel>, public CComCoClass<CBankFree, &CLSID_BankFree>, public ISupportErrorlnfo, public IBank class ATL_NO_VTABLE CBankNoCS : public CComObjectRootEx<CComMultiThreadModel>, public CComCoClass<CBankNoCS, &CLSID__BankNoCS>, public ISupportErrorlnfo, public IBank Сервер: реализация взаимоисключений В случае свободных потоков мы должны быть предельно внимательны при реализ а- ции взаимоисключающего доступа к разделяемым данным. Имеются разделяемые данные (такие, как счетчики ссылок), реализуемые ATL, и совместно используемые данные (баланс и владелец), обеспечить корректный доступ к которым должен программист. ATL обеспечивает взаимоисключения в своем коде. Параметр шаблона CComMultiThreadModel, передаваемый в CComObjectRootEx, заставляет ATL реализовать iunknown безопасным с точки зрения многопоточности способом. Мы же отвечаем за безопасность своего кода. Наш класс CBankFree вполне безопасен, в то время как CBankNoCS — нет.
Критические участки кода Для того чтобы наш код был безопасен, мы можем создать критический раздел для каждого доступа к разделяемым данным. Заблокируйте критический раздел перед обращением к данным, и разблокируйте его по окончании работы. STDMETHODIMP CBankFree::Deposit(long amount) { m_cs.Lock(); g_balance += amount; m_cs.Unlock(); return S_OK; } ATL предоставляет класс CComAutoCriticalSection, который упрощает реализацию критических разделов. class ATL_NO_VTABLE CBankFree : public CComObjectRootEx<CCornMultiThreadModel>, public CComCoClass<CBankFree, &CLSID_BankFree>, public ISupportErrorlnfo, public IBank { protected: static CComAutoCriticalSection m_cs; Резюме Многопоточные объекты COM требуют для обеспечения максимальной параллельности вычислений аккуратной работы с потоками. Стандартный маршалинг СОМ упорядочивает доступ к объектам, предотвращая условия гонки без предъявления каких-либо требований безопасности. В случае апартаментной модели потоков методы СОМ упорядочиваются системой. Для маршалинга интерфейсов между потоками одного и того же процесса можно использовать API-функции СОМ. Модель свободных потоков преодолевает ограничения апартаментной модели потоков в достижении параллельности вычислений ценой требований к обеспечению программистом безопасности методов COM. ATL упрощает реализацию объектов СОМ в модели свободных потоков, предоставляя в распоряжение программистов безопасную реализацию интерфейса IUnknown. Класс CComAutoCriticalSection упрощает использование критических разделов кода. На этом завершается наше рассмотрение основ технологии СОМ, являющейся фундаментом СОМ+. В следующей главе мы приступим к изучению СОМ+. Мы увидим, что "декларативный" стиль программирования, используемый СОМ для работы с многопоточностью, широко применяется в СОМ+. А в главе 16, "Параллельные вычисления в СОМ+" мы узнаем, каким образом СОМ+ еще больше упрощает параллельность вычислений.
ЧАСТЬ III Windows DNA и COM+ 14 Основы архитектуры СОМ+ 270 15 Урок СОМ+ 288 16 Параллельные вычисления в СОМ+ 309 17 Windows 2000 и безопасность СОМ+ 315 18 SQL Server и ADO 345 19 Транзакции в СОМ+ 371 20 Использование СОМ+ в Web-приложениях 388 21 Microsoft Message Queue 424 22 События СОМ+ 444 23 СОМ+ и масштабируемость 459 Третья часть книги представляет собой введение в архитектуру и сервисы СОМ+. Здесь рассказывается об ее использовании при создании многоуровневых распределенных приложений, соответствующих модели Windows DNA. Краткое изложение основ архитектуры СОМ+ дополняется примерами учебных программ с применением СОМ+, включающими настройку и распространение распределенных приложений. Обсуждаются вопросы параллельных вычислений в СОМ+, а также вопросы безопасности, включающие важную информацию о безопасности и администрировании в NT. "Краткий курс" включает описание основных технологий программирования баз данных, в том числе SQL Server, OLE DB и ActiveX Data Objects. Базовые концепции транзакций обсуждаются в контексте их реализации СОМ+. Рассматриваются такие вопросы, как разработка Web-приложений с использованием СОМ+, Microsoft Message Queue и компоненты, интегрирующие MSMQ в программную модель СОМ+, модель событий СОМ+. Наконец, технологии масштабирования приложений, включая такие компоненты, как баланс загрузки и объединение объектов.
Глава 14 Основы архитектуры СОМ+ Мы переходим к детальному изучению архитектуры и обеспечиваемых СОМ+ сервисов. В этой главе приведен обзор концепций архитектуры СОМ+, и хорошее понимание этого материала совершенно необходимо для эффективной работы с сервисами СОМ+. (В следующей главе мы перейдем к практическому изучению СОМ+, и, вероятно, будет небесполезно после ее прочтения вновь вернуться к этой главе, перечитать и переосмыслить ее материал заново, чтобы убедиться, что ваше понимание основ СОМ+ позволяет вам приступить к детальному изучению СОМ+.) Итак, начнем мы с ответа на вопрос: "Что такое СОМ+?" Затем поговорим о построении распределенных приложений После этого обсудим базовые принципы декларативного программирования, основанного на атрибутах. В программной модели, работающей в СОМ+, вы можете, определяя соответствующие атрибуты, перехватывать сервисы СОМ+ без написания сложного кода, в котором наделать множество ошибок — сущий пустяк. Обзор архитектуры СОМ+ начинается с приложений СОМ+. Приложения СОМ+ представляют собой высший уровень иерархии, включающий в себя компоненты и классы, интерфейсы и методы. Приложения являются единицами администрирования и распространения в СОМ+. В основе модели программирования с применением атрибутов находится концепция контекста. Контекст — это описание среды, в которой должен работать объект СОМ+. Определяя атрибут, вы определяете один из аспектов контекста. Поскольку атрибуты в СОМ+ гораздо богаче, чем в СОМ, системный реестр расширен системным каталогом, представляющим собой всеобъемлющее хранилище информации об атрибутах. Когда пользователь запрашивает особый контекст у используемого объекта, система должна обеспечить процесс, называемый перехватом. Для облегчения разработки масштабируемых компонентов среднего уровня в модели СОМ+ время жизни объекта у клиента и у сервера трактуется по- разному. Так, клиент может предпочесть долгоживущий объект, чтобы уменьшить падение производительности из-за постоянного создания и уничтожения объекта. Однако может случиться так, что у сервера будет просто не хватать ресурсов для одновременной поддержки большого количества объектов. В этом случае на помощь приходит активизируемая архитектура и атрибут "активизации по необходимости" — при этом объект может быть деактивизирован при остающейся корректной клиентской ссылке на него. Объединение объектов в пул позволяет серверу уменьшить затраты на создание часто используемых объектов путем получения их из пула вместо постоянного создания и уничтожения.
Основания для выбора СОМ+ В части II, "Основы СОМ" был приведен достаточно детальный обзор основ технологии создания и использования компонентов СОМ, без знания которых невозможно построить распределенные приложения с применением СОМ+. В части I, "Введение в СОМ+ и Windows DNA" была рассмотрена задача построения распределенных приложений и архитектура Windows DNA, представляющая собой проект Microsoft для создания многоуровневых приложений. Мы не будем пытаться добавить что-либо к этим обзорам. При желании вы можете вернуться к главам 1, "Что такое СОМ+" и 2, "Трехуровневые приложения и Windows DNA", чтобы освежить в памяти материал о распределенных приложениях. В этом открывающем новую тему разделе мне бы хотелось рассмотреть несколько специфичных технических проблем, связанных с созданием серверных приложений среднего уровня. Эти проблемы, которые можно условно разделить на три группы — масштабируемость, надежность и сложность, и объясняют архитектуру и возможности СОМ+. Масштабируемость За требованиями к масштабируемости СОМ+ стоит Internet. Мне кажется, что если бы Microsoft не рвалась с такой силой к успеху в Internet, COM+ никогда бы не существовала в том виде, в котором она есть сегодня. Для того чтобы увидеть, почему вопрос Internet настолько критичен, рассмотрим приложение уровня предприятия, используемое большим, но определенным количеством пользователей, и Web-приложение с совершенно неизвестным количеством пользователей. И если в первом случае можно каким-либо образом оценить развитие предприятия и изменение количества пользователей, и, соответственно, стоимость необходимых действий, то что делать в случае Web? Компания создала узел Web, который оказался настолько хорош и посещаем, что пробиться на него стало просто невозможно. Причем наплыв пользователей столь высок, что система не просто слишком медленно работает, но рушится под такой нагрузкой. То, что должно было стать триумфом, становится провалом. Это — реальный сценарий жизненной ситуации, повторяющийся, увы, слишком часто. Какое же может быть решение данного вопроса? Купить еще несколько компьютеров? Сколько же именно их надо? Один? Десять? Сто? Цена решения в бизнесе и г- рает огромную роль, и проблема не может быть разрешена только установкой дополнительного программного обеспечения. Решение проблемы должно включать и более эффективное программное обеспечение, работающее на недорогом оборудовании, таком как персональные компьютеры, что не могло не привлечь в эту область Microsoft, с ее исключительным чутьем того, где можно "сорвать" деньги. В этой главе мы увидим, что СОМ+ позволяет в определенной мере помочь в решении проблемы масштабируемости, предлагая такие технологии, как активизация по необходимости, пул объектов и баланс загрузки. Надежность Приложение, обслуживающее множество пользователей — неважно, сотрудников ли огромного предприятия или посетителей Web-узла, — просто не может быть ненадежно, не имеет права на аварийный останов или потерю данных. В больших системах имеется большое количество источников сбоев, например, таких как проблемы с аппаратным обеспечением, неприятности со связью, ошибки настройки систем и многое другое. Все сбои такого рода не должны приводить к сбоям в работе системы или потере жизненно важной информации.
Другим аспектом надежности является минимизация последствий программных ошибок. Цена программной ошибки в большой распределенной системе многократно превышает цену ошибки в настольной системе, работающей на одном компьютере. И, к сожалению, вероятность первых из-за сложности программного обеспечения для распределенных систем существенно выше, чем вторых. СОМ+ поддерживает разработку надежных систем несколькими различными путями. Она предоставляет огромное количество хорошо протестированного, испытанного кода, который вам не надо реализовывать самостоятельно. СОМ+ поддерживает транзакции, которые являются основой для надежной и долговременной работы модулей в распределенной среде. СОМ+ поддерживает балансировку загрузки компонентов среди кластеров приложений, обеспечивая возможность восстановления приложений при сбое одного узла кластера путем передачи работы другому узлу. Сложность Очень большой проблемой в современных распределенных системах является их сложность. Кроме того, что сложность является питательной средой для компьютерных "жучков", сложность разрабатываемых приложений приводит к высокой цене и большим срокам разработки. Новые технологии, помогающие в решении других проблем, приводят к необходимости изучения большего количества функций API. В результате создается впечатление, что этот процесс никогда не придет к окончанию, и новые решения старых проблем приводят только к возникновению новых проблем. Однако СОМ+ действительно упрощает модель программирования. И если бы даже СОМ+ не привносила ничего (по сравнению с СОМ), кроме новой модели программирования, то и тогда она являлась бы значительным шагом вперед. Например, СОМ+ поддерживает автоматические транзакции, которые обеспечивают реализацию распределенных транзакций без написания специфического кода приложения. СОМ+ обеспечивает простую модель безопасности и поддерживает очень простую синхронизацию параллельных вычислений. Транзакции: классический пример О транзакциях мы упоминали уже не раз. Транзакции можно рассматривать как ключевой принцип построения мощных распределенных приложений. Они обеспечивают как средство разделения сложной работы на простые логические единицы, так и механизм, гарантирующий корректность функционирования системы в целом вне зависимости от неизбежных ошибок индивидуальных компонентов. Транзакции будут более подробно рассматриваться в главе 19, "Транзакции в СОМ+". В этой же главе мы будем использовать транзакции как пример поддержки СОМ+ реализации распределенных приложений. Транзакции можно рассматривать как единицы работы. Транзакция должна удовлетворять четырем фундаментальным требованиям, известным под названием ACID- свойств (ACID — аббревиатура от Atomic, Consistent, Isolated, Durable). ■ Атомарность. Транзакция представляет собой "все или ничего". ■ Согласованность. Транзакция не должна нарушать никаких ограничений целостности. ■ Изолированность. Параллельные транзакции не должны влиять одна на другую. ■ Надежность. Любые изменения, внесенные успешно завершенной транзакцией, становятся постоянными.
Простым примером транзакции может служить перенос суммы с одного счета на другой. Такая операция должна быть атомарна — нельзя прервать выполнение операции, сняв деньги с одного счета и не положив на другой. Современные СУБД обеспечивают механизм транзакций в самой базе данных. Вы сможете группировать серию операций в одну транзакцию. Если все операции успешны, транзакция принимается и изменения становятся постоянными. Если какая-то из операций сбоит, транзакция отменяется и база данных возвращается в исходное состояние. Сложнее дело обстоит с распределенными транзакциями, в которых операции применяются к различным хранилищам данных, которые могут относиться к различным типам и находиться на разных компьютерах. Декларативное программирование с использованием атрибутов Сердцем подхода СОМ+ к программированию является декларативная модель, основанная на значениях атрибутов. У такого подхода имеется множество достоинств, основным из которых является простота использования программистом. Гораздо проще объявить значение того или иного атрибута, чем написать сложный код, который так часто приводит к появлению ошибок в программе. Этот подход снижает сложность программного обеспечения. Не меньшее значение имеет и тот факт, что код, предоставляемый операционной системой, более мощен и надежен, чем код, который вы могли бы написать за реально обозримое время. Апартаменты как модель декларативного программирования Хорошим примером использования атрибутов для определения функциональности может служить архитектура апартаментов СОМ, упрощающая написание безопасного параллельно выполняющегося кода без явной реализации в нем синхронизации. Такой код достаточно сложно написать без ошибок, так что немалым достоинством СОМ является то, что эту задачу она решает вместо вас. Однопоточные апартаменты Рассмотрим вкратце, каким образом можно достичь автоматической синхронизации доступа к классам при их создании внутри однопоточного апартамента (STA). Вы объявляете ваш класс как имеющий апартаментную модель потоков, присвоив значение "Apartment" поименованной величине с ключом ThreadingModel. Это означает, что, когда создается экземпляр объекта класса, он будет расположен внутри собственного STA. В этом STA может выполняться только один поток, и если клиент работает в этом потоке, он будет выполнять метод интерфейса непосредственно. Если другой поток попытается получить параллельный доступ в тот момент, когда поток-владелец обращается к объекту, то такой запрос на доступ к объекту будет передан через про- кси в соответствии с правилами СОМ. Прокси использует очередь сообщений Windows, что автоматически обеспечивает синхронизацию (второй поток будет ожидать завершения работы первого). Такая архитектура обеспечивает синхронизацию без реализации ее вашим приложением.
Требования объявления транзакций СОМ+ идет в направлении программирования, базирующегося на атрибутах, гораздо дальше (в этой книге вы найдете множество соответствующих примеров). Давайте рассмотрим, как можно объявить атрибуты транзакций компонента. Предположим, что у вас есть компонент, реализующий операции с банковским счетом, а также, что информация о состоянии банковского счета хранится в базе данных, например, такой как SQL Server, который может обеспечить защиту транзакций. В таком случае вы можете пометить атрибут "Transaction Support" компонента как "Required". Когда ваш компьютер приступает к работе, система времени выполнения СОМ+ определяет, что ваш компонент требует транзакции, и код, известный как "перехватчик", выполняет необходимые действия. Когда будут выполняться операции со счетом, это будет происходить в пределах транзакции. В результате гарантируется, что либо обе операции будут успешно завершены, либо обе не будут выполнены — без написания вами ни единой строчки кода. Каталог СОМ+ Стандартный СОМ хранит всю конфигурационную информацию в системном реестре. СОМ+ требует значительно большей конфигурационной информации для хранения всех атрибутов приложений и компонентов СОМ+. Вся эта информация хранится в отдельной системной базе данных под названием RegDb. Эта база данных недоступна непосредственно. Доступ к ней осуществляется через семейство системных объектов, известных в под названием "менеджер каталога" (Catalog Manager), или просто "каталог" (Catalog). Каталог обеспечивает доступ как к RegDb, так и к системному реестру, как показано на рис. 14 1. Каталог ^ < \ Системный реестр RegDb Рис. 141 Каталог предоставляет объекты для доступа к системному реестру и RegDb Административные объекты каталога доступны из программ и сценариев, т.е. административные процедуры СОМ+ полностью автоматизированы. Объекты каталога образуют иерархическую структуру На вершине иерархии находится компонент coMAdminCatalog, а для доступа к приложениям, компонентам, интерфейсам и методам используются коллекции. Сервисы компонентов Windows 2000 предоставляет пользователю элемент Microsoft Management Console, который позволяет работать с каталогом. Этот инструмент иногда называют СОМ+ Explorer, хотя официальное его название — Component Services. Вы можете просмотреть информацию, хранящуюся в каталоге для приложений СОМ+, входящих в поставку Windows 2000. Вызовите инструмент с помощью команды меню Start^Programs^Administrative Tools ^Component Services. В окне просмотра информации (в виде дерева) раскройте Component Services, Computers, My Computer и СОМ+ Applications Здесь вы увидите установленные в настоящий момент приложения СОМ+ (Windows 2000 поставляется с рядом предустановленных приложений СОМ+) (рис. 14.2)
Рис. 14.2. СОМ+ Explorer показывает установленные на вашем компьютере приложения СОМ+ Вы можете просмотреть иерархию, открывая узлы интересующих вас приложений и компонентов. Взглянем, например, на System Application. Пиктограммы компонентов находятся в правой части окна, а в левой части — интерфейсы компонентов и методы каждого интерфейса (рис. 14.3). Рис. 14.3. Просмотр иерархии приложений в СОМ+ Explorer С помощью СОМ+ Explorer вы можете установить атрибуты приложений, а разворачивая дерево, — и атрибуты компонентов приложений, их интерфейсов и методов. Просматривать и устанавливать атрибуты можно с помощью диалогового окна Properties соответствующего элемента. В следующей главе мы выполним некоторую работу по установке атрибутов, а пока просто посмотрим, какие же из них имеет один из компонентов
System Application, "Catsrv.CatalogServer.1". Щелкнем на нем правой кнопкой мыши и выберем в контекстном меню пункт Properties. Вы увидите диалоговое окно с несколькими вкладками с группами атрибутов компонента, которые могут быть настроены. Обратите внимание на то, что многие из атрибутов неактивны — поскольку вы не можете изменять атрибуты встроенного System Application. Откройте на вкладку Transaction — как видно из рис. 14.4, атрибут Transaction Support имеет значение Required. Рис. 14.4. Просмотр атрибутов компонента СОМ+ Терминология СОМ+ Теперь мы приступим к детальному рассмотрению архитектуры СОМ+ и увидим, в чем состоит вклад СОМ+ в решение описанных в начале этой главы проблем. Однако первое, что мы сделаем — рассмотрим терминологию. Этот вопрос гораздо более важен, чем может показаться на первый взгляд, и прежде чем говорить о чем-то, стоит согласовать значения ключевых терминов предстоящего разговора. Терминология СОМ СОМ обеспечивает иерархию сервер/класс/интерфейс/метод. Класс в СОМ — это конкретная реализация одного или нескольких абстрактных типов данных, представле н- ных интерфейсами. Интерфейс имеет один или несколько методов, являющихся функциями, предоставляемыми интерфейсом. Методы в совокупности с описанием их параметров и возвращаемых значений определяют "контракт", представляющий интерфейс. Объект является экземпляром класса и может иметь свои собственные уникальные данные экземпляра. Такие данные принадлежат только данному объекту и могут быть доступны только опосредованно — через методы. Апартаменты группируют объекты, которые разделяют одни и те же требования к параллельным вычислениям. Апартамент, в котором располагается объект, представляет собой неотъемлемую часть объекта. В то время как класс, интерфейс и метод представляют логическую структуру, высший элемент иерархии СОМ — сервер — является чисто физическим контейнером, содержащим бинарный код одного или нескольких классов. Сервер может быть либо ЕХЕ, либо DLL.
Терминология СОМ+ СОМ+ поддерживает концепции класса, объекта, интерфейса, метода и апартамента. Концепция сервера в СОМ+ менее важна, чем в СОМ. В СОМ+ все классы должны быть реализованы на DLL-сервере. Если вам требуется ЕХЕ-сервер, СОМ+ будет запускать компоненты в стандартном "суррогате" dllhost.exe. Приложение В СОМ+ вводится понятие приложения. Хотя, по общему мнению, этот термин чрезвычайно перегружен, в СОМ+ он имеет специальное значение. Приложение представляет собой группу классов, разделяющих некоторое множество атрибутов безопасности, активизации и т.п. Таким образом, базовая иерархия СОМ+ выглядит как приложение/класс/интерфейс/метод. Компонент Большинство терминов СОМ/СОМ + используются достаточно точно и последовательно, что, увы, нельзя сказать о термине "компонент". Этот термин широко применяется, и в целом представляет собой более практичную и повторно используемую сущность, чем объект. В то время как объектно-ориентированное программирование все еще достаточно далеко от максимального повторного использования объектов, практические результаты по промышленному применению компонентов более обещающие. Специализированный тип компонентов СОМ — управляющие элементы ActiveX — приняты "программистскими массами", и имеются тысячи разработанных сторонними производителями управляющих элементов ActiveX. Еще один тип компонента, основанный на другой технологии — Java- Beans, — по-видимому, также станет общепринятым. В этом смысле компонент обычно рассматривается как повторно используемая часть бинарного программного обеспечения. В соответствии с таким определением простая DLL также должна рассматриваться как компонент — как видите, на деле компонент означает нечто большее. Однако точное определение этого "нечто" остается предметом обсуждения. В Microsoft Platform SDK компонент описывается как бинарный модуль кода, который создает бинарный объект, включающий код саморегистрации. Однако это определение ближе к определению класса. Технически класс СОМ может быть "голым классом", как мы видели в главе 5, "C++ и СОМ" в нашем первом примере реализации СОМ-протокола на C++ (lUnknown), и не иметь фабрики классов. Компонент же должен иметь инфраструктуру для создания объектов (DllGetClassObject и фабрику классов) и быть способен к саморегистрации (DllRegisterServer и DllUnregisterServer). Заметьте, что часть этой инфраструктуры предоставлена DLL в целом, а не отдельным классом. DLL может реализовывать несколько классов. Ключевой момент состоит в том, что компоненты однозначно соответствуют классам и для каждого компонента имеется единственный класс со своим CLSID, ProgID и прочим, и соответствующая запись в системном реестре. Полный набор требований к компонентам в СОМ+ выглядит следующим образом. 1. Компонент представляет собой СОМ класс, реализующий интерфейс lUnknown. 2. Компонент реализуется в DLL и имеет инфраструктуру для создания объектов, включая CLSID, фабрику классов и экспортируемые DLL функции DllGetClassObject И DllCanUnloadNow. 3. Компонент способен к саморегистрации, с поддержкой DllRegisterServer и DllUnregisterServer.
4. Компонент должен предоставлять библиотеку типов, связанную с DLL. 5. Компонент должен иметь возможность обеспечивать маршалинг своих интерфейсов. Маршалинг двойных интерфейсов выполняется посредством автоматизации. Вы должны поставить и зарегистрировать прокси/заглушки для пользовательских интерфейсов. Каждый класс в ActiveX DLL, построенной с помощью Visual Basic, автоматически соответствует этим требованиям. То же справедливо и для классов, созданных с применением ATL. Если ваш класс имеет пользовательские интерфейсы, вы должны создать и зарегистрировать прокси/заглушки. Установка компонентов Компонент, удовлетворяющий перечисленным требованиям, способен участвовать в сервисах СОМ+, но на самом деле вначале его необходимо установить в приложение СОМ+. Компонент может быть добавлен в приложение либо с помощью административного инструментария, либо программно с применением административного API. После того, как компонент был инсталлирован в приложение, он становится сконфигурированным компонентом (configured component). Компонент, не инсталлированный в приложение СОМ+, называется ^сконфигурированным (unconfigured). Типы приложений СОМ+ Главный тип приложений СОМ+ — приложение сервера. Приложение сервера работает в своем собственном процессе стандартного суррогата dllhost.exe. Приложение сервера обеспечивает изоляцию ошибок, так что аварийный останов сервера не приведет к краху клиентов, так как клиент и сервер работают в разных адресных пространствах. Доступ к приложению сервера клиента на другой машине получается с помощью DCOM. Приложение сервера обеспечивает полный диапазон сервисов перехвата. Библиотечное приложение работает в пределах процесса клиента. Таким образом, изоляции ошибок у этого приложения нет; кроме того, оно обладает только ограниченным множеством сервисов перехвата. Доступ к библиотечному приложению может получить только клиент, работающий на той же машине, что и приложение. Основное применение библиотечных приложений — предоставление повторно используемого кода другим приложениям СОМ+, а не предоставление сервисов непосредственно клиентам. Прокси-приложение содержит информацию, которая требуется удаленному клиенту для доступа к приложению сервера. Прокси-приложение предоставляет файл, который может быть запущен на машине клиента для занесения необходимой информации в системный реестр, включая CLSID, имя удаленного сервера и информацию о маршалинге. И, наконец, имеется ряд предустановленных приложений СОМ+. Они включают системные приложения СОМ+ и IIS и ряд утилит. Эти приложения не могут быть изменены или удалены. Архитектура СОМ+ СОМ+ держится на трех китах, олицетворяющих базовые концепции. Это — контекст, активизация и перехват. СОМ+ использует механизм перехвата для автоматического вызова определенных сервисов от имени приложения. В этой главе мы рассмотрим основные концепции и два сервиса — активизацию по необходимости и пул объектов. В следующих главах мы обсудим дополнительные сервисы, такие как синхронизация, безопасность, автоматические транзакции, очереди, свободно связанные события и балансировка загрузки.
Вначале мы рассмотрим общую архитектуру, а затем (подробно) — основные концепции и сервисы. Фундаментальным принципом СОМ+ является декларативное, программирование, основанное на атрибутах. Программист описывает среду, в которой должны работать компоненты, объявляя конфигурационные атрибуты компонента. Эта информация хранится в каталоге. При создании объекта СОМ+ просматривает каталог и определяет, является ли создаваемый компонент сконфигурированным. Если компонент имеется в каталоге, он создается и размещается в контексте, соответствующем конфигурационным установкам компонента. Связывание объекта с его контекстом называется активизацией. Если клиент компонента СОМ+ находится в контексте, отличном от контекста компонента, СОМ+ использует перехват, для того чтобы обеспечить совместную работу несовместимых компонентов. Если компонент и клиент имеют идентичные требования времени выполнения, компонент создается в том же контексте, что и клиент, и необходимость в перехвате отпадает. Если компонент в каталоге отсутствует, он размещается в контексте для ^сконфигурированных компонентов. Контекст Контекст представляет собой коллекцию объектов в пределах апартамента, имеющих одинаковые требования времени выполнения. Каждый СОМ-объект, который должен быть активизирован, связывается ровно с одним контекстом, а контекст располагается в точности в одном апартаменте. Апартамент может содержать несколько контекстов, а контекст может иметь несколько объектов. Объекты в разных апартаментах обязательно имеют разный контекст. Иллюстрацию описанных взаимоотношений объектов, контекстов и апартаментов вы можете найти на рис. 14.5. Апартамент Контекст Объект Апартамент Контекст Объект Объект Контекст Объект Рис. 14.5. Взаимоотношения объектов, контекстов и апартаментов
Каждый апартамент имеет контекст по умолчанию. Когда активизируется нескон- фигурированный компонент, он связывается с контекстом по умолчанию, и этот контекст большей частью игнорируется. Для сконфигурированного компонента свойства контекста будут определяться значениями атрибутов компонента, хранящимися в каталоге (а также, возможно, атрибутами "родительского" компонента, как поясняется ниже). Контекст сконфигурированного компонента предоставляет ключ для доступа к сервисам СОМ+. Объект контекста Контекст представляет собой абстракцию, определяющую группу СОМ-объектов. С контекстом связывается реальный СОМ-объект, носящий имя "объект контекста" (или "контекст объекта"), который и хранит всю информацию об атрибутах, связанную с данным контекстом (объект контекста представляет собой то, что в MTS называется "оболочкой контекста" (context wrapper)). Код метода может получить доступ к этому объекту контекста с помощью функции API CoGetObjectContext (GetObjectContext в Visual Basic), которая возвращает указатель на интерфейс lObjectContext. Затем вы можете вызвать методы iobjectcontext для работы со свойствами объекта контекста. Интерфейс iobjectcontext перенесен из MTS. В СОМ+ имеются другие, более специализированные интерфейсы, включая IObjectContextlnfo, IContextState и IObjectContextActivity. Контекст вызова Часть контекста, связанная с безопасностью, рассматривается в главе 17, "Windows 2000 и безопасность СОМ+". Функция API CoGetCallContext возвращает указатель на интерфейс iSecurityCallContext, который обеспечивает доступ к методам безопасности и информации о контексте вызова. Активизация Важным в является также то, что контекст описывает "среду" объекта, а не внутренние данные экземпляра объекта (хотя и влияет на поведение объекта). Объект сам по себе не имеет контекста. Процесс связи объекта с контекстом называется активизацией. Главный вопрос для вновь созданного объекта звучит примерно так: "Как меня будут использовать сегодня?" Средой работы объекта является его контекст, и этот контекст определяет поведение объекта. Приведение объекта в состояние "готовности", когда возможен вызов его методов, в СОМ+ является процессом, состоящим из двух шагов. Первый шаг — это создание объекта. Экземпляр объекта создается с помощью фабрики классов, так же как и в обычной СОМ. Затем приходит очередь активизации, когда объект связывается со своим контекстом. Теперь объект готов к работе, а система времени выполнения СОМ+ — к выполнению (при необходимости) дополнительных действий от имени объекта посредством перехвата. Процесс окончания работы также состоит из двух шагов — деактивизации объекта, удаляющей его контекст, и уничтожения объекта. Схематично жизненный цикл объекта в СОМ+ приведен на рис. 14.6. Создание Активизация Деакгивизация Уничтожение Существует Активен Существует, Рис. 14.6. Жизненный цикл объекта в СОМ+
Обычно активизация выполняется сразу после создания объекта, а деактивиза- ция — непосредственно перед его уничтожением. Однако СОМ+ поддерживает пул объектов, в котором могут содержаться созданные объекты. Когда клиенту требуется активный объект, сервер активизирует объект из пула. По окончании работы клиента с объектом сервер не уничтожает объект, а только деактивизирует его и возвращает в пул. Пул объектов рассматривается позже в этой главе. Течение свойств контекста Контекст вновь активизированного объекта определяется атрибутами компонента в каталоге и, возможно, атрибутами "родительского" объекта, если таковой имеется. Если объект создается конечным клиентом непосредственно, контекст будет определяться только атрибутами компонента. Однако, если объект создается другим объектом, ситуация усложняется. Создаваемый объект имеет собственный контекст. При некоторых условиях дочерний объект будет создаваться в контексте его родительского объекта. Это происходит всегда в случае ^сконфигурированных классов, поскольку у них нет независимых средств определения контекста. Для сконфигурированных классов система времени выполнения проверяет, совместим ли контекст родительского объекта с требованиями дочернего объекта. Если они совместимы, дочерний объект будет активизирован в родительском контексте; в противном случае для дочернего объекта будет создан новый контекст. Иногда при создании нового контекста некоторые значения свойств передаются дочернему объекту, чему мы увидим ряд подтверждений. А сейчас просто посмотрим, что может случиться с транзакциями. Родительский объект может иметь атрибут "требуется транзакция". Это означает, что система времени выполнения СОМ+ всегда будет активизировать этот объект в контексте, в котором объект будет работать с применением транзакций; а одним из свойств контекста будет идентификатор транзакции. Теперь предположим, что этот родительский объект создает дочерний объект, который имеет атрибут "поддержка транзакций". Этот новый объект будет активизирован в новом контексте, но будет работать с родительскими транзакциями и, таким образом, "унаследует" идентификатор транзакции. Пример банковского счета Рассмотрим наш пример переноса денег с одного счета на другой, но теперь с использованием транзакций, будем считать, что есть три компонента: Move, Debit и Credit, с методами Transfer, Withdraw и Deposit соответственно. Вот гипотетический код Visual Basic для переноса 25 единиц с одного счета на другой: Dim objMove as New Move objMove.Transfer 25 А ниже приведен код метода Transfer. Обратите внимание на то, что пересылка денег осуществляется созданием двух дочерних объектов и вызовом их методов. Public Sub Transfer(ByVal Amount as Currency) Dim objDebit as New Debit Dim objCredit as New Credit objDebit.Withdraw Amount objCredit.Deposit Amount End Sub Теперь рассмотрим воздействие на поведение компонента различных конфигурационных установок транзакций. Имеются три возможные установки, которые описаны ниже
■ Поддержка транзакций. Компонент будет работать в транзакции его клиента. Если клиент не работает с транзакциями, компонент также не работает с ними. ■ Требуется транзакция. Компонент всегда будет работать в транзакции. Если клиент работает в транзакции, компонент будет работать в той же транзакции; если клиент не работает в транзакции, для компонента будет создана новая транзакция. ■ Требуется новая транзакция. Компонент всегда будет работать в собственной, специально созданной для него транзакции. Итак, предположим, что атрибуты транзакции сконфигурированы так, как предполагалось ранее. Родительский объект Move "требует транзакцию", а дочерние объекты Debit и Credit "поддерживают транзакцию". Тогда Move будет активизирован в контексте, в котором требуется транзакция, и система времени выполнения СОМ+ проделает необходимую работу по запуску транзакции и получит идентификатор транзакции, скажем, tidl. Когда активизируются дочерние объекты Debit и Credit, они также запускаются в контексте транзакции, более того, это будет та же транзакция, так что свойство "идентификатор транзакции" tidl перейдет в их контекст. Затем, если они успешно завершат свою работу, то они сообщат об этом, и вся транзакция в целом будет успешно завершена, как атомарная операция. Если же хотя бы один из объектов сообщит о неудачном завершении, транзакция будет отвергнута целиком. Теперь предположим, что Move остается с атрибутом "требуется транзакция", а Debit и Credit имеют атрибут "требуется новая транзакция". Теперь все три объекта работают в пределах транзакций, но дочерние объекты работают в разных транзакциях, например, таких как tid2 и tid3. В итоге результат работы объектов Debit и Credit никак не влияет на работу объекта Move, и требуемая атомарность операции Transfer гарантирована быть не может. Свойство "идентификатор транзакции" не переходит в этом случае от родителя к потомку. Принудительная активизация в контексте вызова Сконфигурированный класс может установить значение атрибута таким образом, он всегда будет создаваться в родительском контексте. Это может оказаться необходимым, если, например, не все интерфейсы класса будут подвергнуты маршалингу. В таком случае либо создание объекта пройдет успешно, и родительский класс может непосредственно "общаться" с дочерним, либо создание объекта завершается неудачно, возвратив соответствующий код ошибки. Перехват Когда объект активизируется в контексте, вызов его методов из контекста обрабатывается не так, как вызов методов извне. Вызов одного объекта другим в пределах одного и того же контекста не требует никакого вмешательства СОМ+ и осуществляется непосредственно. Объекты в разных контекстах имеют определенную несовместимость их сред выполнения, а следовательно, вызовы, осуществляемые через границы контекстов, требуют перехвата СОМ+ для уменьшения несовместимости. На рис. 14.7 показан апартамент с двумя контекстами, А и В. Контекст А имеет два объекта, 1 и 2, контекст В — один объект, 3. Вызов объектом 1 объекта 2 осуществляется внутри одного и того же контекста и выполняется непосредственно, без вмешательства СОМ+. Вызов же объекта 2 объектом 3 пересекает границы контекста и, следовательно, требует вмешательства СОМ+ посредством перехватчика.
Контекст А Контекст В 1 2 Перехватчик 3 Рис. 14.7. Вызов, пересекающий границы контекста, требует использования перехватчика Перехватчики Перехватчик представляет собой облегченный прокси, предоставляемый системой времени выполнения СОМ+ для разрешения несовместимости при вызовах через границы контекстов. Перехватчик предъявляет те же интерфейсы, что и целевой объект. Для объекта вызов посредством перехватчика вместо объекта совершенно прозр а- чен. Перехватчик выполняет предварительную обработку на пути вызова к объекту, и последующую — при возврате к вызывающему объекту. Перехватчик назван "облегченным", поскольку он не должен включать изменения потоков. Он выполняет только то, что необходимо для разрешения несовместимостей и ничего более. Обычный же прокси включает в себя перемену потоков, а возможно, и перемену процессов. Механизм перехвата СОМ+ — общего назначения. Он может применяться для преодоления множества различных типов несовместимостей контекстов. В СОМ+ 1.0 перехватчики предоставляются только СОМ+, но не приложениями. В дальнейшем такое положение дел может измениться. Маршалинг указателей на интерфейсы Если вы передаете указатель на интерфейс в качестве параметра в вызове метода, пересекающем границу контекста, СОМ+ автоматически конвертирует указатель на интерфейс для прокси, и когда вы возвращаетесь в контекст, проходите через перехватчик. Если вы используете другой вид разделяемого указателя на интерфейс, например, храните его в глобальной переменной, то становитесь ответственными за его маршалинг. Точно такой же вопрос мы рассматривали в главе 13, "Многопоточность в СОМ", при обсуждении передачи указателей на интерфейсы через границы апартаментов. Тот же код маршалинга, что был использован при пересечении границ апартаментов, применим и при пересечении границ контекста.
Активизация по необходимости Компонент может иметь атрибут, разрешающий активизацию по необходимости (just-in-time activation — JIT). Когда такая активизация разрешена, система времени выполнения СОМ+ может вызывать у клиента иллюзию того, что объект существует и клиент может поддерживать долгоживущие ссылки на объект, в то время как на самом деле сервер деактивизирует (а то и вовсе удаляет) объект для сохранения ресурсов. Когда те клиент вызывает метод с применением этой ссылки, сервер по необходимости активизирует (или создает и активизирует) нужный объект. Эта особенность СОМ+ вносит свой вклад в дело масштабируемости приложений. Рассмотрим обычную ситуацию. Клиент обращается к базе данных посредство DCOM-подключения к объекту среднего уровня, который, в свою очередь, связывается с базой данных. Подключения к базе данных — быстро исчерпывающийся и дорогой ресурс. Поэтому клиенту следует вовремя производить отключение от сервера среднего уровня, для того чтобы ненужный объект уничтожался и освобождал подключение к базе данных. Но создание и уничтожение объектов на сервере среднего уровня — тоже дорогое удовольствие, но в этом случае причина кроется в интенсивном обмене информацией по сети посредством DCOM. JIT предоставляет элегантное решение. Клиент может поддерживать объект на сервере среднего уровня столько, сколько ему необходимо. Сервер же среднего уровня посредством СОМ+ будет деак- тивизировать подключенный к базе данных объект по завершении вызова метода и вновь активизировать его по приходу очередного запроса. Помимо того что ЛТ обеспечивает высокую масштабируемость, она упрощает модель программирования распределенных приложений. Клиент не должен беспокоиться о поддержании долгоживущих ссылок на объекты, и его разработка при этом упрощается. В то же время сервер и СОМ+ обеспечивают хорошее управление ресурсами. В СОМ+ ваш компонент реализуется как DLL, которая работает в процессе стандартного суррогата dllhost . ехе. Этот суррогат выставляет те же интерфейсы, что и ваш компонент, и клиент обращается к суррогату. Суррогат поддерживает подключения ко всем клиентам и создает/активизирует реальные объекты при необходимости (рис. 14.8). DLLHOST.EXE Клиент Объект Рис. 14.8. Клиент поддерживает ссылку на объект, активизируемый сервером по требованию Ограничения масштабируемости Хотя ЛТ может увеличить масштабируемость, вы не должны рассматривать этот метод как панацею. При деактивизации объекта ресурсы, связанные с объектом, освобождаются, но dllhost.exe поддерживает у клиента иллюзию, что объект активен. Это означает, что менеджер заглушек СОМ и заглушки интерфейсов остаются в памяти до тех пор, пока клиент не освободит прокси. Тем самым для каждого клиента расходуется несколько сотен байтов оперативной памяти.
Бит "сделано" СОМ+ поддерживает в контексте объекта бит "сделано" (done). При работе с JIT, когда этот бит установлен, система времени выполнения СОМ+ деактивизирует объект. Этот бит может быть установлен несколькими различными способами. 1. Получите указатель на интерфейс icontextstate и вызовите SetDeactivateOnReturn. Это — новый механизм, предоставляемый СОМ+; он влияет только на описанный бит. 2. Получите указатель на интерфейс lObjectContext и вызовите SetComplete или SetAbort. Этот метод использовался в MTS и устанавливает также бит завершенности транзакции, указывающий, успешно или нет завершена транзакция. 3. Административно установите свойство метода "auto done". Тогда после выполнения метода бит будет устанавливаться автоматически. При этом бит завершенности транзакции будет устанавливаться на основании возвращаемого методом значения hresult. Свойство "auto done" метода устанавливается с помощью переключателя Automatically deactivate the object when this method returns. Подключение к активизации и деактивизации объекта Вы можете подключиться к активизации и деактивизации объекта, реализовав интерфейс lObjectControl в вашем классе. Методы Activate и Deactivate этого интерфейса будут вызываться в процессе активизации и деактивизации объекта соответственно. Интерфейс lObjectControl имеет также метод CanBePooled, возвращающий TRUE или FALSE в зависимости от того, поддерживает или нет ваш объект размещение в пуле объектов. Состояние компонентов СОМ+ Традиционно объекты управляют как состоянием, так и поведением. Состояние хранится как данные экземпляра, а поведение реализуется посредством методов. Обычная СОМ полностью согласуется с этой моделью, и многие классы СОМ содержат закрытые данные экземпляров. Требования к высокомасштабируемым компонентам среднего уровня изменили эту картину. Состояние, хранящееся в переменных экземпляра, не в может пережить де- активизацию объекта. Цель деактивизации — позволить системе восстановить ресурсы, чтобы одновременно могло работать большее число клиентов. Таким образом, когда вызывается SetComplete или SetAbort, все данные экземпляра исчезают. В частности, данные экземпляра не сохраняются между транзакциями. Это требование важно для свойства "I" (изоляция) ACID-свойств транзакции. При необходимости состояние может поддерживаться различными средствами. Состояние может храниться клиентом и передаваться серверу как параметр в вызове метода. Сервер может хранить состояние в разделяемой памяти (COM+ предоставляет сервис, именуемый менеджером разделяемых свойств (Shared Property Manager — SPM), который поддерживает эту возможность). И, наконец, наиболее типичный выход состоит в хранении состояния объекта в базе данных. Пул объектов и их конструирование Активизация по необходимости увеличивает масштабируемость сервера, позволяя ему управлять огромным числом подключений клиентов без поддержки всех экземпляров объектов в активном состоянии. Недостаток этой технологии состоит в постоянном создании и уничтожении объектов. Если создание таких объектов в контексте
процесса dllhost.exe влечет за собой малые накладные расходы, проблемы могут и не возникнуть. Но если накладные расходы велики, может потребоваться поиск лучшего решения. Кроме того, важно также, чтобы реинициализация была простой. Пул объектов COM+ обеспечивает автоматический сервис, который поддерживает пул экземпляров объектов компонента. При желании некоторые объекты могут быть созданы при инициализации компонента. Когда клиентам не хватает объектов, создаются и добавляются в пул дополнительные объекты — вплоть до максимального размера пула. Когда объект деактивизируется, он не уничтожается, а возвращается в пул объектов. При активизации объект не создается заново, а выбирается из пула и реинициализи- руется. Тем самым снижаются накладные расходы на создание и уничтожение объектов. Экземпляры объектов, таким образом, могут использоваться повторно. Подробнее о пуле объектов рассказывается в главе 23, "СОМ+ и масштабируемость". Конструирование объектов Одна из слабых сторон СОМ — отсутствие возможности передавать новому объекту информацию как часть процесса его создания. В противоположность этому конструктор C++ может принимать параметры, использующиеся для инициализации вновь созданного объекта. COM+ обеспечивает такую возможность посредством интерфейса lObjectConstruct. Если вы реализуете этот интерфейс в вашем классе, то при создании объекта вызывается метод Construct этого интерфейса с передачей указателя на интерфейс iobjectconstructstring. Вы вызываете метод get_ConstructString для получения строки, в которой может содержаться некоторая полезная информация, используемая при конструировании объекта. Строка может быть указана во вкладке Activation диалогового окна Properties компонента, как показано на рис. 14.9. Хорошим примером использования строки конструктора может служить указание строки подключения к базе данных. Рис. 14.9. Определение строки конструктора компонента
Резюме СОМ+ была создана для решения некоторых важных вопросов программирования приложений уровня крупных предприятий. Наиболее важным из них является вопрос масштабируемости. Для того чтобы построить приложение, которое могло бы работать с очень большим количеством пользователей одновременно, как, например, в случае пр и- ложения, работающего в Internet, требуется изменить модель программирования. Средний уровень должен иметь возможность управлять временем жизни объектов и време н- но освобождать занятые объектами ресурсы, когда последние не используются, и вновь отдавать их объекту, когда тот в них нуждается. СОМ+ достигает этого с помощью модели активизации по необходимости, которая автоматически обеспечивается инфраструктурой времени выполнения СОМ+. Дополнительный выигрыш в масштабируемости можно получить для некоторых компонентов, если разместить объе кты в пуле. Другим важным вопросом создания приложений уровня предприятия являются надежность и преодоление сложности приложений. Обе проблемы СОМ+ решает путем использования декларативной модели программирования, основанной на атрибутах объектов. В СОМ конфигурационная информация хранилась в системном реестре. СОМ+ в силу сложности и многообразия сконфигурационных установок хранит эту информацию с специальной системной базе данных, называемой каталогом. Компоненты СОМ конфигурируются для работы с СОМ+ импортированием их в приложения СОМ+. После этого атрибуты могут быть уточнены и изменены с помощью инструмента администрирования Component Services (COM+ Explorer) либо предоставляемого административного API. В процессе работы экземпляры объектов создаются в контексте, который отражает их условия работы, основанные на значениях атрибутов в каталоге. Любой вызов сконфигурированного объекта СОМ+ должен принимать во внимание контекст; а для достижения совместимости вызов может осуществляться посредством перехватчика. Перехватчик представляет собой облегченный прокси, который выполняет всю необходимую работу по удовлетворению требований объекта. В этом процессе "участвуют", т.е. автоматически вызываются, системные сервисы СОМ+. Такая модель существенно упрощает работу программиста, поскольку, конечно же, указать атрибуты гораздо легче, чем написать код. Модель также повышает надежность, поскольку использует многократно проверенный системный код. В этой главе были рассмотрены основные архитектурные концепции СОМ+ и основные особенности программной модели СОМ+. Возможно, эта глава вам показалась несколько теоретичной, но уже в следующей главе, посвященной основным сервисам СОМ+, вы встретитесь со множеством конкретных примеров.
Глава 15 Урок СОМ+ В предыдущей главе мы обсудили некоторые детали основ архитектуры СОМ+, но в этой главе были представлены только теоретические сведения, без выполнения каких-либо практических действий. В данной главе мы приступаем к работе в среде СОМ+. Здесь мы познакомимся с инструментарием и программными технологиями. Примеры программ, представленные в этой главе, должны помочь вам лучше понять архитектуру СОМ+. По окончании работы с данной главой, возможно, имеет смысл перечитать главу 14, Основы архитектуры СОМ+", и уже имея определенные навыки и знания, продолжить изучения книги. Наш урок СОМ+ разбит на четыре основные части. В первой мы покажем, как инсталлировать компонент СОМ, созданный с помощью Visual Basic, в приложение СОМ+. Затем мы добавим немного специфичного для СОМ+ кода для иллюстрации некоторых возможностей архитектуры СОМ+, таких, например, как активизация по необходимости. Мы также проведем несколько экспериментов с СОМ+ Explorer — испытаем различные настройки атрибутов. Затем мы поработаем с СОМ-компонентом на C++ с аналогичной функциональностью и проведем новые эксперименты (включая получение строки конструктора для инициализации объекта). Как в случае Visual Basic, так и в случае C++ мы будем выводить сообщения в журнальный файл. Кроме того, мы рассмотрим объекты системного администрирования и изучим пример простой программы, получающей информацию из каталога. И, наконец, мы узнаем, каким образом удаленно размещать приложения СОМ+. Компонент СОМ+ на Visual Basic Наш первый пример — компонент банковского счета на Visual Basic (см. главу 6, "СОМ-серверы контекста приложения"). Мы модифицируем его таким образом, чтобы не выводилось никаких окон сообщений. Вместо этого мы воспользуемся разработанным ранее компонентом logger для вывода информации в журнальный файл. (Вообще говоря, вы не должны выводить никаких окон, в том числе окон сообщений, в серверных компонентах. Серверный компонент может работать на другой машине, и рядом может никого не оказаться, а значит, закрыть окно будет некому. Если же сервер не запущен как интерактивный, окно не будет выведено, и ситуация ухудшается — метод выводит окно сообщения, которое не отображается на экране, а значит, закрыть его невозможно, и в результате сервер становится неработоспособным.)
Путеводитель Первый пример достаточно длинный, поэтому, я так думаю, будет уместным составить небольшой путеводитель. Представьте себе нашу работу, как прогулку со множеством остановок. Вся прогулка состоит из трех больших шагов, на их протяжении, мы сделаем семь остановок. По дороге не забывайте, что в СОМ+ многое делается с помощью конфигурации, и один и тот же код может вести себя совершенно по-разному при различных настройках. 1. Мы начнем с работы с ^сконфигурированным СОМ-компонентом. Он схож с нашим примером банковского счета из части II, "Основы СОМ", но метод IDisplay: : Show вместо вывода окна сообщения записывает информацию в файл (причины такого поведения были изложены выше). 2. Затем мы инсталлируем компонент в приложение СОМ+. Здесь мы не увидим нового поведения — это всего лишь пример использования инструментария СОМ+. 3. Мы пройдемся по различным атрибутам, с которыми можем работать. Это не займет у нас много времени. Цель этой остановки — познакомиться с тем, какое количество атрибутов хранится в каталоге, не пытаясь пока что понять, за что отвечает каждый из них. Об этом речь пойдет в следующих главах книги. 4. После этого перейдем к практическому изучению вопросов активизации и состояния. Здесь мы поближе рассмотрим активизацию по необходимости и применим код Visual Basic для использования объекта контекста. 5. Тут мы изменим конфигурацию, отключив ЛТ, и посмотрим, что из этого получится. 6. Затем мы напишем небольшой код на Visual Basic, реализующий интерфейс lObjectControl, для подключения к процессам активизации и деактивизации. 7. И, наконец, посмотрим, как влияет на поведение установка свойства "auto done". Приятной прогулки! №1. Несконфигурированный компонент Перед тем как испытывать наш компонент в СОМ+, посмотрим, как поведет себя несконфигурированный компонент. Пока что это СОМ, а не СОМ+. Использование Logger Мы можем использовать разработанный в главе 12, "Обработка ошибок и отладка" компонент Logger. Мы воспользуемся простым классом Visual Basic LogVb с единственным методом Writeln, который получает строковый аргумент. Информация записывается в жестко прошитый файл c:\logfile.txt. Всякий раз при вызове Writeln переданная методу строка добавляется в конец файла. Если вы хотите начать с пустого файла, — просто удалите его, и он вновь будет создан при первом же вызове Writeln. Для того чтобы ознакомиться с содержимым файла, достаточно просто дважды щелкнуть на нем. Тестовую программу вы можете найти в каталоге Chapl5\LogVb.
Тестирование компонента банковского счета Сервер находится в каталоге chapl5\BankVbPlus\Stepl, а клиент— в каталоге Chapl5\BankClientVb. Зарегистрируйте сервер, запустив на выполнение файл reg_BankVbPlus.bat. Соберите клиент (команда меню File^Make) и запустите его (BankVbClient.exe). Вы должны увидеть тестовое окно, выглядящее так, как показано на рис. 15.1. Щелкните на кнопке Create для создания объекта, сделайте два вклада, ознакомьтесь с состоянием счета, снимите деньги со счета. Вновь просмотрите состояние счета и закройте приложение. После этого просмотрите файл c:\logfile.txt. В нем должна быть примерно следующая запись: Account object created Balance is 250 (IDisplay::Show) Balance is 225 (IDisplay::Show) Account object destroyed Рис. 15.1. Тестовая программа для объекта банковского счета №2. Сконфигурированный компонент Теперь инсталлируем наш компонент банковского счета в каталог, тем самым сделав его сконфигурированным компонентом, имеющим различные установки атрибутов. Перед тем как приступить к работе с тестовой программой, давайте рассмотрим ряд атрибутов. Создание пустого приложения СОМ+ Вызовите СОМ+ Explorer с помощью команды меню Start^Programs^AdminJstrative Tools^Component Services. Откройте дерево просмотра Computers^My Computerc£>COM+ Applications. Выберите СОМ+ Applications, а затем щелкните на нем правой кнопкой мыши. В контекстном меню выберите команду New=>Application. При этом будет вызван COM Application Install Wizard. Щелкните на кнопке Next, затем — на Create empty application. Назначьте ему имя BankVbPlusApp и согласитесь с установками по умолчанию для Server Application (рис. 15.2). Щелкните на кнопке Next. В следующем окне согласитесь с предлагаемой по умолчанию установкой Interactive user. Позже, при обсуждении вопросов безопасности, мы увидим, что стоит указывать конкретного пользователя для запуска приложения. Щелкните на кнопке Next, а затем — на кнопке Finish. Вы создали новое (пустое) приложение COM+, в чем можно убедиться, воспользовавшись COM+ Explorer. Установка нового компонента Вы увидите, что к дереву просмотра добавилось новое приложение BankVbPlusApp. Раскройте его, щелкнув на расположенном рядом знаке "+". Вызовите COM Component Install Wizard, щелкнув правой кнопки на Components и выбрав
в контекстном меню пункт NewoComponent. Щелкните на кнопке Next. Перед вами появится диалоговое окно, позволяющее выбрать установку нового компонента, импорт существующего или установку класса события (рис. 15.3). Рис. 15.2. Создание нового серверного приложения СОМ+ Рис. 15.3. Диалоговое окно установки нового компонента Представленные варианты выбора могут ввести вас в заблуждение. При работе с ^сконфигурированным компонентом вы зарегистрировали его в системном реестре, поэтому, возможно, вы решите, что следует воспользоваться кнопкой Import component(s) that are already registered. Это не самая хорошая идея, так как при этом полное конфигурирование компонента не выполняется, в частности не инсталлируется информация об интерфейсах и методах. Третью кнопку мы рассмотрим в главе 22, "События СОМ+", при изучении событий, а сейчас наш выбор — Install new component(s).
Щелкните на этой кнопке, затем в диалоговом окне Select files to install перейдите в каталог Chapl5\BankVbPlus\Stepl и выберите файл BankVbPlus.dll. Щелкните на кнопке Open. Теперь вы увидите список файлов и компонентов. В нашем случае должен быть один файл и два компонента (Account и iDisplay), как показано на рис. 15.4. Щелкните на кнопке Next, а затем — на кнопке Finish. Рис. 15.4. Диалоговое окно выбора инсталлируемых компонентов (На самом деле второй "компонент" — это следствие реализации СОМ классов в Visual Basic. Поскольку Visual Basic непосредственно интерфейсы не поддерживает, второй интерфейс, IDisplay, реализован как отдельный класс Visual Basic. Этот класс ничего не делает и соответствующий компонент может быть удален из приложения СОМ+.) Использование технологии "перетащить и отпустить" Для инсталляции компонента можно воспользоваться другим методом, с использованием технологии "перетащить и отпустить". Откройте папку Components в COM+ Explorer и отдельное окно Windows Explorer, в котором будет виден ваш DLL-файл. При выбранной в левой части окна COM+ Explorer папке Components перетащите вашу DLL в правую часть окна. Инсталляция и регистрация Если вы инсталлировали компонент, используя Component Install Wizard или технологию "перетащить и отпустить", значит, вся конфигурационная информация находится под контролем СОМ-К Если вы удаляете компонент из приложения СОМ+, удаляется как информация об атрибутах СОМ+, так и информация, содержащаяся в системном реестре, — компонент становится незарегистрированным. Если вы импортируете предварительно зарегистрированный компонент, а затем удаляете его из приложения СОМ+, компонент остается зарегистрированным. Как упоминалось выше, рекомендуется всегда инсталлировать ваши компоненты в приложение СОМ+, для того чтобы они были полностью сконфигурированы. Компонент СОМ может быть инсталлирован в одно приложение СОМ+. Если вы попытаетесь инсталлировать его в другое приложение, вы получите сообщение об ошибке, в котором говорится, что компонент уже зарегистрирован. Вы не можете исправить положе-
ние, дерегистрировав компонент. Если вы попытаетесь это сделать, когда ваш компонент остается инсталлированным в приложении СОМ+, то получите сообщение о невозможности загрузить библиотеку типов при попытке инсталляции во второе приложение. Все, что можно сделать — так это удалить компонент СОМ из первого приложения. Просмотр компонентов в СОМ+ Explorer Просмотреть установленные компоненты можно с помощью СОМ+ Explorer. Щелкнув на кнопке Finish в Component Install Wizard, вы должны увидеть новые компоненты, представленные шарами на правой панели (для изменения вида представления компонентов можно воспользоваться меню View. Шары соответствуют выбору Large icon). Удалите ненужный компонент, соответствующий интерфейсу I Display, после чего у вас останется один компонент, соответствующий классу Account, как показано на рис. 15.5. Рис. 15.5. Новый компонент инсталлирован в приложение СОМ+ Запуск приложения СОМ+ Теперь вы сможете использовать компонент как и раньше, но теперь уже — как сконфигурированный компонент, инсталлированный в приложение СОМ+. Вызовите программу-клиент BankClientVb.exe и расположите окна таким образом, чтобы одновременно видеть и окно клиента, и соответствующий компоненту шар в СОМ+ Explorer. Щелкните на кнопке Create, чтобы создать новый экземпляр объекта банковского счета. Шар при этом должен начать вращаться. Выполните операции, связанные с кнопками Deposit, Withdraw и Show, и убедитесь, что в файле c:\logfile.txt появились соответствующие записи. По окончании работы щелкните на кнопке Destroy. Шарик прекратит вращаться. Закройте приложение-клиент. Теперь откройте проект сервера Chapl5\BankVbPlus\Stepl\BankVbPlus. vbp. Попытайтесь заново построить DLL (с помощью команды меню File^Make BankVb- Plus.dll). Вы получите сообщение об ошибке, в котором говорится о том, что доступ к DLL запрещен. Попытайтесь самостоятельно разобраться, что произошло (прежде чем читать главу дальше).
№3. Экскурсия по атрибутам Теперь у нас есть сконфигурированный компонент в приложении СОМ+. Вспомним иерархию приложение/компонент/интерфейс/метод. На каждом уровне иерархии может быть установлено множество разнообразных атрибутов. Рассмотрим некоторые из них. Такая экскурсия с помощью СОМ+ Explorer, осуществляемая с помощью диалоговых окон Properties, расположенных на каждом уровне иерархии, должна быть достаточно поучительна. Атрибуты приложения Щелкните правой кнопкой мыши на BankVbPlusApp в СОМ+ Explorer и выберите в контекстном меню Properties. Появится диалоговое окно BankVbPlusApp Properties, в котором имеется шесть вкладок для различных типов свойств. Просматривая эти вкладки, вы увидите множество различных установок, относящихся к приложению в целом. Вот некоторые из них. ■ Идентификатор приложения, GUID, используемый в DCOM. ■ Ряд различных настроек безопасности. ■ Учетная запись пользователя, под которой работает приложение (эта информация может быть определена в процессе первоначального создания приложения). ■ Тип активизации библиотеки или сервера (также может быть выбран в процессе первоначального создания приложения). ■ Использование очереди — приложение может быть сконфигурировано для применения MSMQ в качестве транспортного механизма. ■ Информация, используемая при выключении сервера (во вкладке Advanced). Последний пункт имеет отношение к проблеме запрета доступа к DLL при ее перестройке. Вспомним о том, что компонент работает в процессе суррогата DLLHOST.EXE. Вообще говоря, приложение-сервер СОМ+ обслуживает многие клиенты. Если временно клиенты отсутствуют, вероятно, имеет смысл не завершать работу приложения немедленно, а подождать, не появятся ли новые клиенты (и тем самым уменьшить расходы на остановку и запуск сервера). По умолчанию сервер завершает свою работу через три минуты по окончании работы с ним последнего клиента. Эта настройка может быть изменена во вкладке Advanced диалогового окна Properties, показанной на рис. 15.6. Если это вас заинтересовало, можно провести несколько несложных экспериментов. Вы косвенно уже убедились, что процесс продолжает оставаться в памяти — по блокировке DLL. Более непосредственный путь убедиться в этом — использовать Task Manager, вызвать который можно щелчком правой кнопки мыши в свободном месте панели задач. Найдите в окне Task Manager процесс dllhost.exe. (Заметьте, что щелчком на заголовке Image Name можно упорядочить список процессов. Двойной щелчок приведет к сортировке имен процессов в обратном порядке.) Один из этих процессов всегда запущен, и вы не можете прекратить его работу — он служит "хозяином" компонентов встроенного приложения. Если ваше приложение работает, в списке вы увидите два dllhost.exe, как на рис. 15.7 Альтернативой Task Manager служит инструмент Process Viewer, который можно вызвать с помощью команды меню Start^Programs^Microsoft Visual Studio^Microsoft Visual Studio Tools^Process Viewer.
Атрибуты компонента Вы можете открыть диалоговое окно Properties вашего компонента, либо выбрав и щелкнув правой кнопкой мыши на элементе BankVbPlus.Account, расположенном на левой панели, либо щелкнув правой кнопкой мыши на пиктограмме, находящейся на правой панели окна СОМ+ Explorer. В диалоговом окне вы увидите шесть вкладок, отражающих различную конфигурационную информацию. Часть из них предназначены только для чтения, часть можно изменять. Вот некоторые из них. ■ Общая информация, такая как путь к DLL, CLSID и AppID. ■ Настройки транзакций. ■ Настройки безопасности клиента. ■ Установки активизации (включая активизацию JIT). ■ Пул объектов. ■ Конструирование объекта (передача инициализационной строки новому экземпляру компонента). ■ Настройки параллельных вычислений. Рис. 15.6. Вы можете определить, будет ли сервер продолжать работу при отсутствии клиентов Рис. 15.7. Процесс dllhost.exe в окне Task Manager С некоторыми из перечисленных атрибутов мы будем работать позже, а пока продолжим знакомство с атрибутами, спускаясь все ниже и ниже по иерархии. Атрибуты интерфейса Откройте узел Interfaces у BankVbPlus.Account в просмотре дерева в СОМ+ Explorer. Здесь вы должны увидеть два интерфейса: _iAccount и _iDisplay (если вы не видите их, то возможно, в Component Install Wizard неверно выбрали опцию Import component(s) that are already registered (о чем мы говорили ранее). В этом случае удалите ваши компоненты, вернитесь к разделу "Установка нового компонента" (стр. 312) и повторите процедуру инсталляции.)
Откройте диалоговое окно Properties, в этот раз выбрав и щелкнув правой кнопки мыши на интерфейсе _iAccount. Вы увидите три вкладки с настройками конфигурации, включающие: ■ общую информацию, такую как идентификатор интерфейса; ■ свойства очередей, которые не доступны, если очереди не используются приложением; ■ свойства безопасности. Атрибуты метода И, наконец, рассмотрим атрибуты отдельного метода. Откройте папку Methods интерфейса _iAccount, и вы увидите знакомые методы. Теперь в СОМ+ Explorer перед вами (рис. 15.8) — вся иерархия приложение/компонент/интерфейс/метод. Рис. 15.8. Полная иерархия приложение/компонент/интерфейс/метод Щелкните правой кнопкой мыши на методе Deposit и вызовите в контекстном меню Properties. Это диалоговое окно имеет две вкладки с информацией, включающей следующие настройки. ■ Автоматическая деактивация объекта после возврата из метода (свойство "auto done", обсуждавшееся нами ранее). ■ Настройки безопасности отдельного метода. На этом наша экскурсия по атрибутам и настройкам завершена. Как видите, возможности настройки (и количество информации, хранящейся в каталоге) просто огромны — и все они могут использоваться системой времени выполнения СОМ+. №4. Активизация и состояние Версия stepl сервера банковского счета представляет собой обычный СОМ- компонент, в котором явным образом не используется ни один сервис СОМ+. Версия Step2 получает ссылку на объект контекста и передает ее вызову SetComplete после
получения нового баланса. Кроме того, имеется код, записывающий текущий баланс в журнальный файл после каждой транзакции. Работа с этим примером поможет вам лучше понять активизацию в СОМ+ и значение и важность "состояния". Для того чтобы воспользоваться активизацией по необходимости, мы будем хранить состояние вне компонента. Программирование контекста в Visual Basic В Visual Basic работа с объектом контекста выполняется очень просто. Первое, что вы должны сделать — это добавить ссылку на СОМ+ Services Type Library (меню Projects References (рис. 15.9)). Вспомните, что для взаимодействия с компонентами с использованием имен классов и методов Visual Basic требует соответствующую библиотеку типов. Рис. 15.9. Добавление ссылки на СОМ+ Services Type Library После этого в вашем коде Visual Basic вы можете получить ссылку на объект контекста, вызвав функцию GetObjectContext. Посредством этой ссылки вы можете вызывать методы, такие, например, как SetComplete. Далее приведен код методов Deposit и Withdraw из проекта Chapl5\BankVbPlus\Step2. Обратите внимание на вызов SetComplete после вклада, но не после снятия суммы. Мы преднамеренно по разному программируем этих два метода, чтобы показать воздействие деактивизации. Public Sub Deposit(ByVal amount As Long) gBalance = gBalance + amount objLog.Writeln "Balance is " & gBalance & _ " After Deposit" Dim objCtx As ObjectContext Set objCtx = GetObjectContext objCtx.SetComplete End Sub Public Sub Withdraw(ByVal amount As Long) gBalance = gBalance - amount objLog.Writeln "Balance is " & gBalance & _ " After Withdraw" End Sub
Активизация и деактивизация в процессе работы Теперь попытайтесь представить, что случится, если в приложение СОМ+ BankVbPlusApp вы инсталлируете этот компонент вместо компонента версии stepl. Что произойдет после выполнения ряда вкладов и снятий со счета? Для того чтобы увидеть, что произойдет, вначале удалите старый компонент из приложения, а затем инсталлируйте новый компонент. Теперь запустите программу-клиент. Что получается? Снятие со счета работает нормально, зато вкладывание кажется "приклеенным" к стартовому значению 200. Что же происходит? Для большей конкретности рассмотрим журнальный файл. Для его получения удалите файл c:\logfile.txt, а затем выполните следующие действия: снятие, снятие, снятие, за которыми следуют вклад, вклад, вклад. Вот как после этого должен выглядеть ваш журнальный файл. Account object created Balance is 175 After Withdraw Balance is 150 After Withdraw Balance is 125 After Withdraw Balance is 150 After Deposit Account object destroyed Account object created Balance is 225 After Deposit Account object destroyed Account object created Balance is 225 After Deposit Account object destroyed Account object created Account object destroyed Все нормально работает в случае вызова withdraw. Однако после каждого вызова Deposit объект уничтожается, и при вызове метода Balance он создается заново, для того чтобы отобразить новое состояние счета в диалоговом окне. Каждый раз при создании объект инициализируется стартовым значением счета — 200, — что и создает впечатление "приклеенности" к этой величине. При вызове SetComplete объект деактивизируется. Если объект находился в пуле, он не должен уничтожаться, а должен быть просто помещен в пул. Но так как мы не работаем с пулом, объект в действительности уничтожается. (Но даже если объект помещается в пул, его состояние не сохраняется. Нельзя ничего сказать о том, что будет с объектом в следующий момент времени, так что нельзя хранить текущий баланс в объекте. Журнальный файл при этом может выглядеть несколько иначе, поскольку объект уничтожается не сразу после деактивации.) Заметьте, однако, что программа-клиент поддерживает долгоживущую ссылку на объект, которая получается при щелчке на кнопке Create. To есть, клиент думает, что это — ссылка на объект, хотя реального объекта в тот момент не существует. Однако как только клиент вызывает метод (в нашем случае свойство Balance), сервер тут же создает реальный объект, так что вызов завершается успешно. Объект активизируется "по необходимости". Но результат получается неверный... Вот почему СОМ+ лучше работает с объектами, "не помнящими родства", т.е. состояния, которое лучше получать из базы данных или какого-то другого источника.
№5. Отмена активизации по необходимости Как мы видели, создание и уничтожение объекта происходит не всегда, а зависит от атрибутов, которые могут быть изменены. В качестве примера отключим атрибут активизации по необходимости, вызвав диалоговое окно Properties компонента BankVbPlus. Account и перейдя во вкладку Activation, где уберем отметку, стоящую возле опции Enable Just In Time Activation, как показано на рис. 15.10. Рис. 15.10. Активизация по необходимости может быть отключена После этого вновь запустите программу-клиент и повторите три снятия со счета, а затем — три вклада. На этот раз мы получим однократное создание объекта в начале работы и уничтожение в конце. Состояние объекта при этом между вызовами методов сохраняется. Вот как при этом выглядит журнальный файл: Account object created Balance is 175 After Withdraw Balance is 150 After Withdraw Balance is 125 After Withdraw Balance is 150 After Deposit Balance is 175 After Deposit Balance is 200 After Deposit Account object destroyed №6. Подключение к активизации и деактивизации на Visual Basic СОМ+ предоставляет интерфейс iobjectcontrol, который класс может реализовать для подключения к процессам активизации и деактивизации. Visual Basic предоставляет класс ObjectControl, который может реализовываться вашим классом Visual Basic. Версия Step3 иллюстрирует реализацию ObjectControl, которая включает реализацию методов Activate, Deactivate и CanBePooled. Реализация первых двух методов просто вносит соответствующую запись в журнальный файл. Третий метод возвращает False. Вот код из этого проекта:
Option Explicit Implements IDisplay Implements ObjectControl Dim objLog As New LogVb Private gBalance As Long Private Sub ObjectControl_Activate() objLog.Writeln "Account object activated" End Sub Private Function ObjectControl_CanBePooled() As Boolean ObjectControl_CanBePooled = False End Function Private Sub ObjectControl_Deactivate() objLog.Writeln "Account object deactivated" End Sub Если мы удалим версию step2 компонента из приложения СОМ+ и инсталлируем компонент версии step3, то увидим, что активизация по необходимости активна вновь (как значение по умолчанию). Запустим программу-клиент и выполним один вклад и пару снятий со счета. В результате получим следующие записи в журнальном файле: Account object created Account object activated Balance is 225 After Deposit Account object deactivated Account object destroyed Account object created Account object activated Balance is 175 After Withdraw Balance is 150 After Withdraw Account object deactivated Account object destroyed Поскольку помещение в пул объектов мы запретили (честно говоря, компоненты, созданные Visual Basic, не могут быть помещены в пул в любом случае, но об этом — позже, в главе 23, "СОМ+ и масштабируемость"), активизация объекта выполняется сразу же после его создания, а сразу же после деактивизации следует уничтожение. №7. Автоматическая деактивация До этого момента мы деактивизировали наш объект явным образом, вызывая SetComplete. COM+ позволяет устанавливать атрибут, который вызывает автоматическую деактивизацию объекта при возврате из метода. Это — атрибут метода. Откройте диалоговое окно Properties для метода Withdraw и отметьте опцию Automatically deactivate this object when this method returns, как показано на рис. 15.11. Теперь вновь запустите программу-клиент. Теперь баланс "приклеивается" к значению 200 как для Deposit, который явно вызывает SetComplete, так и для withdraw с автоматической деактивизацией. В действительности, когда разрешена автоматическая деактивизация объекта, вызывается либо SetComplete, либо SetAbort — в зависимости от того, о чем сообщает метод — об успешном или неудачном завершении. Объект деактивизируется в любом ел у- чае (устанавливается бит "done"). Разница между SetComplete и SetAbort заключается в работе с транзакциями. SetComplete вызывается, если метод сообщает об успешном
завершении транзакции и предлагает принять результат транзакции (устанавливается бит "consistent"), a SetAbort вызывается, когда транзакция должна быть отменена (бит "consistent" сбрасывается). SetComplete и SetAbort более подробно будут рассмотрены при изучении транзакций в главе 19, "Транзакции в СОМ+". Атрибут Automatically deactivate this object when this method returns часто называют просто "auto done". Рис. 15.11. Вы можете установить автоматическую деактивацию объекта для метода Компонент СОМ+ на Visual C++ В этом разделе мы познакомимся с примером на Visual C++, по сути, эквивалентным только что рассмотренному примеру на языке Visual Basic. В этом примере добавлена только одна новая возможность — строка, передаваемая конструктору объекта. Пример на Visual C++ не поддерживает интерфейс iDisplay, и тестовая программа несколько проще. Так же как и в случае Visual Basic, здесь выполняется трассировка работы с записью в файл c:\logfile.txt с помощью компонента Logger. Использовать этот компонент в C++ менее удобно, чем в Visual Basic, поскольку метод Write требует в качестве входного параметра строку bstr. Для работы с компонентом Logger мы разработали класс-оболочку CLogger, который берет все эти мелочи на себя. Полностью проект вы можете найти в каталоге Chapl5\BankPlus. В этом случае у нас не будет множества шагов. Имеется всего три проекта: BankPlus, TestBank и Logger. Компонент Logger уже был реализован в главе 12, "Обработка ошибок и отладка" на Visual C++, и от него зависит проект BankPlus, что можно увидеть с помощью команды меню Project^Dependencies. Обратите внимание на то, что нам потребуется Platform SDK для построения проекта Visual C++ с использованием библиотек Component Services.
Создание программы-примера Хотя мы не будем детально рассматривать весь процесс создания программы- примера, примите все же один практический совет. Версия Visual C++ 6.0, которую я использовал при написании этой книги, не имеет специальных средств поддержки компонентов, работающих с СОМ+. Но вы можете воспользоваться поддержкой MTS и выполнить несколько преобразований для СОМ+ — это все-таки легче, чем создавать вручную весь необходимый код. При создании ATL СОМ-проекта отметьте опцию поддержки MTS. При вставке нового объекта ATL выберите в качестве его типа MTS Transaction Server Component (рис. 15.12). Измените заголовочный файл MTS mtx.h на требуемый СОМ+, а именно — <comsvcs.h>. // AccountPlus.h : Declaration of the CAccountPlus #include "resource.h" // main symbols #include <comsvcs.h> #include "logging.h" Инсталляция и запуск программы Вы уже должны быть хорошо знакомы с процедурами создания приложения СОМ+ и инсталляции компонентов, так что мы не станем повторять детальные инструкции. Создайте новое (пустое) приложение сервера СОМ+ BankPlusApp. Постройте DLL-сервер и тестовую программу. Инсталлируйте BankPlus.dll в новое приложение СОМ+. Теперь вы готовы к запуску простой тестовой программы, показанной на рис. 15.13. Шарик в окне СОМ+ Explorer должен тут же начать вращаться. Вы можете снимать деньги, но вкладывание "приклеено" к начальной сумме 200, поскольку компонент сконфигурирован с активизацией по необходимости, а в конце метода Deposit делается вызов SetComplete. По окончании работы убедитесь в сказанном, просмотрев содержимое файла c:\logfile.txt. Рис. 15.12. Использование поддержки MTS Рис. 15.13. Простая тестовая программа при создании компонента СОМ+ банковского сервера на C++ Интерфейсы IObjectControl и IObjectConstruct Ваш класс должен реализовывать интерфейс IObjectControl. Хитрость состоит в том, что запрос типа MTS Server Component в ATL Object Wizard выполнит работу вместо вас. Вы также должны реализовать интерфейс IObjectConstruct, для под-
держки которого придется добавлять код вручную. Как обычно в ATL, вы должны добавить новые интерфейсы в список предков вашего класса C++. class ATL_NO_VTABLE CAccountPlus : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CAccountPlus, &CLSID_AccountPlus>, public IObjectControl, public IObjectConstruct, public IDispatchImpl<IAccount, &IID_IAccount, &LIBID_BANKPLUSLib> { . . . Вы должны также не забыть добавить эти интерфейсы в карту СОМ, а прототипы методов интерфейсов — в определение класса. BEGIN_COM_MAP(CAccountPlus) COM_INTERFACE_ENTRY(IAccount) COM_INTERFACE_ENTRY(IObjectControl) COM_INTERFACE_ENTRY(IObjectConstruet) COM_INTERFACE_ENTRY(IDispatch) END_COM_MAP() // IObjectControl public: STDMETHOD (Activate) () ; STDMETHOD_(BOOL, CanBePooled)(); STDMETHOD_(void, Deactivate)(); // IObjectConstruct public: STDMETHOD(Construct) (IDispatch * pUnk); Доступ к контексту объекта Стандартный путь доступа к контексту объекта в C++ заключается в наличии члена- указателя m_pObjectContext типа IObjectContext*, который инициализируется при активизации объекта и освобождается при деактивизации. Тогда, если понадобится применить метод интерфейса IOb jectContext, вы можете использовать этот указатель. HRESULT CAccountPlus::Activate() { m_log.Write("Account object activated"); HRESULT hr = GetObjectContext (&m__pObjectContext) ; if (FAILED(hr)) m_log.Writehr("GetObjectContext failed", hr) ; return hr; } BOOL CAccountPlus::CanBePooled() { return FALSE; } void CAccountPlus::Deactivate() { m_log.Write("Account object deactivated"); m_pObjectContext->Release();
} STDMETHODIMP CAccountPlus::Deposit(int amount) { HRESULT hr = S_OK; m_nBalance += amount; if (mjpObjectContext) { hr = m_pObjectContext->SetComplete(); m_log.Writehr("Deposit...SetComplete", hr); } Обратите внимание на то, что семантика этого компонента та же, что и в версии Visual Basic. Мы вызываем SetComplete после вклада, но не после снятия со счета. Таким образом, когда вы запускаете программу-клиент (с ЛТ-активизацией, установленной для сервера по умолчанию), баланс будет "приклеен" к величине 200 при вкладе, но не при снятии со счета. Конструирование объекта Пример на C++ иллюстрирует еще одну дополнительную возможность, не использовавшуюся в примере на Visual Basic, а именно — реализацию интерфейса lObjectConstruct, посредством которого вы можете получить конфигурационную строку, хранящуюся в каталоге. Мы уже рассмотрели стандартный ATL-код для добавления интерфейса к классу. Теперь рассмотрим реализацию одного метода, Construct, который вызывается сразу после того, как объект создан, но еще не активизирован. Метод Construct получает параметр — указатель на интерфейс, который можно использовать для запроса интерфейса iobjectconstructstring. Этот интерфейс имеет метод get_Constructstring, который может быть вызван для получения самой строки. В нашем примере код просто записывает строку в журнальный файл. STDMETHODIMP CAccountPlus::Construct(IDispatch * pUnk) { if (IpUnk) return EJJNEXPECTED; HRESULT hr; IObjectConstructString * pString = NULL; hr = pUnk -> Querylnterface(IID_IObjectConstructString, (void **)&pString); if (FAILED(hr)) { m_log.Writehr("Construct...Querylnterface", hr); return hr; } if (pString) { BSTR bstr; hr = pString->get_ConstructString(&bstr); if (FAILED(hr)) { m_log.Writehr("get_ConstructString", hr);
pString->Release() ; return hr; } char buf[1024]; USES_CONVERSION; wsprintf(buf, "construct string = %s", OLE2CT(bstr)); m_log.Write(buf); } else m_log.Write("null construct string"); pString->Release(); return S_OK; } Настройка строки конструктора Разрешить конструирование объекта и инициализировать строку конструктора можно в диалоговом окне Properties компонента. Находясь в нем, перейдите во вкладку Activation, отметьте опцию Enable object construction и введите требуемую строку конструктора, как показано на рис. 15.14. Рис. 15.14. Разрешение конструирования объекта и определение строки конструктора Запуск программы После внесения этих изменений в настройки приготовьтесь к новому запуску тестовой программы. Удалите журнальный файл и запустите программу. Сделайте один вклад и выйдите из программы. Просмотрите содержимое журнального файла — оно должно быть примерно таким, как показано ниже. Заметьте, что в связи с JIT- активизацией объект уничтожается сразу же по завершении работы метода Deposit (из-за явного вызова SetComplete) и создается заново при вызове GetBalance. Строка конструктора передается всякий раз при создании объекта.
Account object created construct string = Hello object construction! Account object activated After Deposit, balance = 225 Account object deactivated Account object destroyed Account object created construct string = Hello object construction! Account object activated Account object deactivated Account object destroyed Административные объекты СОМ+ COM+ предоставляет простую в использовании иерархию административных объектов для работы со сконфигурированными СОМ-классами. СОМ+ Explorer просто обеспечивает пользовательский интерфейс для объектов, находящихся на вершине этой иерархии. Все, что можно сделать с помощью COM4- Explorer, можно выполнить и программно — посредством административных объектов. Подробную информацию по этому вопросу вы найдете в разделе "Automating СОМ+ Administration" документации Platform SDK. Простая демонстрационная программа Chapl5\MyAdmin иллюстрирует использование этих объектов для получения списка всех инсталлированных в системе приложений СОМ+. При запуске этой программы вы увидите список приложений СОМ+ (рис. 15.15). Для использования этих объектов вы должны добавить ссылку на библиотеку СОМ+ 1.0 Admin Type Library (рис. 15.16). Рис. 15.15. Вывод списка приложе- Рис. 15.16. Добавление ссылки на СОМ+ 1.0 Ad- ний СОМ+ с использованием адми- min Type Library нистративных объектов Сам по себе код очень прост. Вы создаете корневой объект COMAdminCatalog и получаете коллекцию Applications с помощью метода GetCollection. Затем заполняете коллекцию и итерируете ее с помощью цикла For Each. Dim Catalog As New COMAdminCatalog Dim Applications As COMAdminCatalogCollection
Private Sub cmdShow_Click() Set Applications = Catalog.GetCollection("Applications") Applications.Populate Dim app As COMAdminCatalogObject For Each app In Applications Listl.Addltem app.Name Next app End Sub Вся настройка, осуществляемая с помощью Component Services, может быть выполнена с использованием административных объектов — включая инсталляцию приложения и настройки атрибутов. Удаленное размещение приложений СОМ+ Последняя тема, о которой хотелось упомянуть в нашем несколько затянувшемся уроке, — это размещение приложений СОМ+ для удаленной работы. Этот процесс значительно проще, чем при работе с обычной DCOM. Мы экспортируем наше простое приложение BankPlusApp. Выберите и щелкните правой кнопкой мыши на BankPlusApp в дереве просмотра СОМ+ Explorer, затем выберите в контекстном меню Export. В запущенном мастере выберите каталог Deploy в рабочем каталоге книги ComPlus. Назначьте имя BankPlus создаваемому файлу приложения. Следует использовать расширение .msi, применяемое Windows 2000 Installer. Выберите опцию Application proxy, как показано на рис. 15.17. Мы хотим обеспечить возможность клиентской программе на компьютере №2 работать с сервером на компьютере №1. Рис. 15.17. Создание прокси-приложения для удаленного размещения клиента Щелкните на кнопке Next, а затем — на кнопке Finish. Теперь скопируйте файлы BankPlus.msi и TestBank.exe в тестовый каталог компьютера №2. Попытайтесь запустить тестовую программу на компьютере №2 — она не будет работать. Теперь дважды щелкните на файле BankPlus .msi — запустится Windows 2000 Installer, и на компьютере №2 будет инсталлирован код прокси. Этот код позволяет клиенту удаленно вызывать сервер. Попытайтесь запустить тестовую программу еще раз — теперь она должна работать.
Подобно любому другому корректному программному обеспечению Windows, приложение прокси может быть деинсталлировано. Откройте Add/Remove Programs в Control Panel — здесь вы можете выбрать прокси и удалить его (рис. 15.18). Рис. 15.18. Приложение прокси может быть автоматически удалено с машины клиента Резюме Основным инструментом при работе с СОМ+ является Component Services, или просто — "СОМ+ Explorer", который может использоваться для создания нового приложения СОМ+, инсталляции компонентов, установки атрибутов и экспортирования компонентов, чтобы они могли быть запущены удаленно. Атрибуты могут быть установлены на всех уровнях иерархии — на уровнях приложения, компонента, интерфейса и метода. Мы рассмотрели четыре примера. Первый из них — наш старый знакомый — банковский компонент на Visual Basic. Мы продемонстрировали работу активизации СОМ+, включая вопросы состояния, показали, как подключиться к процессам активизации и деактивизации с помощью реализации интерфейса iobjectcontrol (ObjectControl в Visual Basic). Версия на Visual C++ добавила к функциональности передачу строки для конструктора нового объекта посредством интерфейса iobjectconstruct. Третий пример проиллюстрировал применение системных административных объектов, а последний — то, как разместить приложение, чтобы оно могло работать удаленно. В следующей главе мы обсудим параллельные вычисления в СОМ+ и увидим, как можно достичь синхронизации, объявляя атрибуты.
Глава 16 Параллельные вычисления вСОМ+ В главе 14, "Основы архитектуры СОМ+ " мы узнали, каким образом потоковая архитектура СОМ посредством информации, хранящейся в системном реестре, обеспечивает модель декларативного программирования (основанного на атрибутах), аналогичную модели программирования СОМ+ Как пересечение границ апартаментов в СОМ требует синхронизации, так и пересечение границ контекста в СОМ+ приводит к вызову перехватчика. В этой главе мы рассмотрим, каким образом в СОМ+ осуществляются параллельные вычисления. Апартаменты в СОМ+ менее важны, чем в СОМ, и не являются главной технологией для обеспечения синхронизации. СОМ+ для дальнейшего упрощения предоставляет атрибут синхронизации и новый, более гибкий "нейтральный" тип апартамента, призванный служить в качестве самой распространенной модели потоков для классов СОМ+. СОМ+ определяет также новую абстракцию, именуемую активностью, которая используется для группирования контекстов, требующих параллельности вычислений. Мы обсудим эти концепции и рассмотрим примеры программ. Синхронизация и апартаменты Классическая СОМ основана на абстракции, именуемой апартаментами, которая работает со всеми вопросами, связанными с многопоточ- ностью. Апартаменты и многопоточность СОМ обсуждались нами в главе 13, "Многопоточность в СОМ". Если вы не уверены в ясности своих представлений в этом вопросе, возможно, имеет смысл вернуться к указанной главе перед продолжением изучения. В этом разделе мы рассмотрим базовые концепции апартаментов СОМ и некоторые вопросы мно- гопоточности. Затем мы обратимся к новому типу апартамента — "нейтральному", — который был введен в СОМ+ и является предпочтительной моделью многопоточности для новых компонентов в COM4-. Нейтральные апартаменты не обеспечивают никакой поддержки синхронизации — они просто "уходят в сторону", обеспечивая возможность работы нового механизма синхронизации СОМ+, основанного на активности и не привносящего накладных расходов. Многопоточное приложение банковского счета Для конкретизации нашего рассмотрения мы будем использовать пример кода. Нашим примером будет многопоточный банковский сервер из главы 13, "Многопоточность в СОМ". Класс BankMtPlus поддержи-
вает интерфейс IBank, обладающий такими важными методами, как Deposit, DelayDeposit и GetBalance. У нас есть два потока. В первом потоке мы вызываем DelayDeposit, который выполняет следующее. 1. Получает значение баланса из разделяемой памяти. 2. Увеличивает баланс в локальной памяти. 3. Выполняет задержку в выполнении. 4. Сохраняет новое значение баланса в разделяемой памяти. В то время, когда поток 1 находится в неактивном состоянии (выполняет задержку из пункта 3), включается поток 2 и выполняет быстрый вклад (посредством вызова метода Deposit, который просто увеличивает величину баланса в разделяемой памяти без каких- либо задержек). Когда задержка заканчивается поток 1 продолжает работу, он сохраняет новое значение баланса в разделяемой памяти, перезаписывая значение, сохраненной м е- тодом Deposit во втором потоке. Таким образом, вклад, осуществленный вторым потоком, теряется. Рассмотрим следующий конкретный сценарий условий гонки. 1. Начальное значение баланса — 100. 2. Поток 1 вносит вклад в размере 25, и прерывается после получения текущего значения баланса и выполнения операции сложения, но до сохранения результата. 3. Поток 2 вносит на счет 5000 и сохраняет новое значение баланса — 5100. 4. Поток 1 завершает свою работу, сохраняя значение 125 и теряя при этом те 5000, которые были внесены вторым потоком. Синхронизация посредством апартаментов В обычном СОМ имеется два варианта решения вопроса, оба проиллюстрированные в главе 13, "Многопоточность в СОМ". Первое решение состоит в использовании апартаментной модели потоков. Каждый поток работает в своем собственном однопо- точном апартаменте (STA). Каждый поток обращается к классу банковского счета посредством указателя на интерфейс. Поскольку поток 2 работает в другом апартаменте, указатель на интерфейс, используемый потоком 1, должен быть "маршализован" в поток 2. Тогда, когда поток 2 осуществит вызов класса банковского счета, он пойдет через прокси, который обеспечит переключение потоков и ожидание завершения работы вызова из первого потока, устранив тем самым условия гонки. Во втором решении оба потока работают в многопоточных апартаментах (МТА). Поскольку оба потока находятся в одном апартаменте, они используют один и тот же указатель на интерфейс, без участия прокси. Следовательно, в этом случае СОМ не обеспечивает автоматической синхронизации. Соответствующий синхронизирующий код должен находиться в самом классе банковского счета для предотвращения условия гонки (например, путем использования критических участков кода). Нейтральные апартаменты Имеются определенные слабые стороны, связанные с производительностью, при работе как в МТА, так и в STA. Использование МТА и выполнение вами всей работы по синхронизации, увы, не гарантирует высокой производительности, поскольку вызов, пришедший извне МТА, вызовет обмен потоков. В случае STA любой вызов также будет вызывать обмен потоков.
СОМ+ предоставляет третий тип апартаментов, который не вызывает обмена потоков. Он называется нейтральным апартаментом. Объект в нейтральном апартаменте (Neutral apartment — NA) всегда работает в потоке, вызвавшем его. Процесс может иметь один нейтральный апартамент. Класс определяет модель нейтрального апартамента посредством установки значения ThreadingModel в системном реестре равным Neutral. После этого все экземпляры объектов класса создаются в NA. В отличие от МТА и STA, никакой поток не может считать NA "своим" — флаг COINIT при вызове CoInitializeEx остается либо COINIT_SINGLETHREADED, либо coinit_multithreaded, так что поток "живет" в некотором МТА или STA, в зависимости от использованного флага, и "наносит визит" в NA при работе с экземпляром класса, определившего нейтральную модель, — но обмена потоков при этом не происходит. Синхронизация и активность Многопоточность — одна из самых проблемных областей СОМ. Даже введение в Windows NT 4.0 МТА не облегчило задачу поиска компромисса между высокой производительностью и сложностью программирования. СОМ+ вводит модель синхронизации, основанную на атрибутах и не опирающуюся на апартаменты. Апартаменты продолжают существовать в СОМ+, и контексты физически располагаются в апартаментах, но последние больше не рассматриваются как часть логической программной модели. Вы объявляете, что ваш компонент имеет нейтральную модель и при необходимости определяете требуемые атрибуты синхронизации. В этом разделе вы познакомитесь с архитектурой синхронизации СОМ+, основанной на активности. Активность СОМ+ работает с абстракцией, называемой активностью (activity), которая применяется для упорядочения вызовов методов объекта. Активность представляет собой группу контекстов, в которой не разрешена параллельная работа. Если метод выполняется у объекта в активности, любой другой поток, пытающийся осуществить вызов метода, будет заблокирован до окончания работы первого метода. Активность может перекрывать несколько контекстов, но один контекст может находиться не более чем в одной активности. Контекста может не быть ни в одной активности. В этом случае любой поток в апартаменте контекста может осуществить вызов метода объекта из этого контекста в любой момент. СОМ+ при этом не прилагает никаких усилий по обеспечению синхронизации (СОМ, находящаяся в основе СОМ+, будет обеспечивать синхронизацию в случае STA). На рис. 16.1 показано соотношение между активностями, контекстами и объектами. Заметьте, что для объекта 5, в отличие от объектов 1—4, не предусмотрено никакой защиты от параллельного доступа — за исключением случая защиты на уровне апартамента, если контекст оказывается размещенным в STA. Атрибут синхронизации Если ваш компонент требует защиты при параллельных вычислениях, ее можно попросить у СОМ+, объявив атрибут синхронизации. Это можно сделать во вкладке Concurrency диалогового окна свойств компонента. Заметьте, что потоковая модель показана, но не доступна для изменения — это связано с тем, что модель определяется в системном реестре.
Активность Контекст Объект 1 Объект 2 Контекст Объект 3 Активность Контекст Объект 4 Контекст Объект 5 Рис. 16.1. Взаимоотношения между активностями, контекстами и объектами У вас есть несколько вариантов выбора поддержки синхронизации. ■ Disabled. Эта установка означает, что данный атрибут СОМ+ не будет принимать во внимание, и никакой защиты при параллельной работе не будет. ■ Not Supported. Контекст объекта не будет размещаться в активности, и, соответственно, СОМ+ вмешиваться в его работу не станет. ■ Supported. Объект будет размещаться в активности, если в ней находится его родитель, причем контекст объекта будет располагаться в той же активности, что и родительский. Если родитель находится вне активности, то и объект будет вне активности. ■ Required. Объект всегда будет находиться в активности. Если родитель находится в активности, то потомок разделяет ее с родителем; в противном случае потомком используется новая активность. ■ Required New. Объект всегда находится в новой активности. Если родительский объект также находится в активности, то это будут разные активности. Таким образом, два потока одновременно не смогут обратиться к объекту, но возможно одновременное обращение двух потоков к предку и потомку. Взаимоотношения атрибутов На выбор атрибута синхронизации могут повлиять значения других атрибутов. Если объекту требуется транзакция, то ему необходима и активность, поскольку транзакция требует защиты при параллельных вычислениях. Активности требует и актив и- зация ЛТ. Без защиты один вызов метода может завершиться и заставить объект деак- тивизироваться во время работы другого метода. В обоих случаях все опции, кроме Required и Required New, будут вам недоступны.
Потоковая модель "напрокат" В ранних описаниях новой потоковой модели Microsoft использовала термин "rental" (дословно — "выдаваемый напрокат") для обозначения объекта, который может "посетить" любой поток (чье место "прописки" — МТА или STA), но одновременно вызывать — только один поток. Такая потоковая модель "напрокат" достигается двумя установками: ThreadingModel = Neutral Synchronization = Required Пример программы В этом разделе мы рассмотрим конкретный пример работы с параллельными вычислениями в СОМ+ в программе Chapl5\BankMtPlus. Здесь имеются два проекта — BankMt и test. Это и есть пример, о котором мы упоминали в начале главы. Сервер строится с применением нейтральной потоковой модели. Создайте новое приложение СОМ+, в которое инсталлируйте BankMt.dll. Примите для компонента атрибуты по умолчанию, которые включают опцию синхронизации Required. Запустите тестовую программу. Выберите аргумент coinit функции ColnitializeEx, после чего щелкните на кнопке Initialize. Каждый поток в своем STA Установите значение флага COINIT равным Single и щелкните на кнопке Initialize. При этом будет создан новый объект в нейтральном апартаменте, а главный поток находиться в STA. Вы можете поработать с различными методами интерфейса iBank. Теперь щелкните на кнопке New thread, при этом будет создан новый поток, который также будет находиться в собственном STA. Теперь попытайтесь выполнить один из методов, например GetBalance, и вы увидите сообщение об ошибке, заключающейся в вызове интерфейса, "маршализованного" для другого потока (рис. 16.2). Рис. 16.2. Результат попытки вызова интерфейса, маршализованного для другого потока Это сообщение об ошибке указывает на одну проблему. Метод вызывается через исходный указатель на интерфейс, который хранится в глобальной переменной. Если бы мы позволили прямое обращение, без прокси, возникло бы условие гонки — как было показано в главе 13, "Многопоточность в СОМ". Одно из решений этой проблемы состоит в использовании прокси, что обеспечивает синхронизацию. Однако СОМ+ предлагает более простую альтернативу. Все потоки в МТА Закройте оба окна и перезапустите тестовую программу. На этот раз выберите значение Multi для флага COINIT. Теперь все потоки будут находиться в одном апартаменте — МТА. Щелкните на кнопке Get Balance во втором окне — вызов завершится ус-
пешно, поскольку будет выполнен маршалинг указателя на интерфейс в том же апартаменте. Щелкните на кнопке Delayed Deposit в первом окне, и тут же — на Deposit во втором окне. Вы увидите, что Deposit блокируется до тех пор, пока не будет выполнен процесс Delayed Deposit. COM+ обеспечивает блокировку посредством перехватчика. Последний устанавливает блокировку, поскольку мы выбрали для нашего компонента значение Required атрибута Synchronization support. Обратите внимание, что блокировка выполняется на уровне объекта. Если вы вернетесь к первому окну и щелкнете в нем на кнопке Delay Deposit, а во втором — на кнопке Get Owner, то обнаружите, что этот вызов также блокируется. Синхронизация СОМ+ тоже не идеальна, и если вам требуется более тонкая блокировка, то код для нее вам придется писать самостоятельно — подобно тому, как мы поступили в главе 13, "Многопоточность в СОМ". Резюме Традиционный механизм для работы с параллельными вычислениями в СОМ работает на основе модели апартаментов, однако при таком подходе возникает немало проблем. Использование МТА и написание собственного кода синхронизации — слишком сложный подход, а применение STA, хотя и освобождает вас от написания кода, приводит к излишним накладным расходам. Даже использование МТА не всегда дает хороший результат, поскольку при вызове вашего объекта из другого апартамента происходит переключение потоков. СОМ+ предлагает решение, которое упрощает программирование и повышает производительность. Новая абстракция, активность, определяет группу контекстов, в которых запрещаются параллельные вычисления. Объявляя ваш компонент как требующий синхронизации, вы гарантируете, что ваш объект работает в пределах активности и обеспечивается защитой параллельных вычислений. Эта защита выполняется облегченным перехватчиком (вместо переключения потоков).
Глава 17 Windows 2000 и безопасность СОМ+ В этой главе мы обсудим жизненно крайне вопрос безопасности. При программировании "настольного" однопользовательского приложения этот вопрос не так важен, но в случае приложения уровня предприятия вы не можете обойти его стороной. Наша цель — изучить вопросы безопасности в СОМ+ и научиться использовать полученные знания при программировании своих приложений. Как вы сможете убедиться, это не очень сложная задача, так как и в данной области СОМ+ многое делает вместо вас, используя механизм перехвата, уже рассматривавшегося нами. Проблема заключается в том, что СОМ+ делает вопросы безопасности слишком простыми, но стоит вам зайти в озеро безопасности чуть подальше от берега, как озеро окажется безбрежным морем тонкостей и нюансов. В данной главе мы постараемся не "заплывать " слишком далеко и рассмотрим основы системы безопасности, начав с архитектуры системы безопасности в NT, которая была развита в Windows 2000. Здесь мы рассмотрим систему безопасности СОМ, которая находится на более высоком уровне, чем система безопасности NT, но на более низком, чем СОМ+. Эти базовые знания должны помочь вам в навигации по безбрежному морю системы безопасности Windows 2000 и СОМ+. Понимание основных административных процедур — один из важных вопросов при практической работе с системой безопасности. Если вы работаете как одиночный пользователь настольной системы Windows, вряд ли вам понадобится работать с учетными записями пользователей и их привилегиями. Но если вам приходилось ранее сталкиваться с этими вопросами, то вы обнаружите, что Активный Каталог (Active Directory) претерпел определенные изменения, как, впрочем, и административный инструментарий. В этой главе приведено краткое руководство, призванное помочь вам разобраться в основах администрирования Active Directory для защиты ресурсов вашего приложения. Затем мы обсудим систему безопасности СОМ, представляющую собой более высокий уровень абстракции и разделяющую многие возможности с СОМ+. И, наконец, мы перейдем к программной модели СОМ+ и увидим, что она предоставляет хорошую абстракцию над низкоуровневой инфраструктурой безопасности. Основные задачи обеспечения безопасности могут быть решены путем конфигурирования, а большинство задач — посредством несложного программирования. Если же вам требуется более сложное программирование системы безопасности, интерфейсы СОМ+ обеспечат вам доступ к соответствующим механизмам.
Фундаментальные проблемы безопасности Упрощенно основная проблема безопасности может быть выражена следующим вопросом: "Кому позволено делать нечто с чем-то?" Например, рассмотрим обращение к файлам в операционной системе. В этом случае "кому" подразумевает пользователя, "нечто" — некоторую файловую операцию, например запись, а "что-то" — конкретный файл. Имеется "страж", который следит за операциями, называемый Security Reference Monitor. Правила, которыми руководствуется наш страж, представляют собой политику (policy), в нашем конкретном случае — это обращения к файлу, принимающие вид управляющего списка доступа (access control list — ACL), в котором описано, какой пользователь имеет право на данный файловый ресурс. На рис. 17.1 показана базовая схема системы безопасности, проиллюстрированная только что рассмотренным примером. Политика Кто Что например, пользователь например, запись например, ACL Security Reference Monitor С чем например, файл Рис. 17.1. Базовая задача безопасности Авторизация Работа монитора состоит в применении политики, предназначенной для проверки того, является ли определенный запрос допустимым и должен ли он быть разрешен. Это — вопрос авторизации. Некомпьютерный аналог авторизации — охранник у входа в здание, пропускающий внутрь людей по списку. Пройти могут только авторизованные персоны, соответствующие некоторой политике. Аутентификация Для того чтобы монитор выполнял свою работу, требуется ответить на еще более фундаментальный вопрос: "Действительно ли тот "кто-то" из предыдущего вопроса является тем, за кого себя выдает?" Это и есть вопрос аутентификации. В нашем примере с охранником у входа в здание вопрос решается, например, с помощью документа с фотографией, позволяющего установить личность входящего. Вопросы безопасности еще более важны в случае распределенного вычислительного окружения. Рассмотрим транзакцию в Internet. Ясно, что перед тем как предоставить клиенту определенный сервис (например, позволить загрузить некоторое программное обеспечение), сервер должен убедиться, что он действительно куплен клиентом. Борьба
с пиратами и доходы — это святое! Но, в то же время, вы тоже, пожалуй, захотите удостовериться, что работаете именно с тем сервером, который вам нужен, — чтобы не рисковать, отправляя номер вашей кредитной карточки неизвестно куда... В этой главе мы рассмотрим, как СОМ+ решает оба этих вопроса, но вначале рассмотрим, как решаются вопросы безопасности в Windows NT и Windows 2000. Урок системного администрирования в Windows 2000 Любая практическая работа, выполняемая вами в системе безопасности Windows 2000, требует от вас знаний основ администрирования пользователей и групп. Вы должны также понимать роль рабочих групп и доменов. Рассматривая приведенные здесь примеры, мы полагаем, что вы работаете с контроллером домена и все создаваемые вами учетные записи представляют собой учетные записи домена. Работа с учетными записями в Windows 2000 Пользователи в Windows 2000 аутентифицируются при входе в систему. Система хранит учетные записи (accounts) пользователей, и каждый пользователь идентифицируется комбинацией имени пользователя и его пароля. Учетные записи пользователей имеют определенные привилегии (или права) для доступа к разным ресурсам и выполнения различных операций. Пользовательские учетные записи необязательно соответствуют реальным людям. Некоторые учетные записи существуют, например, для запуска процессов сервера. В Windows 2000 учетные записи пользователей создаются и управляются инструментом Active Directory Users and Computers, запускаемым из меню Starts Programs^Administrative Tools (рис. 17.2). Рис. 17.2. Учетными записями пользователей можно управлять с помощью Active Directory Users and Computers Добавление пользователей Мы проиллюстрируем, как применяется инструмент Active Directory Users and Computers для создания шести новых учетных записей пользователей. Эти учетные записи будут использованы нами в дальнейшем, при рассмотрении системы безопасности
СОМ-К В дереве просмотра выберите Users и щелкните правой кнопкой мыши. Выберите в контекстном меню New=>User — перед вами откроется диалоговое окно Create New Object— (User). Назначьте пользователям следующие имена: amy, bob, carl, vicky, wanda и admin. Имя пользователя и имя для входа (logon name) будут в нашем примере одинаковы, как видно из рис. 17.3. Примите все прочие поля, предлагаемые по умолчанию, включая пустой пароль. Рис 17.3. Добавление нового пользователя После создания нового пользователя вы можете определить его дополнительные свойства, щелкнув правой кнопки мыши на имени пользователя и выбрав Properties в контекстном меню. В качестве примера добавьте описание "Alternate administrator account" для admin, как показано на рис. 17.4. Рис. 17.4. Указание свойств учетной записи пользователя
Встроенные учетные записи В операционной системе имеются две встроенные учетные записи. Administrator представляет собой учетную запись наивысшего уровня, с полным доступом ко всей системе. Эта учетная запись не может быть удалена, так что запереть систему от себя самого вам не удастся. Guest представляет собой учетную запись с ограниченным доступом к системе. По умолчанию эта учетная запись отключена, но при желании вы можете включить ее. Кроме того, имеется ряд специальных пользователей, соответствующих разным системным сервисам, — например, учетная запись, под которой запускается Internet Information Services. Существует также третья встроенная учетная запись, которая всегда имеется в системе, но скрыта. System представляет собой учетную запись, используемую только системой. Войти в систему под этим именем невозможно, нельзя также изменить или удалить эту учетную запись. Эта учетная запись имеет неограниченный доступ к локальной машине, но не имеет доступа к сетевым ресурсам. Группы Группы могут использоваться для упрощения администрирования учетных записей пользователей. Учетная запись обычно участвует в одной или нескольких группах. Группа имеет связанные с ней права, разделяемые всеми пользователями группы. Имеется ряд встроенных групп, таких как Domain Admins, Domain Users и т.п. Вновь созданная учетная запись пользователя по умолчанию принадлежит группе Domain Users. Мы оставим пользователей amy, bob, carl, vicky и wanda в группе Domain Users, а пользователя admin перенесем в группу Domain Admins. Для изменения членства пользователя в группе вызовите диалоговое окно Properties для этого пользователя и выберите вкладку Member Of. Щелкните на кнопке Add. В диалоговом окне Select Groups выберите необходимую группу, например Domain Admins, и щелкните на кнопке Add (рис. 17.5). Таким образом можно добавить несколько групп. По окончании работы щелкните на кнопке ОК. Рис. 17.5. Добавление существующего пользователя в группу
Новую группу можно добавить так же, как и нового пользователя. В дереве просмотра щелкните правой кнопкой мыши на User и выберите в контекстном меню New^Group, вызвав тем самым диалоговое окно Create Object — (Group). Создайте новую группу Cowboys, приняв область видимости по умолчанию Global и тип группы Security. Как и в случае нового пользователя, после создания новой группы вы можете изменить ее свойства, щелкнув правой кнопки мыши для вызова диалогового окна Properties. Для группы Cowboys, например, добавьте описание "Много прав — следует БЫТЬ ОСТОРОЖНЫМ!", затем щелкните на вкладке Members и добавьте в новую группу пользователя carl. Рабочие группы, домены и активный каталог При работе с системой безопасности вы сосредотачиваетесь на учетных записях пользователей. Очень важно понимать контекст, в котором корректна учетная запись пользователя. Учетная запись корректна либо для локального компьютера, либо для домена. Рабочая группа представляет собой путь для объединения локальных компьютеров, а активный каталог (Active Directory) может объединять целое предприятие. В этом разделе приведена базовая информация о рабочих группах, доменах и активном каталоге. Рабочие группы Простейшим типом сетей NT является рабочая группа. Рабочая группа представляет собой подключения равных компьютеров (peer-to-peer). Пользователь на одной машине получает доступ к ресурсам другой машины, входя на нее с учетной записью, которая верна на другой машине. Если вы вошли в одну машину с учетной записью, которая не верна для другой машины, то при попытке доступа к ее ресурсам вам будет выведено приглашение для входа в машину. Для того чтобы разрешить пользователям входить одновременно в несколько машин, следует иметь учетную запись на каждой из них. Если вы вошли в одну машину с учетной записью, справедливой для других машин, то при обращении к ним повторная процедура входа не требуется. Нет понятия "учетная запись рабочей группы" — каждая учетная запись специфична для данной конкретной машины. Домены в NT 4.0 Для расширения кругозора рассмотрим вкратце систему доменов в NT 4.O. Домен NT 4.0 управляется машиной с NT Server, которая работает в качестве первичного контроллера домена (primary domain controller — PDC). PDC хранит учетные записи домена, которые корректны для любой машины, являющейся частью домена. PDC поддерживает базу данных учетных записей домена и аутентифицирует пользователей при их входе. Кроме того, может иметься одна или несколько машин, работающих в качестве резервного контроллера домена (backup domain controller — BDC). BDC хранит копии учетных записей домена и также может производить аутентификацию пользователей. В DC снижает сетевой трафик и загрузку PDC. В DC не поддерживает базу данных учетных записей, но она периодически обновляется PDC. Windows 2000 и активный каталог Основная проблема NT 4.0 возникает при увеличении сети до масштабов предприятия со многими доменами. Поскольку учетная запись справедлива лишь в пределах домена, администратору, например, для работы требуется по учетной записи в каждом
домене, который следует администрировать. Windows 2000 вводит активный каталог (Active Directory), который идентифицирует все ресурсы сети. Активный каталог состоит из распределенной базы данных, в которой хранится информация о всех ресурсах сети, и имеет программные сервисы, предоставляющие информацию, хранящуюся в каталоге, пользователям и приложениям. Ресурсы, хранящиеся в каталоге, известны под названием объекты. Примерами объектов могут служить пользователи, группы, принтеры и базы данных. Компьютеры и другие ресурсы сети организованы в домены. Домен представляет собой часть ресурсов каталога и управляется одним или несколькими компьютерами с Windows 2000, известными под названием контроллеров домена. Все контроллеры определенного домена равноправны, и каждый содержит реплику доменной части каталога. Домен представляет также область безопасности. Например, учетные записи пользователей устанавливаются для домена. Когда пользователь успешно пропущен контроллером домена, он получает доступ (в рамках своих полномочий) ко всем ресурсам домена. Активный каталог предоставляет единую точку приложения административных усилий в сети. Все домены связаны между собой, и администрирование может производиться с одного входа в систему. Без активного каталога администратору бы потребовалось иметь по отдельной учетной записи для каждого домена, что было бы очень неудобно в большой сети предприятия со многими доменами. Логическая структура активного каталога основана на доменах, а физическая — на узлах (sites), которые можно рассматривать как одну или несколько IP-подсетей. При администрировании маленькой тестовой сети все компьютеры обычно находятся в одной подсети и входят в состав одного узла активного каталога. Безопасность NT Теперь мы можем рассмотреть модель безопасности NT, которая является основой системы безопасности во всех версиях NT, включая Windows 2000. При переходе к распределенной системе безопасности Windows 2000 в качестве альтернативы LAN Manager security предоставляет мощного провайдера системы безопасности Kerberos, но при этом базовые концепции на уровне операционной системы остаются теми же. После обсуждения фундаментальных концепций мы продемонстрируем их связь с административными процедурами, обсуждавшимися в предыдущем разделе. Модель безопасности NT Модель безопасности NT состоит из ряда фундаментальных элементов: ■ идентификаторы безопасности (Security ID — SID) для идентификации пользователей и групп, ■ признаки доступа, назначаемые пользователю при входе в систему, определяющие, кто он, и его права, ■ объекты NT (такие как файлы, процессы, потоки, семафоры и т.п.); ■ дескрипторы безопасности, определяющие владельца объекта, а также то, кто имеет право доступа к нему; ■ записи управления доступом, состоящие из дескрипторов безопасности и масок доступа, определяющие разрешенные пользователю или группе действия; ■ списки управления доступом, определяющие, какие пользователи или группы могут предоставлять или закрывать доступ другим пользователям; ■ системные списки управления доступом, которые определяют, кто может выполнять операции с объектом.
Идентификатор безопасности Идентификатор безопасности (SID) представляет собой уникальный идентификатор, использующийся для определения пользователя или группы. После того как пользователь или группа были созданы на машине домена, создается уникальный SID для идентификации этого пользователя или группы. SID используется в функциях системы безопасности Win32 API. Активный каталог (User Manager в NT 4.0) создает идентификаторы автоматически, и обычно вам никогда не приходится иметь с ними дело. Признаки доступа Когда пользователь входит в систему, он получает признак доступа, который определяет следующее: ■ идентификатор пользователя; ■ группу, которой принадлежит пользователь; ■ права, которые имеет пользователь (которые порождаются правами, назначенными непосредственно пользователю, и правами группы, которой принадлежит пользователь). Когда пользователь запускает процесс, тот получает описанный признак доступа и работает в определяемом им контексте безопасности. Процесс сервера имеет собственный признак безопасности со своим контекстом. Процесс сервера может играть роль клиента и выполнять действия в контексте безопасности клиента. Объекты NT В NT такие элементы операционной системы, как файлы, процессы, потоки и тому подобные рассматриваются как объекты. Объект NT представляет собой экземпляр типа объекта. Тип объекта определяет атрибуты объекта и сервисы объекта данного типа. Атрибуты, общие для всех объектов, хранятся в заголовке объекта, а специфичные для данного типа объектов — в теле объекта. Менеджер объектов NT обеспечивает сервисы, общие для всех объектов, такие как создание, уничтожение, защита и трассировка. При создании объекта менеджер возвращает дескриптор (handle) объекта. Весь доступ к объекту производится через его дескриптор. Дескрипторы безопасности Одним из центральных атрибутов объекта каждого объекта NT является дескриптор безопасности. Дескриптор безопасности может быть либо определен явным образом при создании объекта, либо наследовать дескриптор безопасности создавшего его процесса. Дескриптор безопасности определяет: ■ Discretionary Access Control List (DACL), идентифицирующий пользователей или группы пользователей, определяющих права или запрещающих доступ другим пользователям; ■ System Access Control List (SACL), определяющий выполняемые проверки; ■ SID владельца, идентифицирующий пользователя или группу пользователей, владеющих объектом. Владелец может изменять список управления доступом.
Записи управления доступом Списки управления доступом состоят из записей управления доступом, которые, в свою очередь, содержат: ■ идентификатор безопасности, определяющий пользователя или группу, для которых применяется эта запись; ■ маску доступа, определяющую права доступа к объекту (например, чтение или запись); ■ заголовок записи, определяющий тип записи (разрешение, запрет, системная проверка) и поведение при наследовании. Имеются общие права (выполнение, чтение и запись), применимые ко всем объектам, но их значение зависит от конкретного типа объекта. Имеются также стандартные и частные права. Стандартные права применимы ко всем объектам и имеют стандартное значение (например, удаление), а частные права зависят от типа объекта. Discretionary Access Control List Discretionary Access Control List определяет, кто имеет право доступа и кто не имеет его для выполнения некоторых действий над объектом. Проверка процесса возможности доступа к конкретному объекту NT включает в себя проход по всем объектам списка и сравнение записей управления доступом с признаками доступа процесса. Первыми проверяются запрещающие записи, и, если таковая находится, процесс проверки на этом прекращается, а доступ запрещается. Если находится подходящая разрешающая запись, доступ разрешается. Если ни той, ни другой записи не найдено (имеется пустая запись), доступ не разрешается. Если нет записей, значит, защита отсутствует, и доступ к объекту разрешается. Например, файл на разделе FAT не имеет DACL и, следовательно, не может быть защищен системой безопасности NT. Демонстрация безопасности Проиллюстрируем учетные записи пользователей и систему безопасности NT на простейшем примере. ■ Пользователь amy создает файл sensitive.txt в корневом каталоге раздела NTFS. ■ Пользователь amy дает себе права чтения и записи этого файла, право чтения файла пользователям bob и carl и запрещает доступ группе Cowboys. ■ Войдите в систему под именами amy, bob и carl и попробуйте поработать с файлом. В этом примере есть небольшая хитрость — по умолчанию Windows 2000 Server не позволяет обычному пользователю войти в контроллер домена, так что выполнить задание полностью на сервере не удастся. Не удастся это сделать и на другой машине, поскольку Windows 2000 Professional не позволяет администрировать учетные записи пользователей. Простейшее решение состоит в том, чтобы осуществить вход в качестве обычного пользователя на машине с Windows 2000 Professional, а все операции с правами пользователей выполнить на машине Windows 2000 Server. 1. Войдите в машину Windows 2000 Professional как пользователь amy и с помощью программы Notepad создайте файл sensitive.txt с каким-либо текстом. Сохраните его в корневом каталоге NTFS раздела.
Следующая часть работы вы должны выполнять как администратор (учетная запись Administrator) на сервере. В Windows Explorer щелкните правой кнопкой мыши на файле sensitive.txt и выберите в контекстном меню пункт Properties. Щелкните на вкладке Security диалогового окна свойств. Заметьте, что для файла уже имеется ряд разрешений (унаследованных от родительского каталога). Выберите в верхнем списке Everyone и обратите внимание на то, что для всех открыт доступ как для чтения, так и для записи (рис. 17.6). Рис. 17.6. По умолчанию всем предоставляется доступ для чтения и записи файла 3. Удалите разрешения доступа для Everyone. Вначале отмените выбор опции Allow inheritable permissions. В появившемся при этом окне сообщения щелкните на кнопке Сору. После этого при выбранном Everyone щелкните на кнопке Remove. 4. Теперь явным образом задайте права доступа пользователям bob, carl и Cowboys. В главном окне свойств файла щелкните на кнопке Add. В диалоговом окне Select, показанном на рис. 17.7, добавьте bob, carl и Cowboys. Щелкните на кнопке ОК. 5. Вернувшись в окно Properties, вы увидите, что у всех добавленных пользователей имеются определенные права доступа. Так, amy имеет все права (как создатель файла), a bob, carl и Cowboy — права на чтение (Read) и чтение и выполнение (Read & Execute). Выберите Cowboys и щелкните на опции Deny для Full Control. При этом все флажки Deny будут помечены, а все флажки Allow — очищены, как показано на рис. 17.8. 6. Вернемся на машину Windows 2000 Professional и войдем в систему как bob. Мы можем читать файл, но не записывать в него.
Рис. 17.7. Добавим права доступа для bob, carl и Cowboys Рис. 17.8. Мы запретили любой доступ к файлу для группы Cowboys 7. Войдем в систему с учетной записью carl. Мы не имеем никаких прав доступа к файлу. Несмотря на то что carl имеет права на чтение и выполнение, его принадлежность к группе Cowboys закрывает доступ, так как запрещение имеет приоритет перед разрешением в DACL, как упоминалось раньше.
Система безопасности СОМ Система безопасности СОМ представляет собой более высокий уровень абстракции, чем система безопасности NT, и, кроме того, она проще в использовании, чем система безопасности NT. Система безопасности СОМ адресована, в первую очередь, распределенным приложениям. Она реализуется двумя слоями. Верхний слой — ау- тентифицированный RPC, обеспечивающий безопасность соединений. Нижний слой создается с помощью провайдера поддержки безопасности (Security Support Provider — SSP), который реализует интерфейс провайдера поддержки безопасности (SSPI), вызываемый аутентифицированным RPC. В нижнем слое могут использоваться различные провайдеры. Так, в NT 4.0 используется NTLM (NT Lan Manager) SSP, a Windows 2000 предоставляет, кроме того, более мощный Kerberos SSP, который является провайдером поддержки безопасности по умолчанию. Описанные слои системы безопасности СОМ показаны на рис. 17.9. Аутентифицированный RPC SSPI Kerberos NTLM Рис. 17.9. Система безопасности СОМ состоит из двух слоев Используя эти слои, СОМ предоставляет систему безопасности в соответствии с вашими пожеланиями, зафиксированными в системном реестре. Изменить установки в системном реестре вы можете с помощью инструмента dcomcnfg. COM контролирует две основные функции безопасности — авторизацию и аутентификацию, — о которых мы говорили в начале этой главы. Кроме того, СОМ определяет подлинность работающего компонента сервера и выполняет ряд других задач. Обо всем этом мы поговорим чуть ниже. При рассмотрении вопросов авторизации мы будем использовать приложение-пример Name, с которым работали в главах 9, "ЕХЕ-серверы" и 10, "Введение в DCOM". Авторизация Как уже говорилось, основная проблема системы безопасности заключается в авторизованном доступе к ресурсам. В случае СОМ ресурс представляет собой компонент СОМ. СОМ позволяет вам контролировать как запуск компонента, так и доступ к уже запущенному компоненту. Вы можете изменить настройки системного реестра для определения прав доступа и запуска компонента. Мы рассмотрим эти настройки на конкретном примере.
Пример авторизации Нашей демонстрационной программой будет ЕХЕ-сервер Name, рассматриваемый в главе 9, "ЕХЕ-серверы". В нем содержится единственный интерфейс iMachine с единственным методом Get Name, который возвращает имя машины, на которой запущен сервер. Сервер вы найдете в каталоге Chapl7\Name, а тестовую программу- клиент — в каталоге Chapl7\NameTest. Соберите обе программы. Перед тем как рассматривать настройки безопасности, запустим приложение для простой иллюстрации работы DCOM. Для того чтобы наш эксперимент прошел гладко, нам надо разместить сервер на машине с Windows 2000 Server, а клиент — на машине с Windows 2000 Professional. Начать следует с дерегистрации сервера на обеих машинах (если он уже был зарегистрирован в главе 9, "ЕХЕ-серверы"). Для этого запустите два пакетных файла — unreg_name.bat и unreg_nameps.bat. Убедитесь, что вы вошли на обе машины с учетной записью Administrator, и выполните следующее: 1. Зарегистрируйте сервер на машине с Windows 2000 Server. Убедитесь в том, что тестовая программа запускается и возвращает имя машины с Windows 2000 Server. 2. Зарегистрируйте сервер на машине с Windows 2000 Professional. Убедитесь, что тестовая программа запускается и возвращает имя машины с Windows 2000 Professional. 3. Теперь, если хотите, можете удалить файл Name. ехе с машины с Windows 2000 Professional. Воспользуйтесь dcomcnfg на машине с Windows 2000 Professional и измените значение Location так, чтобы запускался сервер на машине с Windows 2000 Server (мы уже выполняли такую процедуру в главе 10, "Введение в DCOM", поэтому, если вы не совсем уверены, как это делается, — обратитесь к данной главе). Теперь при запуске тестовой программы вы должны получить имя удаленной машины с Windows 2000 Server. Если эта демонстрация не работает, вероятно, возникли проблемы с системой безопасности, которые вы легко сможете устранить по прочтении данной главы. Права на запуск и доступ по умолчанию Мы начнем наше рассмотрение с вкладки Default Security главного окна dcomcnfg на машине с Windows 2000 Server, показанной на рис. 17.10. В разделе Default Access Permissions щелкните на кнопке Edit Default... — перед вами появится изображенный на рис. 17.11 список пользователей и групп, имеющих разрешение на доступ к DCOM серверу, у которого не определены его собственные индивидуальные настройки.. Обратите внимание, что для каждого пользователя или группы вы можете определить Allow Access или Deny Access. Вспомните DACL — здесь также запрет имеет преимущество перед разрешением. Такой же список имеется и для раздела Default Launch Permissions. Запуск и доступ представляют собой разные действия, и пользователь, например, может иметь право обратиться к работающему серверу, но не иметь права запустить его. Мы не будем экспериментировать с настройками доступа по умолчанию — это не самое разумное занятие. Мы хотим определить права доступа для отдельных приложений. Единственное, что, возможно, стоит сделать с настройками по умолчанию — это сделать их более строгими, уменьшив вероятность несанкционированного доступа извне.
Рис 17.10. В DCOMCNFG вы можете установить права на запуск и доступ по умолчанию Рис. 17.11. Пользователи и группы с правами доступа по умолчанию Мы можем проверить поведение настроек по умолчанию, попытавшись получить доступ к серверу как обычный пользователь машины-клиента (с Windows 2000 Professional). Заметим, что пользователь amy не имеет прав на запуск и обращение (мы только что зарегистрировали сервер, так что нет никаких оснований для появления специальных настроек для него). Войдите в систему под именем amy и попытайтесь запустить программу NameTest.exe. Щелкнув на кнопке Get Name, вы получите сообщение об ошибке, показанное на рис. 17.12. Рис. 17.12. Пользователю amy доступ к серверу запрещен
Права на запуск и доступ для приложений Вы можете изменить права доступа и запуска для отдельного приложения. Вызовите на машине с Windows 2000 Server dcomcnfg, найдите в списке приложение Name и выберите Properties. Выберите вкладку Security и щелкните на опции Use custom access permissions (рис. 17.13). Рис. 17.13. Можно определить пользовательские права доступа для приложения Щелкните на кнопке Edit. Затем вы можете добавить пользователя bob в список тех, кто имеет права доступа к серверу. Дайте ему также права на запуск приложения. Щелкните на кнопке ОК для выхода из dcomcnfg и затем перейдите на машину с Windows 2000 Professional. Завершите сеанс amy и войдите под именем bob. Теперь запуск тестового приложения должен пройти успешно. Аутентификация Вспомним, что аутентификация позволяет клиенту и серверу проверить подлинность друг друга. Это та основа, на которой стоит авторизация. Определенный пользователь может быть авторизован для использования сервера, но если кто-то попробует "притвориться" этим пользователем, то на защиту встанет механизм аутентификации. Следовательно, аутентификация является существенной частью инфраструктуры системы безопасности. СОМ обеспечивает шесть уровней аутентификации. ■ None. Аутентификация отсутствует. Без аутентификации отсутствует авторизация, а следовательно, и система безопасности в целом. Эта установка никогда не должна использоваться, если вы заботитесь о безопасности. ■ Connect. Аутентификация выполняется при подключении к серверу. ■ Call. Аутентификация выполняется при принятии сервером вызова RPC. ■ Packet. Аутентификация того, что данные приходят от определенного клиента, выполняется по приходу каждого пакета (в случае протоколов типа UDP подключение и вызов интерпретируются как пакет).
■ Packet Integrity. Выполняется аутентификация того, что пакет приходит от определенного клиента и что данные не были модифицированы. ■ Privacy. Выполняются все проверки Packet Integrity, плюс данные в пакетах шифруются. Вы можете определить уровень аутентификации во вкладке Default Properties в программе DCOMCNFG, показанной на рис. 17.14. Подлинность С каким идентификатором пользователя работает сервер, когда удаленный сервер запускается для обработки вызова клиента? Этот идентификатор пользователя необходим для того, чтобы выяснить, имеет ли процесс сервера доступ к различным ресурсам машины, на которой он работает. Изменить идентификатор пользователя можно с помощью вкладки Identity в программе dcomcnfg при работе с определенным приложением, как показано на рис. 17.15. Рис. 17.14. Уровень аутентификации может быть определен в DCOMCNFG Работа в качестве запускающего пользователя Установка по умолчанию — Launching User. Например, пользователь amy вошел на машину-клиент и вызвал удаленный сервер. При этом процесс сервера будет обрабатывать запрос пользователя amy, используя права доступа amy. Это интуитивно понятное поведение, но имеющее свои проблемы. Самая большая проблема состоит в том, что для каждого клиента сервер при этом создает свою станцию Windows, которая весьма ресурсоемка (например, требует около 3 Мбайт памяти). Вторая проблема состоит в том, что сервер играет роль пользователя и потому имеет небольшие возможности в части обращения к разным ресурсам. Кроме того, при этом процессу сервера недоступен интерактивный десктоп. Чтобы понять, что такое станция Windows и интерактивный десктоп, будет полезно рассмотреть сервисы NT.
Сервисы NT Сервис NT (NT Service) представляет собой приложение, запускаемое в фоновом режиме, независимо от текущего пользователя системы. Сервисы запускаются менеджером управления сервисами (Service Control Manager — SCM). Сервисы могут быть сконфигурированы для автоматического запуска при загрузке операционной системы, до входа в систему любого пользователя. По умолчанию сервисы запускаются со встроенной учетной записью System. В Windows 2000 администрировать сервисы позволяет инструмент Computer Manager, окно которого показано на рис. 17.16. Рис. 17.15. Установка идентификатора пользователя, под которым работает сервер Рис. 17.16. Сервисы можно запустить и остановить, а также настроить их автоматический запуск при загрузке операционной системы
Станции Windows и десктопы Когда вы входите в систему как интерактивный пользователь, вам выделяется де- сктоп^, предоставляющий графический интерфейс пользователя. Возможности графического интерфейса пользователя, такие как окна и тому подобное, специфичны для данного десктопа. Сервисы NT обычно десктопа не имеют, а потому, например, окно сообщения, выводимое сервисом, не будет отображено на экране и сервис может попросту остановиться, ожидая, пока кто-то не закроет (невидимое!) окно сообщения. Станция Windows (Windows station) представляет собой элемент Win32, который объединяет десктоп, буфер обмена и некоторые другие ресурсы. Станция Windows требует большого количества ресурсов, в частности, около 3 Мбайт памяти. Win32 API предоставляет функции для создания, открытия и закрытия станций Windows, назначения станции Windows определенному процессу и т.д. Работа в качестве интерактивного пользователя В этом случае сервер запускается с признаками доступа интерактивного пользователя, в настоящий момент зарегистрированного на компьютере. При этом десктоп оказывается доступен, и сервис может выводить окна. Дополнительные станции Windows для клиентов не создаются, не происходит и подмены пользователя — процесс может все, что может текущий пользователь системы. Но и здесь не обошлось без проблем. Если в настоящий момент в системе не работает ни один пользователь, процесс не может запуститься вовсе, а работающий сервис прекратит свою деятельность при выходе пользователя из системы. Запуск в качестве интерактивного пользователя может оказаться удобен на стадии разработки (хотя я не рекомендую выводить окна сообщений даже в этом случае — те же результаты можно получить с помощью записи в журнальный файл, что было продемонстрировано в главах 12, "Обработка ошибок и отладка" и 15, "Урок СОМ+". В этом случае не окажется, что вы случайно забыли удалить вывод окна в переданном заказчику приложении). Работа в качестве конкретного пользователя Третья опция позволяет вам указать конкретного пользователя, под именем которого будет работать сервер. При этом будет создана только одна станция Windows для всех клиентов. Подмены пользователя не происходит, процесс может делать все то, что разрешено пользователю, под именем которого он работает. Сервер может работать при отсутствии на машине активного пользователя. В целом это самая предпочтительная опция. Подмена пользователя Мы видели, что если сервер работает под именем запускающего пользователя, то он выступает от имени этого пользователя. Такая возможность может быть очень полезна во многоуровневых приложениях, где конечный компонент имеет собственную систему безопасности и выполняет проверку подлинности вызывающего клиента. Если компонент среднего уровня работает под определенной учетной записью, такая проверка завершится неудачно. Решение состоит в работе процесса среднего уровня от имени клиента. В данном случае представляется более правильным не переводить термин desktop как, например, рабочий стол, поскольку здесь это понятие не столько физическое, представляющее рабочее место пользователя, сколько логическое, описывающее возможность интерактивной работы — Прим. перев.
Однако нам бы не хотелось нести расходы по созданию станции Windows для каждого пользователя Решение работать от имени пользователя совершенно не подходит для больших систем, требующих масштабирования, — так что на самом деле в СОМ+ эта опция попросту запрещена. К счастью, СОМ позволяет компоненту подменять любого пользователя. Вам необходимо написать небольшой код, который будет рассмотрен нами при изучении системы безопасности СОМ+. Вопрос заключается в уровне подмены пользователя. Все, что мы хотим — это возможность выполнения приложением среднего уровня всего того, что может выполнить конечный клиент, включая вызов других уровней, которые, в свою очередь, могут использовать подмену пользователей. Такой уровень называется уровнем делегирования и впервые стал доступным в Windows 2000, использующей Kerberos SSP. Уровень подмены (имперсонации) по умолчанию может быть установлен во вкладке Default Properties окна dcomcnfg. ■ Anonymous. В настоящее время не поддерживается. ■ Identity Сервер может подменять клиента с целью проверки ACL, но не получает доступ к объектам системного уровня. ■ Impersonate. Сервер может подменять клиент с целью проверки ACL и получает доступ к объектам системного уровня, но не может подменить клиент при вызове другого (удаленного) сервера. ■ Delegate. Сервер может подменять клиент с целью проверки ACL, получает доступ к объектам системного уровня и может подменить клиент при вызове другого сервера (поддерживается Kerberos SSP в Windows 2000). Система безопасности СОМ+ СОМ+ предоставляет очень обширный и в то же время весьма гибкий механизм безопасности. Вы можете административно настроить множество атрибутов, обеспечивающих проверку безопасности, без единой строчки собственного кода. Если вам требуется более тонкий контроль, СОМ+ облегчит программную проверку безопасности В этом разделе мы рассмотрим различные пути настройки системы безопасности СОМ+ и проиллюстрируем некоторые простейшие программные решения. Нашим программным примером будет служить специализированная версия игры Electronic Commerce Game™. Electronic Commerce Game™ В нашем изучении мы работали со множеством простейших программ. При изучении новой технологии полезно работать с простыми программами, с тем чтобы сконцентрироваться на особенностях новой технологии и не отвлекаться на несущественные детали. Однако для лучшего понимания того, как множество концепций работают в реальных условиях, полезно рассмотреть пример побольше и посложнее. Таким примером станет исследование под названием "The Electronic Commerce Game". Базовая концепция очень проста — покупка и продажа продукции в оперативном (online) режиме. Игроки исполняют роль покупателей и соревнуются в выполнении списка покупок, посещая узлы различных поставщиков и приобретая товары по оптовым ценам. На рис. 17.17 показан один из экранов игры, отображающий посещение узла toyland.com.
Рис. 17.17. Посещение узла поставщика в Electronic Commerce Game™ Специализированная версия Electronic Commerce Game™ Electronic Commerce Game™ использует несколько баз данных. Мы начнем рассмотрение баз данных в следующей главе, а сейчас сосредоточимся на специализированной версии-заглушке для иллюстрации концепций безопасности в СОМ+. В этой игре для участников предусмотрены две роли — покупателя и поставщика. Начальный экран Electronic Commerce Game™ дает возможность выбрать для визита один из трех узлов— shopper.com, vendors.com или wholesale.com. Первый узел открыт для покупателя, а третий — для поставщика, второй узел открыт для всех и просто предоставляет список URL поставщиков, куда игроки могут обращаться за покупками. Версия-заглушка игры просто выполняет проверку доступа к первому и третьему узлам. Окна сообщений отображают успешность обращения к узлам. Код сервера можно найти в каталоге Chapl7\EcStub\Server, а код клиента — в каталоге Chapl7\EcStub\Client. Зарегистрируйте сервер и соберите приложение- клиент. (Electronic Commerce Game™ реализована на Visual Basic.) Запуск заглушки Перед тем как приступать к каким-либо настройкам или программированию систем безопасности, стоит запустить программу "как есть" — и как локальное приложение на машине Windows 2000 Server, и как распределенное приложение — с сервером на машине с Windows 2000 Server и клиентом на машине с Windows 2000 Professional. Перейдем непосредственно к запуску программы под СОМ+. Создайте новое приложение СОМ+ EcStubApp для сервера Electronic Commerce Game™. Теперь вы должны понимать важность диалогового окна Set Application Identity. Сейчас мы будем работать как интерактивный пользователь. Установите сервер с помощью технологии "перетащить и отпустить". В СОМ+ Explorer вы должны увидеть два компонента, показанных на рис. 17.18. Теперь вы должны иметь возможность запустить программу-клиент. Вначале попытайтесь посетить shopper.com. В игре имеются несколько игроков, вставленных непосредственно в код программы, и amy — один из них. Введите amy в диалоговом окне Player Login, это приведет к успешному входу в игру (рис. 17.19). Попытайтесь посетить wholesale.com. Здесь также не должно быть никаких сбоев. Обратите внимание, что для тестовых целей имеется только один неверный идентификатор входа, а именно — 0 Вы можете изучить исходный код программы, чтобы понять ее логику, но в настоящий момент это не так важно — сейчас нас интересует не логика программы, а вопросы безопасности.
Рис. 17.18. Версия-заглушка Electronic Commerce Game™ имеет два компонента Рис. 17.19. Вход ату на узел shopper. сот выполнен успешно Экспорт прокси Последнее предварительное упражнение будет состоять в создании .msi-файла для экспортирования прокси на другой компьютер, чтобы мы могли протестировать программу как распределенное приложение. В СОМ+ Explorer щелкните правой кнопкой мыши на EcStubApp и выберите в контекстном меню Export. Перейдите в каталог ComPlus\Deploy и введите имя EcStub для создаваемого файла приложения. Выберите Application proxy, как показано на рис 17.20. Скопируйте файлы EcStub.msi и EcStubClient.exe на удаленную клиентскую машину с Windows 2000 Professional Установите прокси, дважды щелкнув на файле EcStub.msi. Теперь вы должны быть способны запустить клиентскую программу на удаленном компьютере.
Рис. 17.20. Создание файла приложения для экспортирования прокси Конфигурирование системы безопасности СОМ+ Теперь мы готовы приступить к исследованию системы безопасности СОМ+. Начнем с рассмотрения различных путей, которыми можно настроить систему безопасности в СОМ+. Вы увидите ряд сходств с настройкой системы безопасности обычной СОМ в dcomcnfg, однако опций в СОМ+ значительно больше. Настройки Authentication, Impersonation и Identity, по сути, не изменились, но настройки проверок системы безопасности стали существенно более тонкими. В обычной СОМ вы были ограничены указанием безопасности на уровне компонента; СОМ+ же позволяет указывать настройки безопасности на уровне приложения, компонента, интерфейса и метода. Вы устанавливаете конфигурационные настройки на этих уровнях с помощью соответствующих диалоговых окон Properties, как было продемонстрировано в главе 15, "Урок СОМ+". Кроме того СОМ+ вводит совершенно новую абстракцию безопасности, называемую ролью. Мы рассмотрим роли в отдельном разделе. Настройка безопасности на уровне приложения На уровне приложения вы можете определить авторизацию, уровень безопасности, уровень аутентификации и уровень имперсонации (рис. 17.21). Опция авторизации представляет собой основной переключатель проверки доступа на уровне приложения. Если он отключен (состояние по умолчанию), никакие проверки доступа не выполняются. Вы можете проверить это, войдя как обычный пользователь (например, amy) на вашу машину с Windows 2000 Professional. При этом вы получаете права администратора. Опции уровня безопасности определяют, должна ли информация о безопасности включаться в контекст объекта. Настройки по умолчанию включают эту информацию в контекст. Последние две настройки — уровень аутентификации и уровень имперсонации — имеют то же значение, что и в обычной СОМ. Вы устанавливаете их в этом диалоговом окне, не прибегая к помощи dcomcnfg. После того как ваши компоненты импортированы в СОМ+, все настройки безопасности будут выполняться в СОМ+ Explorer — dcomcnfg вам не потребуется совсем.
Рис. 17.21. Настройка безопасности на уровне приложения Пометьте опцию Authorization и щелкните на кнопке ОК. Повторите после этого ваш эксперимент по удаленному входу в приложение в качестве обычного пользователя. Теперь клиентская программа может потерпеть неудачу из-за отсутствия права доступа (рис. 17.22). Рис. 17.22. При включенной проверке вы можете не полунить доступа к серверу В действительности, если даже вы используете учетную запись администратора, в доступе все равно будет отказано. СОМ+ беспристрастна — если в доступе отказано, то отказано всем, кроме тех, кому доступ открыт явным образом. В СОМ+ доступ дается посредством ролей,, которые будут рассмотрены нами в следующем разделе. Если хотите, вы можете изучить диалоговые окна Properties на других уровнях (компонента, интерфейса и метода) и посмотреть, каким образом выглядит вкладка Security для них. Вы увидите, что на уровне компонента опция проверки доступа помечена и имеется поле с указанием информации о роли. Пока роли нами не определены, это поле пусто. Более низкие уровни также имеют поля для определения роли. Система безопасности, основанная на ролях Система безопасности, основанная на ролях была введена в Microsoft Transaction Server и представляет собой существенное упрощение настройки и программирования системы безопасности. Роль представляет собой логическую группу пользователей, авторизованных для доступа к компоненту, интерфейсу или методу. Вкратце, роль представляет собой хорошую абстракцию для определения политики системы безопасности (теперь самое время вновь взглянуть на рис. 17.1).
Роли весьма интуитивны, поэтому мы просто обратимся к нашему примеру без особых дискуссий. Мы определим две роли — Customer (покупатель) и Vendor (поставщик). В нашей программе-заглушке покупатель может входить на узел shopper.com, а поставщик — на wholesale.com. Настройка ролей Настроить роли можно с помощью СОМ+ Explorer. Как и в случае всех административных процедур, выполняемых данным инструментом, эту задачу можно автоматизировать с помощью сценария, использующего объекты СОМ+ Admin. Вначале мы создаем роли, а затем заполняем их пользователями. Откройте EcStubApp и выберите Roles, затем щелкните на нем правой кнопкой мыши и выберите в контекстном меню New=>Role. Первой роли присвойте имя Customer, затем, повторив действия, назначьте второй роли имя Vendor. Теперь в дереве просмотра откройте Customer и выберите Users. Щелкните правой кнопкой мыши и выберите в контекстном меню New^User. При этом на экране появится диалоговое окно Select Users or Groups. Добавьте пользователей amy, bob, carl и Administrator, как показано на рис. 17.23. Точно так же добавьте в роль Vendor пользователей vicky, wanda и Administrator. Рис. 17.23. Добавление пользователей в роль Настройка доступа на уровне компонента Теперь можно приступить к настройке доступа на уровне компонента. В СОМ+ Explorer выберите EcStubServer.Player в Components в дереве просмотра. Щелкните правой кнопкой мыши и выберите Properties в контекстном меню, а в появившемся диалоговом окне — вкладку Security. Отметьте роль Customer, как показано на рис. 17.24. Теперь COM4- использует политику, которая разрешает доступ роли Customer к компоненту Player. Никаким другим пользователям доступ не разрешен. Теперь вы можете вновь запустить программу-клиент. Теперь пользователю amy разрешен вход на shopper.com, а пользователю vicky — нет. И никому из пользователей не разрешен вход на wholesale. com, поскольку он до сих пор не сконфигурирован.
Теперь мы настроим доступ к другому компоненту, EcStubServer. Vendor. В его диалоговом окне пометьте обе роли — и Customer, и Vendor (позже мы ограничим доступ посредством программной проверки). Если вы все еще находитесь на клиентской машине с учетной записью vicky, то теперь должны получить возможность входа на wholesale.com. Более того, поскольку мы дали право доступа обеим ролям, войти на этот узел может любой из наших пользователей. Программная безопасность СОМ+ упрощает программирование проверок безопасности. Это обеспечивается тем, что информация о безопасности является частью контекста. Так же, как вы вызываете методы типа SetComplete и SetAbort объекта контекста, вы можете вызвать в процессе работы метод isCallerlnRole для проверки членства в роли. Рис. 17.24. Передача прав доступа роли Customer компоненту Player Метод IsCallerlnRole является частью интерфейса lObjectContext, использовавшегося MTS. Однако в СОМ+ безопасность передана ее собственным интерфейсам, и предпочтительным является использование интерфейса iSecurityCallContext. В C++ вы получаете указатель на этот интерфейс, вызывая CoGetCallContext. Вы можете вспомнить, что, когда мы обсуждали контекст в СОМ+, говорили, что имеются два вида контекста — контекст объекта и контекст вызова. Первый действует, пока объект остается активным, а второй — только во время определенного вызова. Система безопасности использует контекст вызова. В Visual Basic для получения ссылки на контекст вызова объекта вы используете GetSecurityContext. Ниже приведен фрагмент кода класса поставщика из файла vendor, els, расположенных в каталоге Chapl7\EcStub\Server, демонстрирующий применение IsCallerlnRole в Visual Basic. Как видите, метод получает в качестве параметра строку, указывающую роль, на принадлежность пользователя к которой происходит проверка. В случае нарушения системы безопасности функция возвращает специальное значение перечислимого типа для передачи информации клиенту.
Public Enum LoginCodes ecOK = 0 ecUrlNotFound = 1 eclnvalidID = 2 ecNew = 3 ecOld = 4 ecSecurityViolation = 5 End Enum Public Function Login(ByVal url As String, ByVal id As String) As LoginCodes 'Проверка корректности url и id If url = "toyland.com" Then Login = ecOK Elself url = "petworld.com" Then Login = ecOK Elself url = "foodstore.com" Then Login = ecOK Else Login = ecUrlNotFound Exit Function End If 'Программная проверка принадлежности к роли If (GetSecurityCallContext.IsCallerlnRole("Vendor")) Then If id <> "0" Then Login = ecOK Else Login = eclnvalidID End If Else Login = ecSecurityViolation End If End Function В этом примере, перед тем как приступить к проверке безопасности, мы проверяем значение URL на соответствие жестко заданным в программе значениям. Эта часть кода может выполняться любым клиентом из ролей Customer и Vendor, а оставшаяся часть кода — только пользователем из роли Vendor. Код клиента явным образом проверяет возвращаемое значение для определения произошедшей ошибки (см. f rmVendorLogin. f rm из каталога Chapl7\EcStubClient). Dim objVendor As New Vendor Dim result As LoginCodes result = objVendor.Login(txtURL, txtVendorlD) If result = ecUrlNotFound Then MsgBox txtURL & " not found, try again!", , "Login" txtVendorlD.SetFocus SendKeys "{Home}+{End}" Exit Sub Elself result = ecSecurityViolation Then MsgBox "Security violation!", , "Login" txtVendorlD.SetFocus SendKeys "{Home}+{End}" Exit Sub End If
Попытайтесь запустить клиентскую программу на машине с Windows 2000 Professional. amy и vicky являются двумя различными пользователями: amy, в отличие от vicky, не может успешно работать с программой, поскольку не является членом роли Vendor. Работа сервера в СОМ+ Мы запускаем программу-клиент под разными учетными записями пользователей; что касается сервера, то он по умолчанию работает как интерактивный пользователь. Как пояснялось при рассмотрении системы безопасности СОМ, интерактивный пользователь — это пользователь, работающий на машине в настоящее время. Для такого пользователя создается станция Windows, обеспечивающая графический интерфейс пользователя и позволяющая выводить окна сообщений. В качестве маленького эксперимента добавим код к классу поставщика, выводящий окно сообщения при создании объекта поставщика. Теперь запустим клиент на удаленной машине и посетим wholesale.com. Вы должны услышать гудок с машины-сервера и увидеть на панели задач задачу, соответствующую окну сообщения, которое вы можете просмотреть и закрыть. Когда вы закроете окно сообщения на сервере, на машине клиента появится окно сообщения о входе на узел. Теперь, продолжая эксперимент, завершим работу на сервере, оставив машину работать дальше. Вновь попытаемся запустить клиент для работы с сервером. На этот раз появится окно с сообщением об ошибке, показанное на рис. 17.25, которая возникла потому, что сервер сконфигурирован для работы в качестве интерактивного пользователя, но такового в системе не оказалось. Рис. 17.25. Попытка запуска сервера при отсутствии пользователя приводит к ошибке Вновь войдем на сервер — на этот раз с учетной записью администратора (при этом вы снова получаете доступ к серверному приложению). Перейдем в СОМ+ Explorer и вызовем диалоговое окно Properties для приложения EcStubApp. Выберем вкладку Identity и изменим учетную запись на ранее созданную для пользователя bob, как показано на рис. 17.26 (не стоит использовать здесь учетную запись Administrator). Щелкните на кнопке ОК. Теперь опять завершите сеанс работы на сервере и вновь запустите клиент. Запуск клиента пройдет успешно; не менее успешно вы сможете посетить узел shopper.com. Попробуйте теперь посетить узел wholesale.com — клиент при этом "зависнет". Это связано с тем, что сервер выводит окно сообщения, которое оказывается не на станции Windows интерактивного пользователя, а следовательно, оно не выводится на экран и, соответственно, не может быть закрыто. Все, что вы можете сделать — так это воспользоваться Task Manager для того, чтобы завершить работу зависшего клиента. Эта маленькая неприятность заставит вас прочно усвоить простую истину — никогда не используйте окна сообщений в серверных компонентах. Не стоит делать этого даже при отладке — лучше используйте вместо окон вывод в файл, как мы поступали в главах 12, "Обработка ошибок и отладка" и 15, "Урок СОМ+".
Рис. 17.26. Определите, под именем какого пользователя будет работать сервер Если вы вновь взглянете на рис. 17.26, то увидите, что СОМ+, в отличие от СОМ, предоставляет вам не три, а только два варианта выбора. Третий выбор — работа от имени запускающего пользователя — отсутствует, так как при нем требовалось создание станции Windows для каждого клиента, и, кроме того, быстро исчерпывались подключения к базе данных, перекрывая возможность масштабирования приложения. Имперсонация Мы уже рассматривали концепцию имперсонации при обсуждении системы безопасности СОМ. Здесь же мы обратимся к вопросу о том, когда следует и когда не следует прибегать к имперсонации, а также каким образом программа должна ее использовать Для более глубокого понимания имперсонации рассмотрим гипотетическое четырехуровневое приложение, показанное на рис. 17.27. Клиент С вызывает сервер S1, который вызывает сервер S2, а этот, в свою очередь — сервер S3. Обозначим этих три последовательных вызова как X, Y и Z. с X S1 Y ► S2 Z ► S3 Рис. 17.27. Четырехуровневое приложение— кандидат для имперсонации Вопрос заключается в том, с какими мандатами безопасности выполняется каждый вызов? Для вызова X ответ всегда один и тот же — с мандатом клиента С. Теперь, если сервер S1 работает под своей собственной учетной записью, у него будет свой собственный набор мандатов безопасности. Если уровень имперсонации — Impersonate или Delegate, SI может имперсонировать С и вызов Y также будет выполняться с мандатами безопасности С. Различия между Impersonate и Delegate проявятся на еле-
дующем этапе. Если используется уровень Impersonate, вам разрешен только один удаленный вызов и Z не может работать с мандатами безопасности С. При применении в Windows 2000 в качестве провайдера поддержки безопасности Kerberos допускается уровень имперсонации Delegate, и даже в случае удаленных вызовов могут продолжать использоваться мандаты безопасности исходного клиента. Зачем нам может понадобиться имперсонация? Краткий ответ: "Пригодится!" Им- персонация имеет несколько недостатков. Во-первых, она дорого обходится с точки зрения производительности, так как для проверок системы безопасности должно выполняться несколько удаленных вызовов. Во-вторых, при этом вы слишком поздно обнаруживаете возможную некорректность операции обращения. Причина использования имперсонации может заключаться в обращении к приложению сервера, чья система безопасности построена на использовании мандатов конечного клиента. Предположим, например, что S3 — код базы данных, который при разрешении подключения к базе данных и выполнении критичных операций основывается на личности клиента. Без перестройки приложения S2 должен имперсонировать клиент С. И тут вступает в действие третий недостаток имперсонации. Каждый новый клиент будет работать с сервером S2, используя свое подключение к базе данных, а значит, не позволяя воспользоваться пулом подключений, лишая тем самым нас основного преимущества многоуровневых приложений перед приложениями клиент/сервер. Вероятно, после этого рассказа вы вряд ли пожелаете использовать имперсонацию. А если все же захотите, то как должны ее реализовать? Оказывается, это очень просто — требуется только добавить небольшой дополнительный код к клиенту и серверу. Мы не будем детально рассматривать этот код (об этом можно прочитать в документации), а только вкратце укажем вызовы, которые вы должны сделать. Клиент На стороне клиента следует принять во внимание два соображения. Первое — уровень имперсонации. Клиент должен указать свою готовность к использованию его со стороны сервера. Второе соображение заключается в том, что активный каталог поддерживает возможность заметить учетную запись пользователя как ту, которая не может быть делегирована. Если клиент не указывает уровень имперсонации программно, используется значение по умолчанию, устанавливаемое на данном компьютере с помощью СОМ+ Explorer. Щелкните правой кнопкой мыши на My Computer и выберите в появившемся контекстном меню команду Properties. В диалоговом окне My Computer Properties выберите вкладку Default Properties и в самом низу определите уровень имперсонации, используемый по умолчанию. Заметьте, что это окно обеспечивает глобальное включение и отключение DCOM на машине (рис. 17.28). Клиент может изменить уровень имперсонации с помощью вызова API функции CoGetProxyBlanket, которая может вызываться так часто, как это будет необходимо. Уровень имперсонации может также определяться посредством вызова функции ColnitializeSecurity, которая может вызываться процессом только один раз. Сервер Сервер может начать использование мандатов безопасности клиента, вызвав ColmpersonateClient. Когда сервер выполнит имперсонацию клиента, он может вновь использовать собственные мандаты безопасности, вызвав CoRevertToSelf.
Рис. 17.28. Определение уровня имперсона- ици по умолчанию, используемого на данном компьютере Резюме В этой достаточно длинной главе представлен обзор систем безопасности Windows 2000 и СОМ+. Мы не рассмотрели множество деталей, но к этому моменту у вас должно сложиться ясное представление о фундаментальных принципах систем безопасности. Нами рассмотрены такие базовые понятия системы безопасности, как авторизация и аутентификация. Мы рассмотрели систему безопасности СОМ, которая определяет права доступа для запуска и активизации, уровни имперсонации и аутентификации. Эти значения хранятся в системном реестре и могут быть установлены с помощью инструмента DCOMCNFG. СОМ+ добавляет к системе безопасности множество новых атрибутов, устанавливаемых в СОМ+ Explorer либо изменяемых программно с помощью объектов СОМ+ Admin. Эти атрибуты обеспечивают существенно большую гибкость системы безопасности СОМ+ по сравнению с СОМ. Безопасность СОМ+ настраивается на уровне приложения, компонента, интерфейса и метода. СОМ+ предоставляет новую высокоуровневую абстракцию — роли. Политика системы безопасности при этом определяется назначением пользователей ролям, а компоненты при этом определяют права доступа ролей. Роли также могут быть запрограммированы с использованием контекста вызова. В этой главе мы использовали в качестве иллюстрации упрощенную версию Electronic Commerce Game™. Реальная версия требует использования баз данных, на работу которых в среде Microsoft мы и обратим ваше внимание в следующей главе. Там же мы рассмотрим, каким образом СОМ+ облегчает программирование баз данных.
Глава 18 SQL Server и ADO Подавляющее большинство коммерческих программ поддерживает работу с базами данных. В этой главе мы рассмотрим основы технологии Microsoft, касающиеся работ с базами данных и необходимые для доступа к базам данных с применением СОМ+. Несомненно, архитектура СОМ+ не зависит от конкретной базы данных и может работать с любой из них (например, с базами данных Oracle). Однако для удобства во всех наших примерах, связанных с базами данных, мы будем использовать технологию Microsoft, тем самым получим завершенную картину Windows DNA. Начнем мы с обзора SQL Server 7.0, представляющего собой простую в использовании систему управления базами данных. Затем покажем, как создать базу данных, определить ее схему и выполнить запросы; обсудим программирование баз данных; рассмотрим глобальный подход Microsoft, который называется унифицированным доступом к данным (Uniform Data Access — UDA), и, наконец, перейдем к ODBC, OLE DB и ADO. Будет рассмотрен краткий курс программирования баз данных с использованием ADO, включая пример трехуровневого приложения. В данной главе имеются все необходимые сведения для реализации Electronic Commerce Game™, с которой мы будем работать в оставшейся части книги. Эта глава завершает наше рассмотрение реализации Electronic Commerce Game™. Основы SQL Server 7.0 SQL Server 7.0 представляет собой стратегическую платформу баз данных Microsoft. Дороги Microsoft и Sybase разошлись несколько лет назад, после чего Microsoft начала продвижение SQL Server на своих платформах. Текущая версия продукта — SQL Server 7.0 — отличается простотой в использовании. Теперь SQL Server работает также под управлением Windows 95/98. Это — единственная СУБД, с которой мы будем работать в данной книге. SQL Server 7.0 прост в установке. Я предлагаю установить его на каждом из ваших тестовых компьютеров. Это позволит вам тестировать ваши приложения на любом компьютере, а также провести тестирование распределенных транзакций, рассматриваемых в следующей главе. Дисковые затраты для SQL Server 7.0 невелики, порядка 100 Мб для настольной версии вполне достаточно для выполнения ставящихся в этой книге задач. В любом случае для работы с книгой вам придется установить SQL Server 7 0 по меньшей мере на одном компьютере. При инсталляции вы можете принять все предлагаемые по умолчанию установки. После инсталляции первое, что вы должны сделать, — это запустить сервис MSSQLServer. Это можно выполните с помощью команды меню Start^Programs^Microsoft SQL Server Z.O^Service Manager. Вызываемый
аплет позволит вам запустить три сервиса, связанных с SQL Server (два других — SQLServerAgent и MSDTC). Запустите все три сервиса. Поскольку мы будем работать с базами данных в нескольких последующих главах, стоит настроить автозапуск сервисов при загрузке системы, как показано на рис. 18.1. Рис. 18.1. Запуск сервисов SQL Server с использованием Service Manager Анализатор запросов Следующим используемым нами инструментом из группы Microsoft SQL Server 7.0 является Query Analyzer. Это простой в применении инструмент запросов, в котором в одной панели вводится SQL-запрос, а в другой панели выводится результат запроса. Кроме того, вы можете загрузить сценарии SQL из файла и выполнить их. Вызовите Query Analyzer. Перед вами появится диалоговое окно для подключения к SQL Server. Воспользуйтесь выбором (local) для подключения к локальному серверу. После того как вы зарегистрируете в сети другие серверы, Query Analyzer можно будет использовать для подключения к ним и выполнения запросов. Регистрировать другие серверы мы научимся в следующем разделе, а пока примите параметры аутентификации по умолчанию, используя имя sa и пустой пароль. В этом разделе нас, в первую очередь, интересует то, как функционирует база данных, а не вопросы безопасности. Щелкните на кнопке ОК (рис. 18.2). Рис. 18.2. Подключение к серверу Теперь перед вами откроется новое окно запроса, в котором вы можете ввести запрос и выполнить его (с помощью кнопки панели инструментов, на которой изображена клиновидная зеленая стрелка). Кроме того, вы можете воспользоваться командой меню Query ^Execute или клавишей <F5>. Microsoft поставляет тестовую базу данных pubs,
которую можно использовать для тестирования до того, как будет создана ваша собственная база данных. Выберите эту базу данных в выпадающем списке в правом верхнем углу окна, затем введите простейший запрос select * from authors и запустите его на выполнение. Откроется новая панель с результатами запроса (рис. 18.3). Рис. 18.3. Выполнение простого запроса в Query Analyzer Enterprise Manager Enterprise Manager, как следует из названия, позволяет управлять всеми SQL- серверами, установленными на предприятии, с единственной консоли. Enterprise Manager входит в группу Microsoft SQL Server 7.0 и может быть запущен с помощью меню Starts Programs^ Microsoft SQL Server 7.(^Enterprise Manager. Инсталлировав SQL Server 7.0 на новом компьютере, на экране Enterprise Manager вы увидите только один сервер, как показано на рис. 18.4. Рис. 18.4. Изначально в окне Enterprise Manager можно увидеть только один SQL Server 7.0
Другие серверы могут быть добавлены с помощью щелчка правой кнопки мыши на Microsoft SQL Server и выбора New SQL Server Registration в контекстном меню. Вызываемая программа-мастер позволяет вам добавить другие серверы. После регистрации других серверов последние становятся доступными из Query Analyzer — теперь вы можете выполнять запросы к другим серверам по сети. Управление базами данных с использованием SQL Server 7.0 Теперь, когда SQL Server 7.0 установлен и запущен, вы можете начать создание и управление собственной базой данных. В этом разделе мы рассмотрим создание базы данных, используемой в Electronic Commerce Game™. Такого рода базы данных будут также использоваться в некоторых небольших программных примерах. Вначале мы разработаем схему этих без данных, а затем воспользуемся Enterprise Manager и Query Analyzer для создания баз данных, добавления таблиц и заполнения их некоторыми тестовыми данными. В нашей работе будут использоваться подготовленные сценарии. Базы данных для Electronic Commerce Game ™ В Electronic Commerce Game™ используются три типа баз данных. В этом разделе мы дадим краткое описание каждой базы данных. History База данных History используется для записей об игроках, принимавших участие в игре. Она не записывается при игре, но в состав игры входит специальная программа, позволяющая администратору переместить игроков из активной базы данных в базу данных History (эта программа будет описана в следующей главе). База данных History очень проста и легко создается вручную с помощью Enterprise Manager. В ней содержится всего одна таблица Players со столбцами name, balance и num_games. Столбец name используется в качестве первичного ключа. Game База данных Game хранит информацию об игроках и совершенных ими приобретениях. Имеется в ней также и список поставщиков. Всего в базе данных пять таблиц. 1. Players, соответствующая таблице Players базы данных History, с дополнительным полем active для указания того, находится данный игрок в настоящий момент в игре или нет. 2. Shopping обеспечивает начальный список покупок для каждого игрока. 3. Products предоставляет главный список всех продуктов и их цен. 4. items содержит информацию о покупках каждого игрока. 5. Vendors предоставляет список поставщиков, у которых покупатели могут приобретать продукты.
Базы данных поставщиков Имеется три базы данных поставщиков: Toyland, Petworld и Foodstore. Эти базы данных могут быть созданы на разных машинах, разделяют общую схему и состоят из двух таблиц. 1. Info, дающая URL и текущий счет поставщика (одна строка). 2. inventory, представляющая список продуктов у поставщика, включая цену и их количество на складе. Создание базы данных Создать базу в SQL Server 7.0 чрезвычайно просто. Нам не надо беспокоиться ни о схеме, ни о начальном выделении пространства для базы данных (SQL Server 7.0 сам увеличивает размер базы данных при необходимости). Мы создадим пять баз данных: History, Game, Toyland, Petworld и Foodstore. В Enterprise Manager раскройте дерево просмотра для того, чтобы увидеть все базы данных на вашей машине (рис. 18.5). Рис. 18.5. Управление базами данных с использованием Enterprise Manager Щелкните правой кнопкой мыши на Databases и выберите в контекстном меню New Database. Введите в качестве имени новой базы данных History, примите предлагаемые по умолчанию настройки и щелкните на кнопке ОК (рис. 18.6). Создание таблицы Помимо базы данных, Enterprise Manager позволяет создавать таблицы. Щелкните правой кнопкой мыши на вашей новой базе данных History и выберите в контекстном меню New^>Table. Введите в качестве имени новой таблицы Players, а также следующую информацию, определяющую три столбца таблицы.
Column Name name balance nurn games Data Type char money int Length 30 ~ Рис. 18.6. Создание новой базы данных в Enterprise Manager Отмените опции Allow Nulls для всех столбцов. Затем щелкните правой кнопкой мыши на столбце name и в контекстном меню выберите Set Primary Key. Теперь схема вашей новой таблицы должна выглядеть так, как показано на рис. 18.7. Рис. 18.7. Определение схемы таблицы Вставка данных в таблицу Вставить данные в таблицу можно либо с помощью Enterprise Manager, либо с помощью Query Analyzer. Мы испробуем оба способа, но начнем с Enterprise Manager. Выберите на левой панели Tables, при этом на правой панели отобразится список таблиц. Выберите Players (учтите, что помимо вашей появится огромное количество
системных таблиц), щелкните на ней правой кнопкой мыши и выберите в контекстном меню Open Tabled Return all rows. В появившемся окне вы сможете вводить ваши данные. Введите несколько строк, как показано на рис. 18.8. Рис. 18.8. Ввод данных в таблицу с использованием Enterprise Manager Кроме того, вы можете воспользоваться Query Analyzer для вставки данных в виде оператора SQL insert. Мы уже видели, как Query Analyzer выполняет запрос select. Теперь вновь вызовем Query Analyzer и выберем базу данных History, а затем введем следующий запрос: insert into players values ('carl', 5000, 0) Вы можете выполнить этот запрос. Если хотите, после него можете выполнить запрос select и убедиться в том, что введенные вами данные действительно внесены в таблицу. Воспользуйтесь кнопкой Save, расположенной на панели инструментов, или соответствующей командой меню File и сохраните ваш запрос в файле под именем insert_history.sql (каталог Chapl8\Demos). Копия всех используемых в данной главе сценариев имеется в каталоге CaseStudy\SqlScripts. Создание и использование сценариев SQL Схему нашей базы данных History мы определили вручную, но это может оказаться весьма сложным для больших баз данных, в особенности распределенных по нескольким машинам. Для автоматизации этого процесса вы можете использовать сценарии SQL. Мы покажем пример использования сценария, созданного при сохранении запроса, введенного в Query Analyzer. Сценарии могут также создаваться в Enterprise Manager, что особенно удобно, если понадобится повторно создать схему базы данных. Создание сценариев В качестве иллюстрации мы создадим сценарий, воссоздающий схему базы данных History. Выберите в дереве просмотра опцию History и щелкните на ней правой кнопкой мыши. Выберите в контекстном меню All Tasks^Generate SQL Scripts. В появляющемся диалоговом окне выберите вкладку General и определите, что именно вы хотите записать в сценарий. В нашем случае это — все таблицы (опция All Tables) (рис. 18.9). Затем перейдите во вкладку Options и отметьте Script PRIMARY Key..., а затем щелкните на кнопке ОК. В диалоговом окне Save As перейдите в каталог Chapl8\Demos и сохраните ваш сценарий под именем create_history_tables.sql. Вот как будет выглядеть созданный сценарий. if exists (select * from sysobjects where id = object_id(N1[dbo].[Players]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Players] GO CREATE TABLE [dbo].[Players] ( [name] [char] (30) NOT NULL , [balance] [money] NOT NULL , [num_games] [int] NOT NULL ) ON [PRIMARY] GO ALTER TABLE [dbo].[Players] WITH NOCHECK ADD CONSTRAINT [PK_Players] PRIMARY KEY NONCLUSTERED ( [name] ) ON [PRIMARY] GO Рис. 18.9. Генерация сценария создания таблиц При сравнении с вашим собственным сценарием убедитесь, что сгенерирована часть alter table, обеспечивающая наличие первичного ключа в таблице. Использование сценариев Использовать сценарий очень просто — достаточно загрузить его в Query Analyzer и выполнить. Единственная трудность состоит в том, чтобы подключиться к нужной вам базе данных. В качестве примера подключитесь к другому SQL-серверу, создайте там базу данных History и запустите сценарий создания таблиц, а затем внесите во вновь созданную таблицу пару строк с данными. Настройка баз данных для Electronic Commerce Game тм Теперь, когда вы научились работать со сценариями, задача настройки баз данных, требующихся для Electronic Commerce Game™, представляется очень простой. В этом разделе мы создадим базы данных, таблицы и внесем начальные данные. Итак, прежде всего решите, на какой машине будет выполняться работа с базами данных. Веро-
ятно, это будет машина под управлением Windows 2000 Server. В этом разделе мы поговорим о том, как настроить базы данных, а о программах, работающих с базами данных, — немного позже. Все необходимые для настройки сценарии вы найдете в каталоге CaseStudy\SqlScripts. Создание баз данных 1. Используйте Enterprise Manager для создания базы данных Game и трех баз данных поставщиков — Toyland, Petworld и Foodstore. 2. Используйте Query Analyzer для запуска ряда сценариев. Вначале запустите create_game_tables . sql для создания таблиц базы данных Games. 3. Запустите insert_game_data.sql для вставки начальных данных в базу данных Games. 4. Запустите create_vendor_tables.sql для создания таблиц базы данных Toyland, а после этого — insert_toyland. sql. 5. В случае двух других баз данных поставщиков (Petworld и Foodstore) после запуска create_vendor_tables. sql выполните соответствующие сценарии для внесения данных. Тестирование баз данных Теперь мы выполним пару тестовых запросов, чтобы убедиться, что создание баз данных и заполнение их данными выполнено успешно. Вначале выполните запрос select * from inventory к базе данных Toyland. Вы должны получить следующий результат: item airplane toy beanie baby elephant gun price 15.0000 25.0000 55.0000 quantity 50 50 50 (3 row(s) affected) Теперь выполните запрос select * from products к базе данных Game. Вы должны получить следующий результат: item airplane toy beanie baby cat carrier dog bone elephant gun fruit basket price 10.0000 20.0000 30.0000 5.0000 50.0000 10.0000 (6 row(s) affected) Унифицированный доступ к данным Теперь, когда у нас есть несколько настроенных баз данных, самое время перейти к вопросам программирования баз данных. Сперва мы вкратце рассмотрим архитектуру обращения к данным Microsoft. Microsoft называет свою архитектуру унифициро-
ванным доступом к данным (Uniform Data Access). Его цель состоит в предоставлении согласованного набора программных интерфейсов, которые могут использоваться множеством клиентов для обмена информацией с различными источниками данных, включая как реляционные, так и нереляционные данные. ODBC Первым шагом Microsoft в этом направлении был стандарт открытого подключения к базам данных (Open Database Connectivity — ODBC). ODBC предоставляет интерфейс на языке С к реляционным базам данных. На рис. 18.10 показана схема архитектуры ODBC. Приложение Менеджер драйверов Драйвер Источник данных Драйвер Интерфейс ODBC Источник данных Рис. 18.10. Архитектура ODBC Благодаря этой архитектуре приложение может обращаться к различным реляционным базам данных с использованием одного и того же интерфейса С. Стандарт ODBC широко распространен, и большинство реляционных баз данных предоставляет драйверы ODBC. Кроме того, имеются драйверы ODBC и для некоторых нереляционных источников данных, таких как таблицы Excel. Несмотря на все свои достоинства у описанного подхода имеется два основных недостатка. Первый состоит в его ограниченности реляционными базами данных. Хотя, как мы упоминали, и имеются ODBC-драйверы для нереляционных источников данных, эмуляция реляционности — слишком трудная задача для ODBC-драйвера. Второй недостаток заключается в интерфейсе С, что требует от программистов на других языках для использования ODBC разрабатывать промежуточный интерфейс с языком С.
OLEDB Улучшенная стратегия Microsoft основана на СОМ. Как мы видели, СОМ обеспечивает интерфейс, не зависимый от языка программирования, основанный на бинарном стандарте. Таким образом, любое решение, основанное на СОМ, увеличивает гибкость с точки зрения клиентской программы. Множество интерфейсов СОМ для работы с базами данных известно как OLE DB (еще с тех времен, когда OLE представляла собой всеохватывающую технологию). Еще одна важная особенность состоит в том, что множество OLE DB не специфично для реляционных баз данных. Любой источник данных, который намерен предоставлять данные клиенту, должен реализовать провайдер OLE DB. OLE DB предоставляет большую часть функциональности баз данных, включая курсоры и реляционные запросы. Этот код не должен реплицироваться различными провайдерами, в отличие от случая драйверов ODBC. Клиенты OLE DB известны как потребители (consumers). Первый провайдер OLE DB был разработан для ODBC, что сразу же обеспечило возможность доступа потребителей OLE DB к базам данных, для которых имелись драйверы ODBC (хотя и требовало дополнительного слоя между клиентом и базой данных). В последнее время появилось множество "родных" драйверов OLE DB для разных баз данных, включая SQL Server и Oracle. Имеется драйвер OLE DB и для баз данных Microsoft Jet, обеспечивающий эффективный доступ к настольным базам данных типа Access или dBase. Некоторые объектные базы данных типа ObjectStore также имеют собственных провайдеров OLE DB ActiveX Data Objects Хотя COM и основана на бинарном стандарте, не все языки в равной степени приспособлены для разработки СОМ. СОМ в своем "ядре" похожа на C++, в частности, использованием механизма виртуальных таблиц. Соответственно, использование C++ наиболее эффективно в особенности при работе со структурами и указателями. Но дело обстоит совсем не так при работе с другими языками программирования, например с Visual Basic. Если вы предоставляете двойной интерфейс, который ограничен типами данных, совместимых с автоматизацией, доступ к вашим компонентам будет проще осуществить со стороны программ на Visual Basic. Стандарт OLE DB создан для обеспечения максимальной эффективности программ на C++. Для обеспечения простого в использовании интерфейса для Visual Basic Microsoft разработала объекты данных ActiveX (ActiveX Data Objects — ADO). Объекты данных ActiveX подобны объектам доступа к данным (Data Access Objects — DAO), которые предоставляют простую в использовании объектную модель для работы с Microsoft Jet. Новая модель ADO имеет ряд достоинств. Одно из них заключается в простоте ее использования, без излишних перемещений по иерархии объектов. Но главное достоинство модели ADO состоит в том, что она базируется на OLE DB и, таким образом, дает программисту богатые возможности в плане обращения к разным источникам данных. Конечный результат использования этой технологии заключается в очень широком диапазоне интерфейсов, доступных программисту. Обращаясь к SQL Server, вы можете выбирать из пяти основных программных интерфейсов. Один из них — это внедренный SQL, обрабатываемый препроцессором. Остальных четыре представляют собой интерфейсы времени выполнения (рис. 18.11). Библиотека DB является библиотекой времени выполнения для работы с SQL Server. Она представляет собой слой поверх механизма SQL Server, интерфейс кото-
рого скрыт от приложений. Библиотека DB — С-интерфейс, привязанный к SQL Server и не работающий ни с другими базами данных, ни с нереляционными источниками данных. Вторым интерфейсом является ODBC, который, как мы видели, также представляет собой С-интерфейс, но в состоянии работать со многими различными реляционными базами данных. 1 г 1 г ADO 0LEDB ODBC Приложение 1 г 1 г Библиотека DB SQL Server Рис. 18.11. Интерфейсы для обращения к SQL Server Если приложение разработано для применения OLE DB, оно может работать со множеством реляционных и нереляционных источников данных. Множество источников данных, к которым можно обратиться посредством OLE DB, представляет собой надмножество источников данных, к которым можно обратиться посредством ODBC, поскольку имеется провайдер OLE DB для ODBC. SQL Server предоставляет свой собственный провайдер, так что приложение, использующее OLE DB, работает с SQL Server практически непосредственно. Как мы уже отмечали, провайдеры OLE DB предусмотрены для множества баз данных, включая Oracle; кроме того, постоянно появляются новые провайдеры для различных источников данных.
И, наконец, представляющая собой интерфейс высшего уровня технология ADO. Она очень проста в применении, и именно ее мы будем использовать в этой книге для иллюстративных целей. О программировании ADO мы поговорим немного позже. Урок программирования баз данных Теперь следует хотя бы вкратце рассмотреть такой важный вопрос, как программирование баз данных. Мы напишем некоторый код для обращения к нашей базе данных History. Цель этого урока — научиться пользоваться простейшими технологиями работы с базами данных. Сейчас нас не интересуют вопросы оптимизации производительности, а потому в качестве программного интерфейса мы воспользуемся ADO, а программа-клиент будет написана на Visual Basic. Создание источника данных ODBC Нашу работу мы начнем с создания источника данных ODBC. Вызовите ODBC Data Source Administrator, что в Windows 2000 Server делается с помощью команды меню Start^Programs^Administrative Tool. В Windows 2000 Professional этот инструмент также находится в группе административного инструментария, добраться к этому инструментарию можно через Control Panel (сказанное здесь относится к бета-версии Windows 2000, в окончательной версии Windows 2000 путь к ODBC Data Source Administrator может оказаться одинаковым). Выберите вкладку System DSN, показанную на рис. 18.12. Рис. 18.12. ODBC Data Source Administrator Для подключения к источнику данных ODBC вам понадобится имя источника данных (Data Source Name — DSN). Имеется три типа DSN: User, System и File. Первых два типа хранятся в бинарном виде операционной системой, а последний — в текстовом файле. Это не столь эффективно, зато очень удобно в плане распространения между различными компьютерами. Пользовательское DSN работает только с учетной записью, под которой создано, в то время как системное DSN пригодно для всех учетных записей системы. Нашим первым примером будет системное DSN; с файловым DSN мы познакомимся позже
Итак, приступим к добавлению нового системного DSN. Щелкните на кнопке Add и выберите SQL Server в появляющемся диалоговом окне Create New Data Source, а затем щелкните на кнопке Finish. Следующее диалоговое окно, показанное на рис. 18.13, называется Create a New Data Source to SQL Server. Рис. 18.13. Создание нового DSN для SQL Server Введите sysHistory в качестве имени и выберите локальный SQL Server. Если вы хотите создать многоуровневое приложение с источником данных на другом компьютере, то можете выбрать в этом поле удаленный компьютер. Щелкните на кнопке Next и в следующем диалоговом окне выберите аутентификацию SQL-сервера с идентификатором sa, как показано на рис. 18.14. Рис. 18.14. Для простоты мы используем аутентификацию SQL-сервера Щелкните на кнопке Next и в следующем диалоговом окне измените базу данных по умолчанию на History, но примите при этом все остальные предлагаемые настройки (рис. 18.15). Щелкните на кнопке Next, а затем — на Finish. В последнем диалоговом окне вы можете протестировать новый источник данных. Щелкайте на кнопке ОК, пока не покинете ODBC Data Source Administrator.
Административная программа для базы данных History Нашим примером будет простая программа с графическим интерфейсом пользователя для администрирования этой маленькой базы данных. Предоставляемый графический интерфейс пользователя позволяет находить, удалять или обновлять запись, а также показывать все записи базы данных. Программа написана на Visual Basic с использованием ADO и находится в каталоге Chapl8\HistoryAdminMonolithic. Это простая программа типа клиент/сервер, в качестве сервера которой выступает SQL Server. Позже мы переделаем ее в трехуровневое приложение. Рис. 18.15. Выберите базу данных History в качестве вашего нового источника данных Перед тем как приступить к изучению программы, запустим ее (помимо прочего это послужит дополнительным тестом корректности настройки вашей базы данных и DSN). Вызовите программу в Visual Basic и запустите ее. Если ранее мы внесли в базу данных запись об игроке carl, то можем найти ее, щелкнув на кнопке Find, как показано на рис. 18.16. Рис. 18.16. Административная программа для базы данных History Теперь можно испытать различные возможности программы. Щелкните на кнопке Show Players для вывода окна с информацией о всех игроках. Попробуйте добавить игроков, удалить, обновить информацию об игроке, а затем опять вызвать окно с полной информацией и убедитесь в корректности внесенных изменений. Конечно, все то же самое вы можете сделать и с помощью Query Analyzer.
Программирование с использованием ADO На сегодняшний день самый популярный интерфейс Microsoft для программирования баз данных — ADO. Он располагается поверх OLE DB и таким образом, подключается к любой базе данных или другому источнику данных, для которого существует провайдер OLE DB. Сами по себе ADO являются потребителями OLE DB. В этом разделе мы рассмотрим объектную модель ADO и некоторые примеры программирования баз данных, использованные рассмотренной выше административной программе. Как уже говорилось, мы будем работать с Visual Basic. Убедитесь, что в ваш проект добавлена ссылка на Microsoft ActiveX Data Objects Library (выберите при этом последнюю версию библиотеки). В момент написания этой книги последней была версия 2.5. Объектная модель ADO ADO представляют собой семейство классов СОМ ("объектов"), поддерживающих двойные интерфейсы. Таким образом, ADO могут быть доступны как из сценариев, так и из языков программирования с ранним связыванием. Это означает, что ADO может использоваться в Active Server Pages и, следовательно, работать на среднем уровне Web- приложений, обеспечивая средство обмена информацией с уровнем данных (о чем мы более подробно поговорим в главе 20, "Использование СОМ+ в Web-приложениях"). Классы в ADO представлены в виде иерархии, как показано на рис. 18.17, где для ясности иерархия показана в несколько упрощенном виде. В отличие от предыдущих иерархий, таких как иерархия DAO, эта представляется более однородной, нестрогой и очень простой в использовании. Connection Errors Error Command Parameter Recordset Field Рис. 18.17. Упрощенная объектная модель ADO Объектная модель содержит как индивидуальные объекты, так и коллекции объектов. На диаграмме коллекции представлены в заштрихованном виде. Parameters Fields
Connection Для подключения к источнику данных вам необходим объект подключения Connection. Либо вы создаете его явным образом, либо некоторый другой объект создает его неявно. Самое важное свойство этого объекта — ConnectionString. Оно используется провайдером OLE DB для подключения к источнику данных. Одно из приятных свойств ODBC заключается в том, что строка подключения при этом чрезвычайно проста — в нашем случае это "sysHistory". Строка подключения передается в качестве первого параметра методу Open. ADO выполняет объединение подключений в пул. Это означает, что если вы подключаетесь к источнику данных с использованием определенной строки, затем уничтожаете объект Connection, а позже вновь обращаетесь к той же базе данных, используя ту же строку, система ADO может не устанавливать заново соединение с базой данных, а применять уже существующее, взяв его из пула подключений. Как говорилось в главе 17, "Windows 2000 и безопасность СОМ+", это одна из причин того, почему не стоит использовать имперсонацию при необходимости масштабирования приложения. При имперсонации нескольких клиентов сервером каждый из них подключается к базе данных, используя собственную строку, указывающую идентификатор пользователя и пароль, и применение пула подключений невозможно. Однако, если компонент среднего уровня работает с собственной учетной записью, все подключения используют одинаковый идентификатор пользователя и пароль, и подключения к базе данных могут быть помещены в пул. Будучи созданным, объект Connection может быть либо использован непосредственно — с помощью вызова метода Execute, либо передан объекту Recordset или Command. Следующий фрагмент кода иллюстрирует простое использование объекта Connection. Он используется в программе History Admin для получения информации обо всех игроках. Public Function GetAll() As Recordset Dim conn As New Connection conn.Open "sysHistory" Set GetAll = conn.Execute("Players") End Function Создается объект Connection, вызывается метод Open, которому передается строка подключения. После этого вызывается метод Execute, которому передается имя таблицы. Этот вызов возвращает все данные из таблицы в виде набора записей. Метод Execute может также принимать строку, представляющую оператор SQL. Recordset В ADO, как и в других интерфейсах баз данных Microsoft, для получения данных из базы данных используется набор записей (recordset), который может состоять из множества строк и множества столбцов (полей). Таким образом объект Recordset содержит коллекцию Fields объектов Field. Иногда может понадобиться явное обращение к коллекции Fields — например, при опросе источника данных, имена полей которого неизвестны. Коллекция Fields полезна также, если вы хотите создать ваш собственный набор записей для хранения данных вместо заполнения его данными из базы. Однако в большинстве случаев вам не придется явно работать с коллекцией Fields. Вы можете обратиться к конкретному полю, используя его имя в качестве ключа. Таким образом, если rsHistory представляет собой объект набора записей, то rsHistory ("balance") можно использовать для ссылки на поле "balance" таблицы Players.
Набор записей может содержать много строк, и вы можете перемещаться по ним, используя методы типа MoveFirst и MoveNext. При итерации набора записей таким способом вы используете курсор базы данных, который предоставляет провайдер OLE DB. В качестве примера итерации по набору записей и получения данных из отдельных полей рассмотрим следующий код, который заполняет список данными, полученными из таблицы Players базы данных History. (Основные моменты работы с базой данных выделены полужирным шрифтом.) Private Sub Form_Load() 'Таблица Players lvPlayer.ColumnHeaders.Add , , "Name", _ lvPlayer.Width * 0.4 lvPlayer.ColumnHeaders.Add , , "Balance", _ lvPlayer.Width * 0.3 lvPlayer.ColumnHeaders.Add , , "Games", _ lvPlayer.Width * 0.3 lvPlayer.View = lvwReport Dim objHistory As New dbHistory Dim rsHistory As New Recordset Set rsHistory = objHistory.GetAll Dim itmX As Listltem Do While Not rsHistory.EOF Set itmX = lvPlayer.Listltems.Add(, , _ Trim(rsHistory("name"))) itmX.Sublterns(1) = CStr(rsHistory("balance")) itmX.Sublterns(2) = CStr(rsHistory("num_games")) rsHistory.MoveNext Loop rsHistory.Close End Sub В приведенном примере кода набор записей заполняется рассмотренным в предыдущем разделе методом GetAll класса dbHistory. Выбор данных из таблицы Другой вариант наполнения набора записей состоит в явном создании экземпляра объекта Recordset с последующим вызовом Open. Иллюстрация этого метода имеется в приведенном ниже фрагменте кода. Здесь вместо получения всех данных из таблицы Players мы получаем запись об игроке с этим именем (поскольку имя представляет собой первичный ключ, мы получим либо одну, либо ни одной записи). Public Function GetByName(ByVal name As String, _ ByRef balance As Currency, _ ByRef num_games As Integer) As Boolean Dim strSQL As String Dim conn As New Connection Dim rsPlayers As New Recordset conn.Open "sysHistory" 'Поиск имени в таблице Players strSQL = "SELECT * FROM Players WHERE name = " & "'" & name & rsPlayers.Open strSQL, conn, , adLockReadOnly, adCmdText
'Проверка, найдено ли имя If rsPlayers.BOF Then GetByName = False Else balance = rsPlayers("balance") num_games = rsPlayers("num_games") GetByName = True End If End Function Два последних примера иллюстрируют использование свойств bof и eof. Эти значения типа Boolean указывают, находится курсор непосредственно перед первой записью или сразу за последней записью. Если при итерации вы переместились за последнюю строку, EOF становится равным True. Если набор записей пуст, значение True принимают равным как BOF, так и EOF. Обновление записи До сих пор все рассмотренные примеры иллюстрировали только считывание из источника данных. При изменении данных должны быть учтены некоторые дополнительные вопросы. В рассматриваемой нами программе имеются примеры добавления записи, удаления и обновления (изменение одного или нескольких полей существующей записи). Рассмотрим вначале обновление записи. Соответствующий код (приведенный ниже) очень похож на только что рассмотренный нами. Мы выбираем запись по заданному имени, однако вместо чтения записи мы записываем ее. Public Function Update(ByVal name As String, _ ByVal balance As Currency, _ ByVal num_games As Integer) As Boolean Dim strSQL As String Dim conn As New Connection Dim rsPlayers As New Recordset conn.Open "sysHistory" 'Поиск в таблице Players strSQL = "SELECT * FROM Players WHERE name = " & "'" & name & "'" rsPlayers.Open strSQL, conn, , adLockOptimistic, _ adCmdText 'Проверка, найдено ли имя If rsPlayers.BOF Then Update = False Else rsPlayers("balance") = balance rsPlayers("num_games") = num_games rsPlayers.Update Update = True End If End Function Первое отличие заключается в способе открытия набора записей. Раньше параметр блокировки в методе Open был равен adLockReadOnly, поскольку мы только считывали данные, и блокировка не требовалась. Теперь мы записываем данные и просто обязаны использовать блокировку во избежание рассогласованности данных. Имеются
два вида блокировок: оптимистическая и пессимистическая. Первая осуществляет блокировку на минимальное время — только при вызове метода Update. Пессимистическая блокировка устанавливается в момент начала редактирования и удерживается до тех пор, пока вы не завершите работу путем вызова метода Update. Обычно большая масштабируемость достигается при использовании оптимистической блокировки, но тогда требуется, чтобы ваш код работал в случае ошибки, возникающей из-за того, что другой клиент изменил данные. Второе отличие состоит в изменении направления присвоения — в этом случае данные присваиваются набору записей. Последнее отличие заключается в вызове метода Update. Когда вы присваиваете данные набору записей, первоначально они хранятся в буфере, а не в базе данных. Вызов метода Update вносит изменения в саму базу данных. Альтернативой использованию Recordset для обновления записей в базе данных служит применение операторов SQL. Для этого следует создать строку с оператором SQL UPDATE и передать ее методу Execute объекта Connection. Этот подход будет проиллюстрирован в следующей главе. Добавление записи Класс Recordset имеет метод AddNew, который может использоваться для добавления в набор новой записи. Вызов этого метода создает буфер, где хранятся данные новой записи. В базу данных новая запись добавляется при вызове метода Update. Как и при обновлении записи, вы должны воспользоваться блокировкой — оптимистической или пессимистической. Вот пример кода для добавления записи: Public Sub Add(ByVal name As String, _ ByVal balance As Currency, _ ByVal num_games As Integer) Dim rsPlayers As New Recordset rsPlayers.Open "Players", "sysHistory", , _ adLockOptimistic, adCmdTable rsPlayers.AddNew rsPlayers("name") = name rsPlayers("balance") = balance rsPlayers("num_games") = num_games rsPlayers.Update End Sub Как и в случае обновления записи, альтернативный подход состоит в применении SQL. Создайте соответствующий оператор SQL insert и передайте строку с ним методу Execute объекта Connection. Удаление записи И, наконец, вы можете использовать набор записей для удаления записи. Выберите запись, которую следует удалить, и вызовите метод Delete. Удаление воздействует на базу данных немедленно — последующий вызов Update не требуется. Вот пример такого кода: Public Function Delete(ByVal name As String) As Boolean Dim strSQL As String Dim conn As New Connection Dim rsPlayers As New Recordset conn.Open "sysHistory"
'Просмотр таблицы Players strSQL = "SELECT * FROM Players WHERE name = " & " '" & name & rsPlayers.Open strSQL, conn, , _ adLockOptimistic, adCmdText 'Проверка того, найдено ли имя If rsPlayers.BOF Then Delete = False Else rsPlayers.Delete Delete = True End If End Function И опять-таки альтернативный подход заключается в создании оператора SQL DELETE и передаче его методу Execute объекта Connection. Коллекция Errors В процессе работы с ADO могут возникать ошибки различных типов. Поскольку система ADO располагается над системой OLE DB, которая осуществляет вызовы базы данных либо непосредственно, либо через ODBC, ошибки могут возникать в разных уровнях. Так, если работа идет по сети, к списку возможных ошибок добавляются сетевые ошибки. Стандартный механизм сообщения об ошибках СОМ через возвращаемое значение hresult в целом неадекватен для передачи всей необходимой информации. Интерфейсы расширенной информации об ошибках, которые обсуждались в главе 12, "Обработка ошибок и отладка", помогают в решении этой проблемы, но не в состоянии полностью разрешить ее. Таким образом вы можете получить информацию только об одной ошибке. Для того чтобы иметь возможность предоставлять полную информацию о нескольких ошибках, которые могут произойти при одном вызове, технология ADO обеспечивает коллекцию Errors, которая состоит из объектов Error — по одному объекту для каждой ошибки. Коллекция имеет свойство Count, сообщающее о количестве объектов Error в коллекции. Детальную информацию, содержащуюся в отдельном объекте Error, можно получить с помощью свойств объекта, таких как Description. В качестве примера рассмотрим программу из каталога Chapl8\Demos\PriceDemo, которая демонстрирует простой поиск цены в таблице Products базы данных Games. В данном примере используется провайдер OLE DB для SQL Server, который мы обсудим в следующем разделе. Это приводит к несколько перегруженной строке соединения, так что весьма сложно безошибочно создать собственную строку подключения, особенно делая это впервые. Мы преднамеренно допустим ошибку, и допустим не ADO-ошибку. "Безошибочная" версия программы находится в каталоге Chapl8\PriceDemo. Перед тем как приступить к работе с коллекцией Errors, вы должны обратиться к ее свойству Count, для того чтобы убедиться в том, что коллекция непуста. Код демонстрационной программы выводит информацию об ошибке в окне сообщения. Конечно, при разработке реального приложения среднего уровня вы не должны использовать вывод окна сообщения (вместо этого используйте запись в файл, например, с помощью уже рассматривавшегося нами в главе 12, "Обработка ошибок и отладка" компонента Logger). Вот код нашей демонстрационной программы: Private Function FindPrice(ByVal server As String, _ ByVal item As String) _ As Currency On Error GoTo ErrorHandler
Dim strSQL As String Dim rsProd As New Recordset Dim conn As New Connection strSQL = "SELECT * FROM Products WHERE item = " & _ " '" & item & 'strSQL =1/0 conn.Provider = "SQLOLEDB" conn.ConnectionString = "Server=" & server & "; " _ & "Database= Game;" __ & "uid=sa;" _ & "pwd=;" conn.Open rsProd.Open strSQL, conn, , adLockReadOnly, adCmdText If rsProd.BOF Then FindPrice = -1 Exit Function End If FindPrice = rsProd("price") Exit Function ErrorHandler: Dim er As Error If conn.Errors.Count > 0 Then For Each er In conn.Errors MsgBox er.Description, , "ADO Error" Next er Else MsgBox Err.Description, , "Non-ADO error" End If FindPrice = -1 End Function Если вы запустите программу в том виде, в котором она имеется в каталоге Chapl8\Demos\PriceDemo, ошибка произойдет при подключении (обратите внимание на пробел между знаком = и Game в строке подключения). Это — ошибка ADO, и вы должны увидеть окно сообщения, показанное на рис. 18.18. Теперь раскомментируйте следующую строку: 'strSQL =1/0 Это приведет к появлению не ADO-ошибки, при возникновении которой будет вызван обработчик ошибки, но коллекция Errors останется пустой. В результате будет получено сообщение об ошибке, показанное на рис. 18.19. Рис. 18.18. Сообщение об ошибке ADO из коллекции Errors Рис. 18.19. Коллекция Errors при возникновении не ADO-ошибки пуста (Если ваш обработчик не был вызван при запуске программы из среды Visual Basic, отметьте опцию Error Trapping во вкладке General диалогового окна, вызываемого с помощью команды меню Tools^Options. Ее значение должно быть либо Break in Class Module, либо Break on Unhandled Errors.)
Использование OLE DB-провайдера SQL Server Наш последний пример иллюстрирует использование OLE DB-провайдера SQL Server Его применение более эффективно, чем использование провайдера ODBC. Объект Connection имеет свойство Provider, которое вы применяете для определения OLE DB-провайдера, который будет использоваться ADO. Значение по умолчанию — "MSDASQL", представляющее провайдер ODBC. Для использования OLE DB- провайдера SQL Server строка должна быть равна "sqloledb". После того как вы определите свойство Provider, следует указать свойство ConnectionString. Вы должны указать четыре части строки: Server, Database, uid (Идентификатор пользователя) и pwd (Пароль) Для обозначения завершения частей используется символ ";", а в строке не должно быть лишних пробелов После знака равенства сразу же должен идти первый символ значения Если значения нет, то сразу за знаком равенства должен следовать символ ";", например: conn.Provider = "SQLOLEDB" conn.ConnectionString = "Server=" & server & "; " _ & "Database= Game;" _ & "uid=sa;" _ & "pwd=;" conn.Open Рис. 18.20. Поиск цены на удаленном сервере Значение параметра Server может отсутствовать — в этом случае будет использоваться локальный сервер. При запуске программы вы должны иметь возможность либо указать удаленный сервер, либо оставить поле имени сервера пустым для запуска локального сервера (на рис. 18.20 показан запуск программы, работающей с удаленным сервером). Трехуровневое приложение СОМ+ Несложно преобразовать наше "монолитное" приложение в логически трехуровневое приложение. Если у вас есть три компьютера, приложение можно разместить на них, сделав его трехуровневым не только логически, но и физически. Наше приложение HistoryAdminMonolithic мы разобьем на компонент среднего уровня HistoryAdminServer и компонент уровня представления HistoryAdminClient. Теперь наше приложение логически представляет собой трехуровневое приложение, с уровнем данных, представленным SQL Server. Затем мы инсталлируем сервер в СОМ+ и создадим клиентский прокси. После этого мы сможем запустить нашу программу на двух компьютерах. Последний шаг — если вы захотите его сделать — состоит в реконфигурировании источника данных ODBC для работы с удаленным компьютером. Последний шаг носит чисто методический характер; на практике очень часто база данных работает на той же машине, что и сервер среднего уровня (это делается, чтобы избежать лишней работы в сети).
Создание сервера среднего уровня Окончательный код сервера можно найти в каталоге Chapl8\HistoryAdminServer. Если вы хотите выполнить всю работу самостоятельно — к вашим услугам каталог Chapl8\Demos\HistoryAdminServer. Все, что вы должны сделать— так это создать новый проект ActiveX DLL, скопировать файл dbHistory.cls, установить свойство Multillse и добавить ссылку на библиотеку ActiveX Data Objects. После построения DLL измените версию бинарной совместимости. Создание клиента уровня представления Окончательный код клиента можно найти в каталоге Chapl8\HistoryAdminClient. Если вы хотите выполнить всю работу самостоятельно — к вашим услугам каталог Chapl8\Demos\HistoryAdminClient. Все, что вы должны сделать— так это создать новый проект ЕХЕ, скопировать файлы Forml.frm и frmHistory. f rm и добавить ссылку на ваш собственный HistoryAdminServer и на Microsoft ActiveX Data Objects. Отключенные наборы записей Можно подумать, что если вы инкапсулируете доступ к базам данных, то вы должны создать собственную коллекцию для передачи данных клиенту. Однако это добавит лишней работы вашей программе. Класс ADO Recordset создан так, что он может работать в "отключенном" режиме. Наш пример программы иллюстрирует такое использование класса. Метод сервера Get All возвращает набор записей, который на стороне сервера создается как локальная переменная. При возвращении из метода этот объект будет закрыт и уничтожен, а копия передана клиенту. Этот новый набор записей больше не подключен к базе данных и не может использоваться для ее обновления, но вполне может применяться для передачи структурированных данных клиенту. Сейчас вы должны иметь возможность запускать HistoryAdminClient. Поведение нового многоуровневого приложения ничем не должно отличаться от поведения старой, монолитной программы. Использование СОМ+ для создания удаленного прокси Вы можете импортировать ваш компонент в СОМ+, а затем экспортировать удаленный прокси. Мы уже работали с этой технологией, так что будем предельно кратки в описании того, что требуется сделать. Воспользуйтесь СОМ+ Explorer для создания пустого приложения HistoryAdminApp. Инсталлируйте вашу ActiveX DLL в это новое приложение. Вы можете попытаться запустить приложение-клиент, и оно должно работать (и при этом на панели СОМ+ Explorer должен вращаться соответствующий шарик). Затем воспользуйтесь возможностью экспортирования для создания .msi-файла для вашей удаленной машины Windows 2000 и дважды щелкните на нем для инсталляции. Затем скопируйте клиентский .ехе-файл. После этого вы должны иметь возможность запуска удаленного клиента. Удаленный запуск уровня данных Выполнить доступ к данным на третьем компьютере достаточно просто. На машине, на которой размещен средний уровень приложения, следует реконфигурировать подключение ODBC. Убедитесь, что база данных History установлена на удаленной машине с SQL Server. Вызовите ODBC Administrator, выберите вкладку System DSN, a
на ней — ваше имя источника данных sysHistory. Щелкните на кнопке Configure В первом появившемся диалоговом окне выберите нужный удаленный сервер из списка What SQL Server do you want to connect to?, как показано на рис. 18 21. Рис. 18.21 Подключение для доступа к данным к удаленному серверу Провайдер ODBC очень удобен для такой реконфигурации. Если же вы используете OLE DB-провайдер SQL Server, то вам придется немного попрограммировать. Для того чтобы избежать жесткого задания имени сервера в коде, вам придется написать код, осуществляющий получение средним уровнем имени сервера в процессе работы. Одним из подходов может оказаться создание соответствующей строки конструктора, о которой мы говорили в главе 15, "Урок СОМ+". Electronic Commerce Game™ Теперь самое время завершить настройку нашей игры. Мы познакомились с версией-заглушкой в главе 17, "Windows 2000 и безопасность СОМ+", когда демонстрировали работу системы безопасности, но теперь самое время перейти к полной версии игры. Сейчас нам надо настроить несколько источников данных ODBC для нормального функционирования игры. Первое, что нам надо, — это источники данных для трех баз данных поставщиков. Создайте системные DSN toyland.com, petworld.com и foodstore.com для баз данных Toyland, Petworld и Foodstore соответственно. Все необходимые файлы имеются в каталоге CaseStudy. Файловое имя источника данных Код доступа к базе данных Games иллюстрирует файловое DSN, когда конфигурационная информация хранится в текстовом файле. В каталоге CaseStudy\SqlScripts вы найдете интересующий вас файл game.dsn Скопируйте его в папку, предназначенную для источников данных ODBC (на моей машине это каталог Program Files\Common Files\ODBC\Data Sources, расположенный в разделе диска, на котором установлена Windows 2000). Теперь вызовите ODBC Data Source Administrator и перейдите во вкладку File DSN Вы должны увидеть установленное DSN game.dsn, как показано на рис 18.22.
Рис. 18.22. База данных Game доступна через файловое DSN Для использования DSN в вашем коде вы должны воспользоваться строкой подключения типа "FiLEDSN=Game.DSN", что проиллюстрировано в файле VendorList.cls (каталог CaseStudy\Monolithic). Const fileDSN = "Game.DSN" Public Function GetVendcrs() As Recordset Dim connGame As New Connection connGame.Open "FILEDSN=" & fileDSN Set GetVendors = connGame.Execute("Vendors") End Function Игра Убедиться в корректности настройки баз данных и источников данных ODBC можно, непосредственно сыграв в игру. В настоящий момент воспользуйтесь "монолитной" версией игры (файл ClientBrowser.exe в папке CaseStudy\Monolithic). Если у вас возникнут проблемы с запуском ЕХЕ-файла, откройте проект в Visual Basic, убедитесь в корректности разрешения всех ссылок в вашей системе (например, в правильности версии ADO и т.п.). Резюме В этой главе представлено очень много информации, касающейся баз данных. Даже если вы никогда ранее не занимались программированием баз данных, то все равно должны отметить простоту применения инструментария Microsoft. Естественно, для работы с теми или иными технологиями и инструментами вам может понадобиться дополнительная информация об ADO или SQL Server. В этой главе были приведены только те сведения, которые необходимы для работы с последующими двумя главами. Мы тратим столько времени на рассмотрение баз данных не только потому, что они имеют практическую важность, но и потому, что базы данных необходимы для иллюстрации транзакций — одного из самых важных сервисов СОМ+, о котором мы поговорим в следующей главе.
Глава 19 Транзакции в СОМ+ В этой главе мы рассмотрим очень важную тему — работу транзакций, и узнаем, каким образом СОМ+ упрощает программирование приложений с транзакциями. Вначале мы поговорим о принципах обработки транзакций, включая вопросы распределенных транзакций, а затем рассмотрим технологии реализации транзакций от Microsoft, включая транзакции OLE и Microsoft Distributed Transaction Coordinator. Мы также изучим, каким образом СОМ+ использует OLE Transactions и MS DTC от имени прикладных программ. Транзакции в СОМ+ — превосходная иллюстрация того, каким образом концепции контекста и перехвата могут быть приложены к решению очень важной программной проблемы. В последнем разделе главы представлен пример хода работы с транзакциями в СОМ+. Мы покажем, каким образом реализуются распределенные транзакции с двумя базами данных SQL Server на различных компьютерах. Принципы технологии транзакций Транзакции могут рассматриваться как ключевой унифицирующий принцип построения мощных распределенных приложений. Они обеспечивают как средство разделения сложной обработки данных на простые логические модули, так и механизм гарантии корректности работы системы при незначительных сбоях отдельных компонентов. Перед погружением в пучину деталей технологии транзакций Microsoft и, в частности, СОМ+, полезно рассмотреть общие принципы. Ниже будут описаны общие характеристики, которыми должны обладать транзакции в пределах одной базы данных; затем мы рассмотрим работу распределенных транзакций. Транзакции Транзакцию можно трактовать как единицу работы, которая должна удовлетворять четырем фундаментальным принципам (известным, как АСШ-правила7). ■ Атомарность (Atomic). Транзакция представляет собой все или ничего. Например, транзакция перевода денег с одного счета на другой не должна прекратиться посреди операции, т.е. снять деньги с одного счета и не положить их на другой. ' Как будет видно при перечислении правил,ACID (кислота) представляет собой аббревиатуру от первых букв в английском написании названий принципов. По- русски, если допустить небольшую перестановку порядка принципов, эти правила можно было бы назвать САНИ-правила — Прим перев.
■ Согласованность (Consistent). Транзакция не должна нарушать никакие ограничения целостности. Базы данных обычно создаются с различными требованиями и ограничениями, связанными с согласованностью и целостностью данных, и транзакция должна полностью удовлетворять им. ■ Изолированность (/solated). Параллельные транзакции не должны воздействовать на внутреннее состояние друг друга. Система должна работать таким образом, как если бы первая транзакция полностью выполнялась до начала другой. Таким образом, транзакции могут рассматриваться как высокоуровневый механизм синхронизации. ■ Надежность (Durable). По окончании выполнения транзакции все изменения становятся постоянными. Если впоследствии произойдет сбой системы, после восстановления состояние должно отражать результат транзакции. В этой главе мы рассмотрим простой пример, код которого будет приведен в конце главы. Наша простая транзакция состоит в перемещении записи из таблицы активной базы данных в таблицу архивной базы данных. Нам определенно нужна атомарность, поскольку такая операция не может быть прервана посредине — например, в тот момент, когда из активной базы данных запись уже удалена, а в архивную еще не попала. Требуется и согласованность, так как удалить запись, на которую ссылается другая запись, не нарушив целостность базы данных, нельзя. Конечно же, нам нужна изолированность транзакций, и, само собой разумеется, что мы выполняем перемещение записи именно для того, чтобы изменения стали постоянными. Современные базы данных обычно обеспечивают механизм транзакций в базе данных. Вы можете сгруппировать серию операций в транзакцию. Если все операции выполнены успешно, транзакция принимается (commit), и внесенные изменения становятся постоянными. Если любая из операций неуспешна, транзакция отменяется (rollback) и база данных возвращается в исходное состояние. Распределенные транзакции Более сложной оказывается работа с распределенными транзакциями, в которых операции производятся над различными хранилищами данных, которые могут быть различного вида и располагаться на разных компьютерах. Комплексная операция может обновить две различные базы данных по сети, отправив информацию электронной почтой или послав сообщение в очередь для доставки его удаленному приложению. Одна база данных может быть SQL Server, вторая — Oracle, электронная почта пересылаться с помощью Microsoft Exchange, а в качестве системы очереди использоваться MSMQ. Каждое из этих хранилищ данных по отдельности способно работать с транзакциями, но все они имеют различные API и работают с разными протоколами. Каким же образом эти хранилища данных могут сотрудничать, участвуя в одной распределенной транзакции? Группа стандартов Х/Open ввела в обращение 1991 году модель обработки распределенных транзакций (Distributed Transaction Processing — DTP) и два стандартных интерфейса. Эта модель широко распространена и концептуально, такая же модель (но не протокол) используется Microsoft. Поэтому свое рассмотрение распределенных транзакций мы начнем с модели DTP. Модель DTP В обобщенной модели DTP приложения для координации транзакций работают с менеджером транзакций. Транзакции создаются менеджером транзакций, и запросы приложений на принятие или отмену транзакций направляются менеджеру транзак-
ций. Хранилища данных, используемые приложениями, контролируются своими собственными менеджерами ресурсов, гарантирующими выполнение ACID-правил, сохраняющими изменения в базе данных или производящими откат в случае неуспешной работы и т.п. Приложение общается с менеджером ресурсов для передачи запроса на некоторые операции с хранилищем данных. Менеджер транзакций общается с менеджером ресурсов для координации воздействия транзакций на хранилище данных под управлением этого менеджера ресурсов. При наличии нескольких компьютеров каждый из них имеет собственного менеджера транзакций и одного или нескольких менеджеров ресурсов. Локальный менеджер транзакций общается с удаленным менеджером транзакций для координации влияния транзакций на ресурсы удаленной машины. В целом архитектура DTP представлена на рис. 19 1, где для простоты показаны два менеджера ресурсов на одной машине. тх Менеджер транзакций Приложение ХА Менеджер ресурсов 1 Рис. 19.1. Модель Х/Ореп DTP Х/Ореп определяет два протокола как часть модели DTP. Первый — ТХ — обеспечивает стандартный API для приложений, общающихся с менеджером транзакций. Второй — ХА— предоставляет API для общения менеджеров транзакций с ресурсами. Каждый X/Open-совместимый менеджер транзакций должен поддерживать как протокол ТХ, так и ХА, а X/Open-совместимый менеджер ресурсов должен поддерживать протокол ХА Для понимания работы распределенных транзакций в этой модели пройдем пошагово все действия при выполнении приложением транзакции, включающей операции у каждого из двух менеджеров ресурсов 1. Приложение вызывает менеджер транзакций для создания транзакции, получая при этом ссылку на новую транзакцию. 2. Приложение вызывает каждый менеджер ресурсов для выполнения своей части работы. Им передается ссылка на транзакцию. 3. Каждый менеджер ресурсов вызывает менеджер транзакций для сообщения о том, что он приступает к транзакции. Менеджер транзакций теперь знает все менеджеры ресурсов, которые он должен координировать, когда придет время принять транзакцию. 4. Каждый менеджер ресурсов выполняет работу, запрос на которую пришел от приложения, и возвращает код успешности завершения приложению. Менеджер ресурсов может сохранить новое состояние в буфере и обновит хранилище
данных только тогда, когда будет передана команда на принятие изменений. Как часть этой операции менеджер ресурсов выполнит блокировку для гарантии изолированности операций. 5. Приложение вызывает менеджер транзакций для принятия или отмены транзакции. Двухфазное принятие транзакции Если приложение принимает решение об отказе от транзакции, менеджер транзакций информирует каждого менеджера ресурсов об отказе от транзакции. Менеджер ресурсов может отменить все буферы результатов, не внесенных в хранилище, и освободить все блокировки. Если приложение принимает решение о принятии транзакции, менеджер транзакции инициирует двухфазное принятие, выполняемое следующим образом. 1. (Фаза подготовки.) Менеджер транзакций вызывает каждого менеджера ресурсов, задействованных в транзакции, и сообщает им о подготовке к принятию транзакции. 2. Каждый менеджер ресурсов отвечает "да" или "нет". Если он отвечает "да", то данные этим менеджером вносятся в промежуточное постоянное (по отношению к сбоям) хранилище. Этот менеджер ресурсов готов к завершению своей части принятия транзакции (при этом последующие сбои не имеют значения). 3. (Фаза принятия.) Если хотя бы один менеджер ресурсов ответил "нет", транзакция отменяется и менеджер транзакций сообщает об этом менеджерам ресурсов, которые могут отменить внесенные изменения и освободить блокировки. Если все менеджеры ресурсов ответили "да", менеджер транзакций уведомляет менеджеров ресурсов о принятии изменений. 4. Каждый менеджер ресурсов записывает изменения в постоянное хранилище данных. На этом его часть транзакции выполнена. Этот протокол достаточно устойчив по отношению к ошибкам всякого рода. Предположим, например, что менеджер ресурсов аварийно прекратил работу после ответа на запрос менеджера транзакций и перед тем, как получил от него "добро" на окончательное принятие изменений. Поскольку результаты при этом находятся в надежном промежуточном хранилище, они не теряются, а вносятся в хранилище данных после восстановления работоспособности и "консультации" с менеджером транзакций. В случае распределенных транзакций с использованием нескольких машин каждая из них имеет своего менеджера транзакций, работающего с менеджерами ресурсов локальной машины. Все обращения к удаленным менеджерам ресурсов происходят через соответствующие менеджеры транзакций. Технология транзакций Microsoft Microsoft поддерживает транзакции в своей СУБД SQL Server. В SQL Server 6.5 Microsoft ввела распределенные транзакции, работающие с использованием нового сервиса NT, называемого координатором распределенных транзакций (Microsoft Distributed Transaction Coordinator — MS DTC), который играет роль менеджера транзакций в модели DTP. В роли менеджеров ресурсов выступают такие важные продукты доступа к данным, как SQL Server и MSMQ. Вместо реализации протоколов ТХ и ХА Microsoft вводит объектно-ориентированное множество интерфейсов транзакций, называемое OLE Transactions и построенное над СОМ. Эти интерфейсы очень хорошо подходят к инфраструктуре Microsoft, базирующейся на СОМ.
Следующим шагом в эволюции технологии обработки транзакций Microsoft был сервер транзакций Microsoft (Microsoft Transaction Server — MTS), который расширяет COM для предоставления простого пути участия компонентов СОМ в транзакциях обычным объявлением требования транзакции. В настоящее время СОМ+ заменил MTS. Детально обработку транзакций в СОМ+ мы рассмотрим в следующем разделе. Здесь же речь пойдет о базовых сервисах транзакций, применяемых СОМ+, а именно OLE Transactions и MS DTC. OLE Transactions Транзакции OLE являются стандартными интерфейсами Microsoft для обмена информацией между менеджерами транзакций и менеджерами ресурсов. В модели обработки транзакций Microsoft они выполняют роль интерфейсов ТХ и ХА (в модели DTP). Выбор компанией Microsoft собственных интерфейсов имеет несколько причин. ■ Корпорации Microsoft требуется стандарт, основанный на СОМ, для создания высокоуровневой, основанной на атрибутах обработки транзакций, которая была реализована в MTS и перешла в СОМ+. ■ OLE Transactions более расширяемы, чем DTP и, в частности, могут быть распространены на очень широкое множество ресурсов, защищаемых транзакциями. ■ OLE Transactions имеют определенное преимущество перед DTP в части многопоточности. ■ (И вообще, где это видано, чтобы Microsoft следовала стандарту, разработанному кем-то другим? — Прим. перев.) Хотя интерфейсы OLE Transactions выглядят похожими на интерфейсы СОМ, они во многом отличаются от последних. Объекты создаются специальными функциями API или вызовами методов , а не стандартными функциями CoCreatelnstanceEx и связанными с ней. Интерфейсы OLE Transactions не разработаны для вызовов, пересекающих границы процессов, и не поддерживают маршалинг. Кроме того, у них нет автоматической поддержки синхронизации, основанной на модели апартаментов. OLE Transactions представляет собой низкоуровневую систему, с которой вряд ли будут непосредственно работать ваши программы. Координатор распределенных транзакций Microsoft Microsoft DTC выполняет роль менеджера транзакций в системах Microsoft. С точки зрения высокоуровневой архитектуры модель распределенных транзакций Microsoft идентична модели X/Open DTP. Таким образом, Microsoft DTC базируется на испытанной промышленной модели. Чтобы вы могли понять, что представляет собой идея Microsoft DTC, рассмотрим конкретный пример. Наше приложение выполняет транзакцию, включающую обращение к ресурсам на двух разных компьютерах. Эти ресурсы управляются менеджерами ресурсов RM1 и RM2 на этих машинах, каждая из которых имеет также свой DTC — DTC1 и DTC2 (рис. 19.2). Эта диаграмма концептуально та же, что и изображенная на рис. 19.1. Теперь у нас имеется специальный менеджер транзакций, а именно Microsoft DTC. Менеджеры ресурсов располагаются на различных компьютерах, и каждый из них работает со своим локальным DTC. Предположим, что DTC1 играет роль координирующего DTC. Пройдем пошагово действия приложения, выполняющего транзакцию, обновляющую ресурсы, контролируемые двумя менеджерами ресурсов на двух различных машинах.
Общая логика при этом та же, что и в примере, приведенном в предыдущем разделе, в котором речь шла о DTP, но теперь мы более подробно рассмотрим использование Microsoft DTC и OLE Transactions. 1. Приложение подключается к DTC1 посредством вызова DllGetTransactionManager, получая указатель на интерфейс ITransactionDispenser. 2. Приложение вызывает метод BeginTransaction этого интерфейса, получая указатель на интерфейс I Trans act ion. 1 Приложение DTC1 RM1 DTC2 RM2 Рис. 19.2. Microsoft DTC координирует транзакцию, работающую на двух компьютерах 3. Приложение подключается к менеджерам ресурсов, передавая им указатель на интерфейс ITransaction. 4. Менеджеры ресурсов включаются в транзакцию, вызывая метод Enlist интерфейса IResourceManager, реализованного объектом в DTC. 5. Приложение вызывает менеджеры ресурсов для выполнения своей работы (например, выполняя SQL-код в SQL Server и используя ADO).
6. Менеджеры ресурсов выполняют затребованные действия, сохраняя результаты в памяти и сообщая приложению об успешном или неудачном выполнении работы. 7. Приложение вызывает DTC1 для принятия или отмены транзакции, для чего используются метод Commit или Abort интерфейса ITransaction. Теперь DTC1 инициирует двухфазный протокол принятия транзакции, используя ту же логику, которая была описана в предыдущем разделе. DTC1 обращается к RM1 непосредственно, а к RM2 — через DTC2. Взаимодействие с ХА Имеется два способа работы Microsoft DTC с менеджерами ресурсов для координации распределенных транзакций. Он может использовать OLE Transactions для любого менеджера ресурсов, реализующего интерфейсы OLE Transactions. Можно также взаимодействовать с менеджерами ресурсов, поддерживающими протокол X/Open XA, посредством компонента ХА Mapper, осуществляющего "перевод" между ХА и OLE Transactions. Посредством такого отображения Microsoft DTC может координировать транзакции, работающие с менеджерами ресурсов, совместимыми как с OLE Transactions, так и с ХА. Точно так же менеджеры ресурсов, совместимые с OLE Transactions, например, SQL Server, могут участвовать в транзакциях, управляемых менеджером транзакций, совместимых с DTP. Автоматическая обработка транзакций с использованием СОМ+ СОМ+ эффективно инкапсулирует описанную модель обработки транзакций. Ключ к выполнению работы кроется в трехуровневой архитектуре. Клиентское приложение не обновляет ресурсы непосредственно, а работает через компонент среднего слоя, который может быть сконфигурирован в СОМ+ с атрибутами транзакций. В этом случае во время работы посредством стандартной модели перехвата СОМ+ может установить распределенную транзакцию от имени компонента — возможность, называемая автоматическими транзакциями. В этом разделе мы детально опишем, каким образом работают автоматические транзакции, а в следующем познакомимся с программным примером. Транзакционные компоненты Транзакционный компонент представляет собой компонент, импортированный в приложение СОМ+ и сконфигурированный как поддерживающий или требующий транзакции с помощью соответствующих атрибутов. Транзакционные атрибуты могут использоваться СОМ+ для определения контекста, в котором работает компонент. Как и в случае других компонентов СОМ+, транзакционные компоненты могут либо разделять контекст вызывающей программы, либо работать в собственном контексте. Один из элементов, хранящихся в контексте объекта — идентификатор транзакции — GUID, — который идентифицирует транзакцию, в которой участвует объект. Если экземпляр объекта участвует в транзакции (или работает в транзакции), ресурсы, с которыми работает объект, будут защищены транзакцией. У вас есть возможность выбрать любой из пяти вариантов транзакционных атрибутов, представленных в СОМ+ Explorer (рис. 19.3).
■ Disabled. COM+ игнорирует транзакционный атрибут при определении контекста нового экземпляра объекта. Это то же поведение, что и у несконфи- гурированного компонента. ■ Not Supported. Компонент никогда не участвует в транзакции. Это — значение по умолчанию для транзакционного атрибута. Рис. 19.3. Определение транзакционных атрибутов компонента ■ Supported. Компонент будет работать в его клиентской транзакции Если клиент не работает в транзакции, то компонент также не работает в транзакции. ■ Required. Компонент всегда работает в транзакции. Если клиент работает в транзакции, компонент будет работать в той же транзакции Если клиент не работает в транзакции, для компонента будет создана новая транзакция. ■ Requires New. Компонент всегда работает в своей собственной транзакции, создаваемой специально для него. Зависимые атрибуты Если вы выбираете любой из трех последних атрибутов (определяющих возможное использование транзакций), автоматически выбираются некоторые другие атрибуты. Автоматически устанавливается активизация по необходимости (ЛТ); это означает, что время жизни объекта управляется битом "done" и объект деактивируется при вызове SetComplete или SetAbort. Такое поведение согласуется с атомарностью транзакции. Атрибут поддержки синхронизации устанавливается равным Required, тем самым обеспечивается автоматическое упорядочение обращений к объекту средствами СОМ+, что согласуется с требованием изолированности транзакций. (Активизацию по необходимости (ЛТ) мы рассматривали в главе 14, "Основы архитектуры СОМ+", а параллельные вычисления — в главе 16, "Параллельные вычисления в СОМ+".) В СОМ+ Explorer эти измененные опций (при отображении их на экране) будут выделены серым цветом (указывающим на невозможность изменения), в чем вы легко можете убедиться сами.
Объекты и границы транзакций В СОМ+ зачастую полная обработка транзакций, обращающихся к разным ресурсам, разделяется на отдельные шаги, реализуемые дополнительными компонентами, по одному для каждого ресурса. Объект наивысшего уровня, отвечающий за транзакцию в целом, называется корневым объектом. Вспомогательные объекты, созданные для выполнения операций с индивидуальными ресурсами, называются внутренними объектами. На рис. 19.4 показана иерархия времени выполнения корневого объекта Move, отвечающего за переносящую запись из базы данных Game в базу данных History. Он передает свою работу двум внутренним объектам — "Delete Player" и "Add History". Неформально можно сказать, что объект Move — родительский по отношению К Delete Player И Add History. Move Delete Player Add History Рис. 19.4. Иерархия времени выполнения для транзакции перемещения записи Корневой объект начинает новую транзакцию и получает идентификатор транзакции, который хранится в его контексте. Этот идентификатор транзакции очерчивает границы транзакции. Внутренние объекты будут иметь те же идентификаторы транзакции и представлять собой части той же транзакции. Согласованность и бит выполнения Транзакционный компонент допускает активизацию по необходимости (ЛТ) и, таким образом, уделяет внимание битам выполнения (бит "done"). При вызове метода объект активизируется по необходимости, и бит выполнения устанавливается равным 0. По завершении метода он должен установить бит равным 1, что выполняется посредством вызова функций SetComplete или SetAbort, что вызывает деакгивизацию объекта. Транзакционный компонент, кроме того, уделяет внимание и другому биту — биту согласованности — для определения того, каким образом ответить менеджеру транзакций на сообщение о подготовке к принятию транзакции. Если бит согласованности установлен равным 0 (false), то компонент отвергнет транзакцию. Интерфейс IObjectContext имеет методы SetComplete и SetAbort, которые воздействуют на оба этих бита. Оба метода устанавливают бит выполнения равным 1 (true), а бит согласованности — равным 1 и 0 соответственно. Раздельная установка этих битов может осуществляться с помощью интерфейса icontextstate.
Флаг транзакции Транзакция в целом имеет флаг транзакции, именуемый также флагом "обреченности" ("doomed" flag). При создании транзакции этот флаг принимает значение 0 (false), указывая, что потенциально транзакция может быть принята. Если какой-либо объект в границах транзакции вызывает SetAbort или каким-либо другим методом устанавливает бит выполнения равным 1, а бит согласованности — 0, флаг транзакции устанавливается равным 1. Это означает, что транзакция никак не может быть принята и, следовательно, по завершении работы корневого объекта СОМ+ требует от DTC отмены транзакции. Жизненный цикл автоматической транзакции Теперь мы опишем все части обработки транзакции СОМ+ и получим общее представление о механизме работы распределенных транзакций. Все эти части работы будут связаны в единое целое при рассмотрении примера транзакции. Транзакция состоит в перемещении записи из таблицы Players базы данных Game на одной машине в базу данных History на другой машине. Обе базы данных работают под управлением SQL Server в качестве менеджера ресурсов и Microsoft DTC в качестве менеджера транзакций. Мы реализуем транзакцию, используя компоненты СОМ+. Корневой компонент — Move, передающий свою работу компонентам Delete Player и Add History. Предположим, что операция удаления завершена неудачно, а операция добавления прошла успешно. Если бы мы работали без применения транзакций, то получили бы новую запись в базе данных History без соответствующего удаления. Рассмотрим, каким образом СОМ+ и механизм транзакций обеспечивают корректный результат (в нашем случае — отказ от транзакции). Транзакционные атрибуты следующие: ■ Move, который требует транзакцию, ■ Delete Player, который поддерживает транзакцию; ■ Add History, который также поддерживает транзакцию. В нашем примере мы будем следовать той же логике, которая воплощена в примере кода, представленного в следующем разделе (с некоторыми упрощениями). SetComplete и SetAbort вызываются корневым объектом. 1. Клиент вызывает компонент Move, что включает вызов через границы контекста (так как Move требует транзакцию), а значит, вызывается перехватчик СОМ+. 2. Перехватчик вызывает DTC для получения новой транзакции. Идентификатор транзакции хранится в контексте корневого объекта Move. 3. Move создает внутренние объекты Delete Player и Add History. Они работают в той же транзакции; идентификатор транзакции передается их контекстам. 4. Move вызывает внутренние объекты. 5. Внутренние объекты вызывают SQL Server через ADO. 6. Каждый SQL Server участвует в транзакции, сообщая об этом DTC локальной машины. 7. Delete Player завершает работу неудачно, не внеся обновления в свой SQL Server.
8. Add History успешно завершает работу. Так как действие происходит в транзакции, результат заносится в специальный буфер, а не в саму базу данных. 9. Move получает уведомления о результатах работы от внутренних объектов и узнает о необходимости отмены транзакции. Он вызывает SetAbort — при этом быт выполнения устанавливается равным 1, а бит согласованности — 0. После этого Move завершает работу и внутренние объекты, представлявшие собой локальные переменные, уничтожаются. 10. На обратном пути к клиенту в связи с пересечением границ контекста управление передается перехватчику. И. Перехватчик "замечает", что бит согласованности равен 0 и вызывает DTC для отмены транзакции. 12. DTC вызывает локальный SQL Server и передает ему требование отменить транзакцию. 13. DTC вызывает удаленный DTC и передает ему требование передать сообщение об отмене транзакции менеджеру ресурсов. 14. Удаленный DTC вызывает SQL Server и передает ему требование отменить транзакцию. 15. Перехватчик возвращает управление клиенту. Никакие изменения в базы данных внесены не были. Программирование транзакций в СОМ+ Теперь мы рассмотрим код, иллюстрирующий пример, описанный в предыдущем разделе, и продемонстрируем соответствующую настройку окружения СОМ+. После всех наших рассмотрений код может показаться просто разочаровывающим. Реализация транзакций, даже распределенных, в СОМ+ не представляет никаких сложностей, в чем вы сейчас убедитесь сами. Наш пример, кроме того, продолжает демонстрацию программирования баз данных с использованием ADO, начатую в главе 18, "SQL Server и ADO". Программа Player Administration Наша демонстрационная программа представляет собой административную программу для таблицы Players базы данных Game, являющейся частью Electronic Commerce Game™. Программа позволяет вам находить игрока, добавлять его, удалять, обновлять информацию об игроке и отображать список всех игроков. Программа также позволяет добавить игрока в базу данных History и показать список игроков из этой базы данных. И, наконец, вы можете переместить игрока из базы данных Game в базу данных History. Внешний вид окна программы приведен на рис. 19.5. Команда Move выполняет рассмотренный нами пример транзакции. Он работает следующим образом: удаляет запись, показанную в области Data, из базы данных Game и добавляет ее в базу данных History. Обратите внимание на то, что запись идентифицируется полем Name в области Data; в действительности такого игрока в базе данных Games может просто не оказаться, и тогда удаление будет неудачным. В то же время при вводе корректных данных во все поля добавление в базу данных History будет успешно выполняться — тем самым введя отсутствующего в базе данных игрока, мы моделируем описанную в разделе "Жизненный цикл автоматической транзакции" (см. стр. 433) ситуацию.
Рис. 19.5. Программа Player Administration Если вы хотите поэкспериментировать с программой до создания и инсталляции сервера СОМ+, то можете запустить ее "монолитную" версию, имеющуюся в каталоге Chapl9\PlayerAdminMonolithic. Эта версия программы не использует транзакции. Компоненты среднего уровня У нас имеется три компонента, соответствующие корневому и двум внутренним объектам. Они представлены в проекте, расположенном в каталоге Chapl9\edutil, утилитами для Electronic Commerce Game™. Создайте новое приложение СОМ+ под именем ecUtilities, а затем инсталлируйте компоненты, реализованные в ecUtil.dll. После этого вы должны увидеть три компонента, установленные в новом приложении, как показано на рис. 19.6. Рис. 19.6. Наш демонстрационный сервер содержит три компонента Затем установите атрибуты поддержки транзакций следующим образом: ■ ecUtil .bmove. Должен иметь атрибут Required;
■ ecutil. dbPlayer. Должен иметь атрибут Supported; ■ ecutil. dbHistory. Должен иметь атрибут Supported. Теперь рассмотрим код всех трех компонентов. ecUtil. dbPlayer Этот компонент представляет собой компонент базы данных, инкапсулирующий различные операции по обращению к данным таблицы Players базы данных Games. В приведенном коде вы должны узнать, как используется ADO — технология, с которой мы познакомились в главе 18, "SQL Server и ADO". Метод, который использует наша транзакция, — Delete. Приведенный код достаточно сильно упрощен — например, в нем нет обработчика ошибок. Const fileDSN = "Game.DSN" Public Function Delete(ByVal name As String) Dim strSQL As String Dim connGame As New Connection Dim objLog As New LogVb Dim ctx As ObjectContext Set ctx = GetObjectContext If ctx.IsInTransaction Then objLog.Writeln _ "In transaction (dbPlayer::Delete)" Else objLog.Writeln _ "Not in transaction (dbHistory::Delete)" End If 'On Error GoTo ErrorHandler connGame.Open "FILEDSN=" & fileDSN strSQL = "DELETE FROM Players WHERE name = " _ & "'" & name & Dim numrow As Long connGame.Execute strSQL, numrow If numrow = 0 Then Delete = False ctx.SetAbort Else Delete = True ctx.SetComplete End If End Function ecUtil.dbHistory Этот компонент подобным рассмотренному выше образом инкапсулирует доступ к базе данных History. Метод, который используется в рассматриваемой нами транзакции, — Add. Public Function Add(ByVal name As String, _ ByVal balance As Currency, _ ByVal num_games As Integer) As Boolean Dim connGame As New Connection
Dim objLog As New LogVb Dim ctx As ObjectContext Set ctx = GetObjectContext If ctx.IsInTransaction Then objLog.Writeln "In transaction (dbHistory::Add)" Else objLog.Writeln "Not in transaction (dbHistory::Add)" End If On Error GoTo ErrorHandler connGame.Open "sysHistory" Dim strSQL As String strSQL = "INSERT INTO Players VALUES (" _ & "'" & name & ",n & " , " & balance & " , " _ & num_games & ")" connGame.Execute strSQL ctx.SetComplete Add = True Exit Function ErrorHandler: Dim er As Error If connGame.Errors.Count > 0 Then For Each er In connGame.Errors objLog.Writeln er.Description & " (ADO Error)" Next er Else objLog.Writeln Err.Description & " (Non-ADO error)" End If ctx.SetAbort Add = False End Function ecUtil.bMove Этот компонент представляет собой "бизнес-логику", включая также некоторую функциональность за пределами работы с базами данных. В нашем случае этот компонент реализует метод Move, который пытается удалить запись из таблицы Players базы данных Game и добавить запись в базу данных History. Именно здесь мы используем объект контекста для вызова SetAbort или SetComplete. Код разработан таким образом, что он может быть запущен как при использовании СОМ+, так и без нее. В последнем случае объект контекста отсутствует, и вызов SetAbort или SetComplete некорректен. Public Function Move(ByVal name As String, _ ByVal balance As Currency, _ ByVal num_games As Integer) As Boolean Dim objHistory As dbHistory Dim objPlayer As dbPlayer Dim ctx As ObjectContext Dim ok As Boolean Dim ok2 As Boolean Set ctx = GetObjectContext If ctx Is Nothing Then Set objHistory = New dbHistory Set objPlayer = New dbPlayer objHistory.Add name, balance, num_games ok = objPlayer.Delete(name) Move = ok Else
'Set objHistory = 1 ctx.Createlnstance("ecUtil.dbHistory") 'Set objPlayer = ' ctx.Createlnstance("ecUtil.dbPlayer") Set objHistory = New dbHistory Set objPlayer = New dbPlayer ok = objHistory.Add(name, balance, num_games) ok2 = objPlayer.Delete(name) Move = ok And ok2 If Move Then ctx.SetComplete Else ctx.SetAbort End If End If End Function Ключевой код метода выделен полужирным шрифтом. Мы создаем экземпляр внутреннего объекта, используя обычный оператор New. Затем мы вызываем метод Add у objHistory и метод Delete у objPlayer. Каждый из вызовов методов может завершиться неудачей, в зависимости от этого мы вызываем SetAbort или SetComplete. Непосредственно перед выделенным кодом имеется несколько "закомментированных" строк Этот код корректен, но не употребляется. В случае MTS нам понадобится специальный вызов метода Createlnstance объекта контекста для создания внутреннего объекта. СОМ+ не требует специального создания объектов, поскольку в СОМ+ работа с контекстом — это часть стандартной инфраструктуры СОМ. Использование наборов записей в СОМ+ ЕЯ обнаружил ошибку в ADO, которая приводит к некорректной работе при использо- jaHHH наборов записей в СОМ+. Обходной путь состоит в отказе от использования наборов записей и в непосредственном применении SQL, т.е. передаче оператора SQL методу Execute объекта Connection. Выше были приведены два примера такого кода, когда добавление записи производилось оператором SQL insert, а удаление — оператором delete. Альтернативный подход состоит в использовании наборов записей и вызове методов AddNew/Update или Delete, что вполне нормально работает при вызове извне СОМ+, но не работает, когда компоненты импортируются в СОМ+. Уровень данных Уровень данных предоставляется SQL Server. В зависимости от имеющегося у вас аппаратного обеспечения базы данных Game и History могут размещаться на одном или на разных компьютерах. В случае их размещения на одном компьютере конфигурация будет такой, как показано на рис. 19.1 (за исключением использования OLE Transactions вместо протоколов Х/Open), в случае использования двух компьютеров конфигурация будет такой, как показано на рис. 19.2. Перед тем как настроить базу данных History на удаленном компьютере, вам необходимо создать ее там и настроить ODBC на локальном компьютере для доступа к удаленному SQL Server, как показано на рис 19 7. Уровень представления Клиентская программа уровня представления обеспечивает изображенный на рис. 19.5 графический интерфейс пользователя. Эта программа находится в каталоге Chapl9\PlayerAdmin. Вы можете запустить эту программу и понаблюдать за поведе-
нием транзакций. Весь специализированный код СОМ+ для работы с транзакциями находится на среднем уровне и уровне данных, а клиентская программа представляет собой не что иное, как обычный клиент СОМ. Вот весь код обработчика кнопки Move, который выполняет транзакцию: Private Sub cmdMove_Click() Dim objMove As New bMove Dim ok As Boolean ok = objMove.Move(txtName, txtBalance, txtNumGames) If Not ok Then MsgBox "Could not move " & txtName End If End Sub Рис. 19.7. Настройка источника данных ODBC для удаленного SQL Server Автоматическая деактывызацыя метода Мы видели, что СОМ+ требует минимального количества кода на среднем уровне для реализации транзакционного поведения, который в конечном итоге приводит к вызову SetComplete или SetAbort. В большинстве случаев, выполнив соответствующие настройки, можно избежать даже этого вызова. В СОМ+ Explorer вы можете сконфигурировать метод Move интерфейса _bMove (на рис. 19.8 показано, как найти этот метод в СОМ+ Explorer). Для установки флага автоматической деактивизации выберите метод Move, щелкните на нем правой кнопкой мыши и выберите в контекстном меню команду Properties. Во вкладке General диалогового окна свойств отметьте опцию Automatically deactivate this object when this method returns, как показано на рис. 19.9. Установка этого флага приводит к установке бита выполнения по окончании работы метода и бита согласованности на основании возвращаемого методом значения hresult. Если hresult указывает на успешное выполнение метода, бит согласованности устанавливается равным 1 (true), в противном случае — 0. В программе на C++ возвращаемое значение определяется явным образом, а на Visual Basic вызов считается успешным, если не была сгенерирована ошибка (т.е. для указания того, что метод был выполнен неудачно, программа на Visual Basic должна сгенерировать ошибку). В нашей демонстрационной программе этого не делается.
Рис. 19.8. Открытие отдельного метода в СОМ+ Explorer Рис. 19.9. Установка флага автоматической деактивизации Резюме Инфраструктура обработки транзакций Microsoft базируется на концептуальной модели X/Open DTP, состоящей из менеджеров транзакций и менеджеров ресурсов. Вместо Х/Open-протоколов ТХ и ХА Microsoft использует свой собственный основанный на СОМ протокол OLE Transactions. Роль менеджера транзакций выполняет Microsoft Distributed Transaction Coordinator. COM+ вводит концепцию автоматических транзакций, в которой транзакционное поведение ваших компонентов достигается с помощью настройки их атрибутов. Стандартный механизм перехвата СОМ+ предоставляет код для взаимодействия с DTC. В следующей главе мы продолжим работу с базами данных, рассмотрим, каким образом программы СОМ+ вызываются тонким клиентом, работающим с помощью Web-броузера.
Глава 20 Использование СОМ+ в Web-приложениях Web имеет важное значение как средства распространения не только информации, но и целых приложений Основным достоинством Web является простота распространения. Все, что требуется на компьютере- клиенте — это наличие Web-броузера. Никаких других ЕХЕ или DLL при этом не требуется Многие компании переходят к этому типу приложений, и их количество в будущем, несомненно, увеличится. В этой главе приводится обзор наиболее важных технологий для разработки Web-приложений, достигающих кульминации при использовании СОМ+ для разработки трехуровневых распределенных приложений. Их базовая архитектура та же, что и у уже рассмотренных нами приложений. Два основных отличия заключаются в использовании "тонкого" клиента на основе Web-броузера и в том, что в качестве коммуникационного протокола между клиентом и средним уровнем используется не DCOM, a HTTP. Исходя из концепции самодостаточности данной книги мы не предполагаем, что читатель имеет большой опыт разработки Web- приложений. Мы разъясним здесь все базовые концепции. Естественно, для создания реальных приложений требуются более глубокие знания, но для начала работы материала, представленного в этой главе, будет вполне достаточно. Классическая технология Web Internet была создана в лабораториях Министерства обороны США как ARPAnet и представляла собой чисто военное исследование. Со временем при поддержке национального научного фонда сеть распространилась и на научные организации, а затем подошла очередь и коммерческих организаций. Взрывной рост Internet начался в 1993 году с появлением первых Web- серверов и созданием World Wide Web. Web представляет собой коммуникационную систему, построенную на базе гипертекста и обеспечивающую клиенту доступ к богатой информации (текстовой, графической, аудио и прочей), расположенной на серверах Internet, с помощью различных протоколов Web был разработан в Европейском центре ядерных исследований CERN как новый тип информационных систем, позволяющих исследователям использовать общую информацию в процессе работы над тем или иным проектом. В большой степени это работа одного человека — Тима Бернерса-Ли (Tim Berners-Lee). Web базируется на открытой спецификации, использующей протоколы Internet.
Роль стандартов в технологии Web преувеличить невозможно, поскольку Web обеспечивает объединение разнотипных серверов и клиентов во всем мире. Эти серверы и клиенты работают на множестве различных компьютерных платформ. Единственный способ обеспечить возможность связи разнотипных систем — строгое следование стандартам. В классическом Web имеется три стандарта: ■ HTML (HyperText Markup Language — Язык разметки гипертекста) для распределенных в Web-документов; ■ HTTP (HyperText Transfer Protocol — Протокол передачи гипертекста) — коммуникационный протокол между клиентами и серверами Web; ■ CGI (Common Gateway Interface — Интерфейс общего доступа) — протокол общения Web-серверов с прикладными программами. Гипертекст и HTML Гипертекст представляет собой путь размещения информации на страницах (page). Связи (link) указывают на различные места в той же или другой странице, возможно, на другом узле Web. Место назначения связи называется якорем (anchor). HTML представляет собой язык, описывающий гипертекстовые документы, к которым можно получить доступ в Web. HTML был изобретен Тимом Бернерсом-Ли как часть разработки WWW в CERN. HTML представляет собой приложение стандартного обобщенного языка разметки (Standard Generalized Markup Language — SGML). Документы HTML представляют обычный ASCII-текст, который может быть создан в любом текстовом редакторе. Базовым компонентом документа является элемент (element), примерами которого могут служить заголовки, абзацы или списки. Дескрипторы (tags) используются для указания элементов в HTML-документе. Дескриптор состоит из символа левой угловой скобки (<), имени дескриптора и символа правой угловой скобки (>). Дескрипторы обычно располагаются парами, причем закрывающий дескриптор отличается от открывающего тем, что начинается с косой черты (/). <Н1> Пример заголовка первого уровня </Н1> Некоторые элементы могут включать атрибуты для указания дополнительной информации в начальном дескрипторе. <Р ALIGN=CENTER> Центрированный абзац </Р> HTML не чувствителен к регистру символов (за исключением некоторых Esc- последовател ьностей). Вот пример некоторой гипотетической HTML-страницы, базирующейся на примере Microsoft из поставки Personal Web Server for Windows 95 (файл MyHomePage.htm из каталога Chap20\Html). <HTML> <head> <title> My Home Page </title> </head> <body> <h2 align=center> Bob's Home Page </h2> Welcome to my web server running on Windows 2000
<р> <hr> Here are some of my interests: <ul> <li> Movies <li> Reading <li> Computers </ul> <hr> Here are some links to some other interesting Web sites: <P> <a href="http://www.microsoft.com">www.microsoft.com</a> <P> <a href="http://www.objectinnovations.com"> www.obj ectinnovations.com</a> </body> </html> Унифицированные локаторы ресурсов Для определения местоположения файлов (или других данных) в Internet World Wide Web использует унифицированные локаторы ресурсов (Uniform Resource Locator — URL). URL определяет метод доступа, адрес сервера и адрес файла (или других данных). scheme://host.domain[:port]/path/filename Чаще всего используются следующие схемы доступа: file Файл вашей локальной системы ftp Файл на FTP-сервере http Файл на сервере World Wide Web Номер порта обычно опускается (HTTP-серверы, как правило, используют порт по умолчанию — 80). Web-броузеры Web-броузер представляет собой программу-клиент, которая осуществляет доступ и выводит содержимое из World Wide Web. Она включает коммуникационное программное обеспечение для получения информации с помощью различных протоколов, таких как, например, HTTP. Броузер читает и выводит HTML-страницы. Если броузер не знает, каким образом должен интерпретироваться некоторый дескриптор, он просто игнорирует его. Web-броузер — это то средство, которое оживляет HTML, показывая вместо обычного текста множество различных визуальных элементов (и даже невизуальных — например, воспроизводит звуки). В настоящее время самые популярные броузеры — Microsoft Internet Explorer и Netscape Navigator. На рис. 20.1 показан внешний вид приведенной ранее HTML-страницы в Microsoft Internet Explorer. Обратите внимание, что Microsoft Internet Explorer выводит файл с локального диска так же, как если бы он был получен из Internet.
Рис. 20.1. Вид простейшей персональной HTML-страницы в Web-броузере Формы HTML Простейшие HTML-страницы только выводят информацию. Броузер может перемещаться в World Wide Web и отовсюду получать информацию. Однако он способен на большее. HTML обеспечивает возможность работы с формами, позволяя пользователю вводить данные, которые будут отосланы на сервер как часть запроса. Web- сервер может выполнить некоторую программу (об этом мы поговорим позже) и отправить пользователю ответ, зависящий от его ввода. В качестве иллюстрации рассмотрим простой пример с двумя текстовыми полями и одной кнопкой. Пользователь может ввести имя сервера (на котором хранятся сведения о цене) и название товара. Щелчок на кнопке Get Price отправляет информацию на Web-сервер, который запускает программу поиска цены и возвращает выданный ею ответ пользователю. На рис. 20.2 показано, как эта форма выглядит в броузере (вы не можете проверить ее на деле, пока мы не установим компонент COM PriceList. Price). Рис. 20.2. Форма HTML для поиска информации о цене
Вот код, который приводит к выводу формы: <!-- price.htm --> <HTML> <HEAD> <TITLE>Price of an Item</TITLE> </HEAD> <BODY> <FORM METHOD="POST" ACTION="price.asp"> Server <INPUT TYPE = "text" NAME="txtServer"> <P> Item <INPUT TYPE = "text" NAME="txtItem" VALUE = "dog bone"> <P> <INPUT TYPE = "submit" NAME="btnGetPrice" VALUE = "Get Price"> </FORM> </BODY> </HTML> Часть кода HTML method="post" представляет собой директиву для передачи данных на Web-сервер. Серверы Internet Internet сервер предоставляет возможность публикации информации в Internet или intranet. Web-сервер делает HTML-страницы доступными в World Wide Web с помощью протокола HTTP. Серверы Internet обеспечивают также доступ к информации с помощью других протоколов, таких как FTP или Gopher. В настоящее время протокол передачи файлов FTP все еще остается важным, эффективным и широко используемым, однако Gopher утрачивает свое значение, и последние версии Internet серверов Microsoft не поддерживают его. Internet-сервер обеспечивает также и другие сервисы, такие как, например, безопасность, аутентификация и администрирование. Последним продуктом Microsoft в этой области является Internet Information Services 5.0 (IIS), встроенный в Windows 2000. Web-сервер отвечает на запросы, передавая в ответ HTML-документы (или документы другого вида, например обычный текст). Имеется три вида "ответных" HTML- страниц: ■ статические, заранее подготовленные для ответа на запрос HTML-страницы; ■ динамические HTML-страницы, создаваемые сервером в ответ на запрос (для этой цели сервер может использовать сценарии CGI или ISAPI (Internet Server API) DLL. Важной технологией Microsoft для создания динамических HTML-страниц являются активные страницы сервера (Active Server Pages — ASP). CGI, ISAPI и ASP мы рассмотрим несколько позже в этой главе; ■ листинг каталога. Сервер может быть настроен для просмотра системы каталогов, и при отсутствии начальной страницы по умолчанию он просто высылает пользователю гипертекстовую версию листинга каталога.
Hypertext Transfer Protocol (HTTP) HyperText Transfer Protocol (HTTP) используется клиентами Web для подключения и обмена информацией с Web-сервером. Простейший запрос просто запрашивает HTML-страницу (или документ другого типа). Более сложный запрос может передавать введенные пользователем данные из форм. HTTP использует обычный текст. Порт, используемый им по умолчанию, — 80. HTTP представляет собой протокол без сохранения состояния; связь между клиентом и сервером прекращается после каждого ответа сервера. HTTP-сессия содержит заголовок, метод и требуемые данные. Взаимодействие Web-сервера и Web-клиента элегантно в своей простоте. Клиент делает запрос и получает ответ, и на этом взаимодействие оканчивается. Следующий запрос, как правило, инициируемый связью на HTML-странице, может быть обращен к другому Web-серверу. Имеется запрос и ответ, а все остальное — от лукавого. На рис. 20.3 показана эта простейшая архитектура. Web-клиент HTTP Web-сервер Рис. 20.3. Web-клиент работает с Web- сервером посредством HTTP Заголовки HTTP Заголовок запроса посылается клиентом серверу и определяет используемый запросом метод и возможности клиента (например, типы файлов, поддерживаемые клиентом). Заголовок ответа посылается сервером клиенту и содержит информацию о статусе транзакции (успешна она или нет) и типе отправляемых данных. Вот пример заголовка запроса, в котором клиент запрашивает статическую (заранее подготовленную8) страницу сервера, используя метод get: GET /bylaws.htm HTTP/1.0 Accept: text/html Accept: text/plain Accept: image/gif User-Agent: Mozilla/2.0 From: bob@www.smallcompany.com - - - пустая строка - - - Вот как следует понимать этот запрос. ■ Клиент использует метод GET для получения файла bylaws .htm с помощью HTTP версии 1.0. ■ Клиент поддерживает определенные типы MIME (Multipurpose Internet Mail Extension, многоцелевое расширение почты Internet). ° По сути, клиент не может отличить статическую страницу от динамической, такое деление имеет смысл только на стороне сервера Клиент получает информацию, а каким образом она была подготовлена на сервере, остается для него неизвестным В приведенном запросе файла bylaws .htm может и не существовать, как такового, он может быть создан в момент обработки запроса — Прим. перев.
■ Броузер пользователя совместим с HTTP "Mozilla/2.0" (имя, созданное фирмой Netscape из комбинации "Mosaic" и "Godzilla", которая рассматривала свой броузер как "убийцу броузера Mosaic". Microsoft эмулирует Netscape, чтобы ее броузер не был отвергнут сервером, ожидающим клиент Netscape.) ■ Пользователь идентифицируется полем From. Ответ Web-сервера Вот типичный ответ сервера на запрос: НТТР/1.0 200 ОК Date: Tuesday, 07-Dec-99 20:55:00 EST Server: IIS/5.0 MIME-version: 1.0 Last-modified: Monday, 02-Dec-99 7:15:00 EST Content-Type: text/plain Content-length: 3500 - - - пустая строка - - - < данные > Вот что означает ответ: ■ сервер согласен использовать HTTP 1.0 и отвечает, что запрос принят и успешно обработан (код 200); ■ сервер идентифицируется как IIS 5.0; ■ используется версия MIME 1.0; ■ тип MIME указан в поле Content-Type. В данном случае это обычный текст; ■ длина данных — 3500 байт; ■ сами данные следуют за пустой строкой. Методы HTTP Протокол HTTP имеет четыре различных метода, которые может использовав клиент. Чаще всего применяются методы get и post. GET — получение HTML-страницы и отправка результатов заполнения формы, присоединенных к URL. POST — отправка результатов заполнения формы в теле запроса. put — замена содержимого определенного URL данными из формы, пересылаемыми клиентом. head — Запрос статуса транзакции и информации заголовка (обычно используется в диагностических целях). Common Gateway Interface CGI представляет собой стандарт общения внешних программ с Web-сервером. Такие внешние программы, работающие в связке с Web-сервером, часто называются сценариями и могут быть написаны как на языках сценариев типа Perl, так и, например, на обычном С. Сценарий представляет собой автономную программу, работающую в отдельном процессе. CGI определяет механизм, посредством которого сценарии
обмениваются данными с Web-сервером, для чего используются переменные окружения, такие как, например, query_string. Сервер может пересылать данные сценарию на стандартный вход, а получать от него информацию через стандартный выход. Тот факт, что программы CGI работают в отдельных процессах, делает их менее эффективными, чем другие механизмы, и является существенной помехой для масштабируемости. Позже в этой главе мы рассмотрим другие, более эффективные механизмы. Поскольку CGI представляет собой стандарт, который поддерживается большинством Web-серверов и концептуально прост, мы вкратце изучим его для понимания принципов работы Web-серверов. Динамические Web-страницы Вместо существующей Web-страницы клиент может определить в URL программу. Web-сервер распознает такой запрос и вызывает программу, работающую в связке с Web- сервером: программа создает Web-страницу, возвращаемую клиенту посредством HTTP. В рассматриваемом далее примере клиент запрашивает программу datetime.exe, которая определяет дату и время и возвращает значения на Web-странице. Вот заголовок гипотетического запроса: GET /scripts/datetime.exe HTTP/1.О Accept: text/html Accept: text/plain Accept: image/gif User-Agent: Mozilla/2.0 From: bob@www.smallcompany.com - - - пустая строка - - - Web-сервер выполняет следующую обработку запроса: 1. Web-сервер получает HTTP-запрос и вызывает программу datetime.exe, указанную в URL. 2. Программа datetime.exe выводит заголовок, определяющий тип содержимого, пустую строку и данные. /* datetime.c */ #include <time.h> #include <stdio.h> void main() { char dbuffer [9] ; char tbuffer [9] ; printf("content-type: text/plain\n"); printf("\n"); _strdate( dbuffer ); printf( "The current date is %s \n", dbuffer ); _strtime( tbuffer ); printf( "The current time is %s \n", tbuffer ); } 3. Web-сервер создает полный HTTP-ответ. HTTP/1.0 200 OK Content-type: text/plain
- - - пустая строка - - - The current date is 05/19/00 The current time is 19:30:00 Еще немного об HTML-формах HTML предоставляет дескриптор form, который используется для сбора информации на стороне клиента и определяет действия, выполняемые сервером. <FORM ACTION="url"> . . . </FORM> Атрибутами дескриптора являются: ■ action. Дает URL, определяющий выполняемый на сервере сценарий. ■ method. Определяет метод HTTP, такой как get или post. ■ enctype. Определяет кодирование содержимого формы и используется только с методом post. В настоящее время определен единственный атрибут enctype: application/x-www-form-urlencoded Вот пример формы HTML (Chap20\Club\getdate.htm), которая представляет собой кнопку для вызова datetime.exe на сервере. <!— getdate.htm —><HTML> <HEAD> <TITLE>Get Date</TITLE> </HEAD> <BODY> <FORM ACTION="scripts\datetime.exe" METHOD="POST"> <P> <INPUT TYPE=SUBMIT VALUE="Get Date and Time"> </FORM> </BODY> </HTML> Изучение программирования Internet Теперь давайте убедимся, что у вас имеется все необходимое для изучения существующих приложений Web. Позже в этой главе мы покажем, каким образом реализовать трехуровневое распределенное приложение, позволяющее клиенту работать с SQL Server посредством компонента СОМ+, вызываемого Web-сервером. Для того чтобы такая конструкция работала, требуется большое количество настроенных и работающих составных частей. Однако, прежде чем переходить к такому большому приложению, стоит вначале сделать разминку, изучив более простую функциональность Internet. Рассматриваемый далее материал поможет вам получить твердые базовые знания, необходимые в дальнейшей работе. Internet Explorer 5.0 Internet Explorer 5.0 встроен во все версии Windows 2000 и автоматически инсталлируется при установке операционной системы. Microsoft Internet Explorer представляет собой интегрированную составную часть операционной системы, начиная с Win-
dows 98. Он устанавливается как броузер по умолчанию, так что двойной щелчок на файле с расширением . htm автоматически вызовет Microsoft Internet Explorer, в котором будет открыт этот файл. Несколько примеров HTML-файлов можно найти в каталоге Chap20\Html. В качестве микротеста откроем файл getdate.htm, который мы рассматривали в предыдущем разделе. Мы будем использовать содержащуюся в нем форму для тестирования сценария CGI (пока что вы не сможете запустить этот сценарий, к тому же для этого необходим доступ с помощью HTTP, а не из локального файла). Как выглядит этот файл в броузере, показано на рис. 20.4. На рис. 20.1 вы можете увидеть еще один пример HTML-страницы. Рис. 20.4. HTML-форма, которой мы воспользуемся для тестирования сценария CGI Чтобы можно было использовать "неподписанные" управляющие элементы ActiveX, потребуется небольшая настройка броузера — впрочем, об этом будет сказано немного позже. Internet Information Services 5.0 Web-сервер Microsoft предоставляется Internet Information Services 5.0 (аббревиатура IIS образована от Internet Information Server). IIS включен в состав Windows 2000 Server и инсталлируется по умолчанию. Администрируется IIS с помощью программы, вызываемой командой меню Starts Programs^ Administrative Tools^lnternet Services Manager. Первое, что следует проверить — это то, что Web-сервис работает. Для этого щелкните на имени вашего сервера в Internet Information Services. Вы должны увидеть список из четырех запущенных сервисов, включая Default Web Site, имеющий, как и ожидалось, порт 80. Любой сервис можно запустить, выключить и приостановить с помощью кнопок, расположенных на панели инструментов. То же можно сделать, щелкнув правой кнопки мыши на интересующем вас сервисе и выбрав соответствующий пункт контекстного меню. На рис. 20.5 показаны работающие в системе сервисы. Размещение материалов Разместить материалы на Web-узле можно, просто скопировав файлы в папку inetpub\wwwroot, которая должна находиться в разделе, в котором установлена операционная система Windows 2000. По умолчанию Web-сервер просматривает каталог wwwroot в поисках файлов, пересылаемых броузеру клиента. В качестве первого примера скопируем файл MyHomePage.htm из каталога Chap20\Html в каталог wwwroot. Теперь вы должны иметь возможность просмотреть эту страницу в вашем броузере, указав URL http: //yourservername/myhomepage .htm. Здесь вместо yourservername следует указать реальное имя вашего сервера. Если вы запускаете Microsoft Internet Explorer на той же машине, что и сервер, можно воспользоваться именем localhost: http://localhost/myhomepage.htm
Чтобы не рисковать испортить файлы в каталоге wwwroot, будем работать в подкаталогах. Для этого скопируйте папки club и Html из каталога Chap20 в каталог wwwroot. В броузере вы должны указывать путь к файлу, например: http://localhost/html/myhomepage.htm. Рис. 20.5. Администрировать IIS помогает инструмент Internet Services Manager Впрочем, возможно, более удобным для вас представится изменение рабочего каталога в программе администрирования IIS. Для изменения рабочего каталога (в нашем случае — на каталог club) щелкните правой кнопкой мыши на Default Web Site и выберите в контекстном меню команду Properties, а в появившемся диалоговом окне — вкладку Home Directory. Затем укажите требуемый каталог в поле Local Path (рис. 20.6). Обратите внимание на опцию Directory Browsing — сейчас ее не надо отмечать, но вскоре мы ею воспользуемся. Рис. 20.6. Определение нового рабочего каталога сервера
Теперь вернитесь в ваш броузер и просто введите имя компьютера, без определения файла: http://localhost/ При этом перед вами появится начальная страница "Von Neumann Computer Club", показанная на рис. 20.7. Рис. 20.7. Переход к начальной странице по умолчанию Что же произошло? То, что и всегда, когда вы вводите имя сервера без указания конкретной страницы. Все, что требуется, для того чтобы такое обращение к серверу корректно работало, — это наличие страницы default.htm, а, как легко убедиться, в каталоге club такая страница имеется. Просмотр каталога Мы уже упоминали о том, что возможны три варианта ответов сервера на запрос броузера клиента, а именно: статические страницы, динамические страницы и листинги каталогов. Последний вариант легко разрешить, отметив опцию Directory Browsing в диалоговом окне, приведенном на рис. 20.6. Если вы отметите эту опцию, измените рабочий каталог на html и вновь введете только имя узла в броузере — http://localhost/, — то, поскольку в этом каталоге нет файла default.htm, получите список файлов (и подкаталогов, если таковые имеются) каталога со связями с файлами на сервере (рис. 20.8). Для просмотра файла в броузере достаточно щелкнуть на нем. Заметим, однако, что этот режим удобен для тестирования, но на рабочих коммерческих серверах не применяется.
Рис. 20.8. На сервере разрешен просмотр каталогов Запуск сценариев CGI Теперь познакомимся с работой сценариев CGI. Соберите две программы — Chap20\datetime и Chap20\homepage (либо воспользуйтесь готовыми программами из каталога Chap20\Scripts). Скопируйте файлы datetime.exe и homepage.exe в каталог lnetpubs\Scripts. Теперь откройте getdate.htm в броузере и щелкните на кнопке Get Date and Time. При этом должна выполниться программа datetime.exe, а результат ее работы должен вернуться броузеру, как показано на рис. 20.9. Рис. 20 9. Результат работы сценария CGI Заметим, что тот же результат мы могли бы получить, непосредственно введя URL http://localhost/scripts/datetime.exe. Теперь запустим другой сценарий, открыв страницу name.htm. Этот пример немного интереснее. В нем выводится простая форма, в которой вы можете ввести имя. Введите, например, имя John и щелкните на кнопке Submit. При этом в броузере вы увидите страницу с именем John, показанную на рис. 20.10. Программа CGI выводит HTML-текст в стандартный выход, так что Web-сервер может собрать полный заголовок HTTP-ответа для отправки броузеру. Полностью код программы вы можете найти в файле Chap20\homepage\homepage.cpp. Программа
получает информацию из формы, посланную броузером. Эта информация согласно протоколу CGI передается программе через переменные окружения. Мы не станем вдаваться в детали, поскольку будем в основном пользоваться ASP, что гораздо проще. Рис. 20.10. Страница, созданная сценарием CGI с использованием введенных пользователем данных Цель приведенных примеров — показать механизм работы сценариев на сервере в его простейшем виде (простейшем с точки зрения Web-сервера, но не программиста). Эта архитектура проиллюстрирована рис. 20.11. Web-клиент передает запрос, используя протокол HTTP, на Web-сервер, возможно, с информацией, введенной пользователем. Web-сервер передает эту информацию Web-программе с использованием протокола CGI. Web-программа создает часть ответа и передает ее Web-серверу, который формирует полный ответ, который направляется клиенту. Web-клиент HTTP Web-сервер CGI 4 Web-программа Рис. 20.11. Архитектура создания динамических страниц Web- программой Если вы попытаетесь ввести в форму полное имя, например John Smith, то увидите на полученной от сервера странице нечто странное: John+Smith's home page. Это — часть специального кодирования, выполняемого HTTP. Если вы пишете программу на уровне простого CGI, вы должны сами декодировать получаемую информацию. Поскольку мы не будем этим заниматься, то и рассматривать правила кодирования мы не станем. Web-технологии Microsoft Microsoft, возникшая в Internet несколько лет назад буквально ниоткуда, стала одним из основных игроков на этом прибыльном поле, создав множество собственных технологий для разработки Internet-приложений Если вы планируете разрабатывать собственные Internet-приложения с помощью инструментария Microsoft, то вам просто необходимо познакомиться с базовыми технологиями Internet, представленными в этом разделе. Имеется множество продуктов, типа Site Server или Commerce Server, описывать которые мы не будем. Наша цель — познакомиться с основами, необходимыми для применения СОМ+ при разработке Web-приложений.
Клиентские Web-технологии Microsoft Изначально усилия Microsoft были направлены на клиентскую часть программного обеспечения, и в основном — на использование Java. Основная цель при этом состояла в развитии клиентской части и разгрузке сервера. Однако работа в этом направлении показала, что на практике решить эту задачу гораздо сложнее, чем ожидалось. И проблема заключается в самой сильной стороне Web — ее универсальности. К большому сожалению Microsoft, в мире все еще существуют различные операционные системы, разные броузеры, и клиентские технологии должны работать на всех из них. К тому же всегда находятся пользователи, которые предпочитают старые броузеры, и уж тут общий знаменатель — обычный HTML, причем достаточно старой версии. Важным "водоразделом" при создании приложений для Web является область их размещения — Internet или intranet. Последний термин означает внутреннюю сеть предприятия, основанную на протоколах Internet. При этом все клиенты находятся внутри предприятия, так что компания может контролировать, что находится на сервере, а что — на стороне клиента, и диктовать при этом свои условия. В случае же Internet компания не в состоянии влиять на клиент. Например, Web-узел компании представляет собой Internet-приложение. Компания хочет, чтобы внешние пользователи могли получать доступ к узлу и информацию о продукции компании. Установить при этом некоторое программное обеспечение, которое смогут использовать не все клиенты, значит, лишиться множества потенциальных покупателей и, следовательно, доходов. В то же время, приложения для коммивояжеров компании могут использовать определенные технологии на стороне клиента, так как коммивояжеры могут быть снабжены компанией лэптопами с необходимым программным обеспечением. Хотя приложение может изначально разрабатываться для применения в intranet, это в любом случае ограничивает его гибкость. Если же приложение разрабатывается как приложение Internet, то гибкость такого приложения существенно выше и новые пользователи легко приспосабливаются к такому приложению. Мы же используем выражение "Web-приложение" как более нейтральное, применимое как к приложениям intranet, так и Internet. Хотя, как мы выяснили, развитие серверных технологий более важно, мы начинаем наше рассмотрение с клиентских технологий. Такой порядок отвечает историческому развитию, а кроме того, некоторые из клиентских технологий могут применяться и на сервере. Сценарии В определенных случаях очень эффективно выполнение некоторых действий на стороне клиента — например, выполнение проверки корректности введенных в форму данных. Как часто вы заполняли формы, допуская ошибки, которые выявлялись только после того, как информация отправлялась на сервер и оттуда приходил ответ о некорректно заполненной форме? Не будет ли быстрее и эффективнее провести такую проверку до того, как информация уйдет на сервер? Для такого вида работ идеально подходят сценарии на стороне клиента. Основная идея очень проста. События типа щелчка на кнопке вызывают соответствующую функцию обработки. Объектная модель предоставляет вам доступ к введенным в форму данным. Ваш сценарий в функции обработки события может проверить корректность введенных данных и, если они допустимы, передать запрос серверу. Файл valid.htm (глава Chap20\Html) демонстрирует такую проверку вводимых данных. Откройте этот файл на вашем локальном Web-узле в Internet Explorer, введите неверные данные и щелкните на кнопке Submit. При этом вы получите сообщение об ошибке, показанное на рис. 20.12.
Рис. 20.12. Проверка данных формы на сто- роне клиента Если вы пойдете дальше и передадите серверу корректные данные, то получите ответ (в данном случае он не зависит от введенной вами информации, так как это не более чем простейшая демонстрация). Ниже приведен HTML-код. Обратите внимание на то, что код сценария размещается в HTML-комментарии, для того чтобы броузер, не поддерживающий VBScript, мог просто проигнорировать код сценария. <!— valid.htm —> <HTML> <HEAD> <TITLE>Validation Demonstration</TITLE> <SCRIPT LANGUAGE="VBScript"> Sub btnSubmit_OnClick Dim form Set form = Document.Forml If IsDate(form.txtName.Value) Then MsgBox "Date is valid" form.Submit Else MsgBox "Please enter a valid date" End If End Sub --> </SCRIPT> </HEAD> <BODY> <Hl>Validation Demonstration</Hl> <FORM NAME = "Forml" ACTION="scripts\datetime.exe" METHOD="POST" ENCTYPE="application/x-www-form-urlencoded"> <P> Enter a date <INPUT NAME="txtName" VALUE="" SIZE=8> <P> <INPUT NAME="btnSubmit" TYPE=BUTTON VALUE="Submit"> <INPUT TYPE=RESET VALUE="Reset">
</FORM> </BODY> </HTML> VBScript и JavaScript Все наши примеры сценариев используют язык VBScript. Причина этого кроется в удобстве. Поскольку VBScript представляет собой подмножество Visual Basic, его синтаксис хорошо известен. Так как в нашей книге мы широко используем Visual Basic, выбор VBScript представляется совершенно естественным. В реальной жизни стоит подумать об использовании языка JavaScript, как более стандартизированного и широко распространенного (так, JavaScript поддерживается Web-броузерами Microsoft и Netscape). Управляющие элементы ActiveX Управляющие элементы ActiveX представляют собой особенно богатый возможностями тип компонентов СОМ. Они используются в среде разработки (так, в главе 1, "Что такое СОМ+" было показано, как легко создать Web-броузер, используя Visual Basic. He менее просто создать Web-броузер и на Visual C++). Подобно компонентам СОМ, управляющие элементы ActiveX могут использоваться многими языками программирования. Главное достоинство управляющих элементов ActiveX состоит в том, что они могут использоваться прикладным программистом как встроенные управляющие элементы, и при этом не требуется никакого знания СОМ. Сторонними производителями в настоящее время разработаны тысячи управляющих элементов ActiveX. Они же могут использоваться и в Web-приложениях с помощью специального дескриптора object. Этот дескриптор позволяет разместить управляющий элемент ActiveX (на самом деле — любой объект СОМ) на HTML-странице. Для идентификации управляющего элемента используется CLSID. Вы можете вызывать методы объекта и устанавливать его свойства с помощью языка сценариев. Вот пример HTML-страницы со внедренным управляющим элементом Shape Preview (см. файл client.htm в каталоге Chap20\club): <!-- client.htm —> <HTML> <HEAD> <TITLE>ActiveX Control Demo</TITLE> </HEAD> <BODY> <H2>ActiveX Control Demo<BR> </H2> <script language = "vbscript"> <! — sub NewShape if btnShape.Item(0).Checked then txtShape.Value = "Rectangle" Shapel.ShapeType = 0 elseif btnShape.Item(1).Checked then txtShape.Value = "Ellipse" Shapel.ShapeType = 1 else txtShape.Value = "No selection" end if
end sub sub cmbColor_onChange if cmbColor.selectedlndex = 0 then txtColor.Value = "Red" Shapel.ForeColor = &H0000FF elseif cmbColor.selectedlndex = 1 then txtColor.Value = "Blue" Shapel.ForeColor = &HFF0000 else txtColor.Value = "No selection" end if end sub — > </script> <P> Select a shape and a color<BR> <P> Shape: <INPUT TYPE="RADIO" NAME="btnShape" VALUE="Rectangle" OnClick="NewShape">Rectangle <INPUT TYPE="RADIO" NAME="btnShape" VALUE="Ellipse" OnClick="NewShape">Ellipse <P> Color: <SELECT NAME="cmbColor" > <OPTION SELECTED VALUE="Red">Red <OPTION VALUE="Blue">Blue</SELECT> <BR> <P> Your selection is shown via text boxes<BR> <P> Shape: <INPUT NAME="txtShape" VALUE="" MAXLENGTH="25" SIZE=25> <P> Color: <INPUT NAME="txtColor" VALUE="" MAXLENGTH="2 5" SIZE=25> <P> Your selection is shown via an ActiveX Control<BR> <P> <object id ="Shapel" width=100 height=50 classid="clsid:B1028D2C-35A7-llDl-A01B-00A024D06632" codebase="http://localhost/shape.dll" > </object> </BODY> </HTML> Для запуска этой демонстрационной страницы вначале следует построить управляющий элемент Shape Preview, расположенной в каталоге Chap20\Shape, и зарегистрировать его. Затем надо открыть файл client.htm в Internet Explorer (как локально, так и через локальный Web-узел). На странице с помощью стандартных управляющих элементов можно выбрать форму и цвет управляющего элемента и тут же увидеть его "живьем".
Настройка безопасности в Internet Explorer Если вы пользуетесь настройками Internet Explorer по умолчанию, то при попытке задать управляющему элементу новые свойства на описанной странице вы получите сообщение об ошибке, показанное на рис. 20.13, в котором говорится о том, что настройки системы безопасности не разрешают использовать "ненадежный" управляющий элемент ActiveX. Рис. 20.13. Сообщение о нарушении установок безопасности Internet Explorer Для изменения настроек Internet Explorer 5.0 (чтобы вы могли пользоваться описанным управляющим элементом) воспользуйтесь командой меню Tools^Internet Options и выберите вкладку Security. Затем щелкните на кнопке Custom Level и в появившемся диалоговом окне выберите опцию Prompt для первых трех настроек управляющих элементов ActiveX: ■ Download signed ActiveX controls; ■ Download unsigned ActiveX controls; ■ Initialize and script ActiveX controls not marked as safe (рис. 20.14). Рис. 20.14. Настройка безопасности в Internet Explorer 5.0 Вернитесь к демонстрационной странице. Теперь при попытке изменения свойств управляющего элемента ActiveX вы получите не сообщение о фатальной ошибке, а всего лишь диалоговое окно предупреждения, в котором можно разрешить продолжать выполнение сценария. Разрешите его; после этого вы должны увидеть, как изменяется цвет и форма управляющего элемента на странице, показанной на рис. 20.15.
Рис. 20.15. Работа управляющего элемента ActiveX на Web-странице Подход Microsoft к безопасности при работе с управляющими элементами ActiveX отличается от подхода Sun, принятого при работе с Java. Sun запрещает ап- летам Java "вылезать из своей песочницы", где они не могут ничего натворить и никоим образом не могут получить доступ к системным ресурсам. Такой подход, при всей его безопасности, имеет тот недостаток, что резко ограничивает возможности аплетов. Управляющие элементы ActiveX имеют гораздо большие возможности и настраиваемую проверку безопасности, основанную на том, что управляющий элемент ActiveX может быть помечен как "надежный" и его работа в системе разрешена. К сожалению, пользователь не имеет никакой реальной возможности убедиться в надежности управляющего элемента ActiveX, кроме заверений его разработчика. Загрузка управляющего элемента ActiveX После изменения настроек безопасности для сценариев наш управляющий элемент работает, поскольку он установлен и зарегистрирован в нашей системе. Однако требовать от пользователя, чтобы он загрузил и установил управляющий элемент ActiveX в своей системе, — не самая лучшая идея. Хотелось бы, чтобы пользователь мог загрузить и автоматически установить управляющий элемент в системе. Для этого можно воспользоваться атрибутом codebase дескриптора object. Еще раз рассмотрим, каким образом был определен наш объект на HTML-странице. <object id ="Shapel" width=100 height=50 classid=Mclsid:B102 8D2C-35A7-llDl-A01B-00A024D0 6632" codebase="http://localhost/shape.dll" > </object>
Атрибут codebase определяет URL, где может быть найден и откуда может быть загружен управляющий элемент, который в настоящее время не установлен в системе. Для того чтобы увидеть эту возможность в работе, дерегистрируйте управляющий элемент (запустив unreg_shape.bat из каталога Chap20\Shape), затем скопируйте shape.dll в рабочий каталог Web-сервера (который в настоящее время установлен в lnetpub\wwwroot\Html) и вновь обратитесь к странице client.htm, воспользовавшись URL http://localhost\client.htm. Выполните обновление страницы, чтобы убедиться, что она загружена именно с сервера. При этом вы получите предупреждение системы безопасности, показанное на рис. 20.16. Рис. 20.16. Предупреждение системы безопасности о загрузке управляющего элемента ActiveX Щелкните на кнопке Yes. При этом файл shape.dll будет загружен в вашу систему и инсталлирован (зарегистрирован). После этого вы должны установить свойства, как и перед этим, и увидеть управляющий элемент ActiveX в работе. Технология Microsoft "Authenticode" использует систему цифровой подписи. Цифровую подпись можно получить в независимой компании (типа Verisign), которая получает приложения от отдельных разработчиков и компаний для цифровой подписи. Цифровая подпись может использоваться различными путями, такими как обеспечение идентификации по электронной почте или при электронной коммерции. Так, система цифровой подписи подтверждает, например, что компания занимается легитимным бизнесом и использует сертификат для подписи управляющих элементов ActiveX. Когда пользователь вызывает Web-страницу с имеющимся на ней подписанным управляющим элементом ActiveX, он видит сертификат, идентифицирующий компанию. Если компания хорошо известна, пользователь может разрешить загрузку и инсталляцию управляющего элемента, если компания пользователю не известна, он может отказаться от загрузки. Выбор — за пользователем. Серверные Web-технологии Microsoft Как мы уже упоминали ранее, во введении в предыдущий раздел, технологии на стороне клиента имеют основной недостаток, состоящий в том, что они не универсальны. Управляющие элементы ActiveX, например, требуют от пользователя работать с Internet Explorer как с Web-броузером. Основное достоинство серверных технологий состоит в том, что вы можете выбирать для сервера подходящую платформу и инструментарий, и, если вы предоставляете клиентам информацию в виде обычного HTML (возможно, с применением базовой части JavaScript), работать с ней смогут все.
Internet Server API (ISAPI) CGI представляет собой стандарт, у которого имеется множество недостатков. Каждая Web-программа, запускаемая Web-сервером с использованием CGI, работает как отдельный процесс. Это приводит к большим накладным расходам и ограничению масштабируемости. Решение Microsoft состоит в программном интерфейсе Internet Server (ISAPI), который определяет интерфейс расширений сервера, реализованных как DLL. Эти DLL работают в том же адресном пространстве, что и Web-сервер. Базовая архитектура показана на рис. 20.17. Web-клиент HTTP Web-сервер ISAPI DLL-расширения Рис. 20.17. Базовая архитектура ISAPI Концептуально ISAPI работает так же, как и CGI. Web-сервер получает запрос от Web-клиента. Вместо того чтобы использовать переменные окружения, Web-сервер работает с ISAPI DLL посредством блока управления расширением (Extension Control Block — ЕСВ). Web-сервер предоставляет функции обратного вызова ReadClient и WriteClient для чтения и записи данных Web-сервера (то, что в случае CGI происходит через систему стандартного ввода-вывода (stdin и stdout). DLL расширения разбирает данные, полученные от сервера, и компонует ответ, который передается назад серверу. Затем сервер добавляет к ответу заголовок и отсылает полный ответ назад Web-клиенту. Visual C++ упрощает создание DLL расширения ISAPI с помощью ISAPI Extension Wizard. Имеется также ряд упрощающих работу классов MFC. Давайте рассмотрим маленький демонстрационный пример. Создайте новый проект Visual C++ в каталоге ComPlus\Chap20\Demos (завершенную версию программы можно найти в каталоге ComPlus\Chap20\Hello). Тип создаваемого проекта — ISAPI Extension Wizard (рис. 20.18). Рис. 20.18. Создание нового проекта ISAPI Extension Wizard в Visual C++
В следующем диалоговом окне примите все предлагаемые по умолчанию настройки для объектов Server Extension. В сгенерированном проекте отредактируйте метод Default класса CHelloExtension таким образом, чтобы он просто возвращал строку "Hello from ISAPI". void CHelloExtension::Default(CHttpServerContext* pCtxt) { StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt « _T("Hello from ISAPI\r\n") ; EndContent(pCtxt); } Соберите DLL и скопируйте полученный файл hello.dll в каталог inetpub\Scripts. Теперь вашу DLL можно вызвать, указав URL http://localhost/scripts/hello.dll (результат вызова представлен на рис. 20.19). Рис. 20.19. Вызов DLL расширения ISAPI в Internet Explorer Более интересный пример можно найти в файле join.htm (каталог Chap20\club). Для того чтобы посмотреть на него в действии, сначала следует собрать проект, взяв его в каталоге Chap20\clubisap, и скопировать полученный файл clubisap.dll в каталог inetpub\Scripts (если вы не хотите возиться с проектом, готовый файл clubisap.dll можно найти в каталоге Chap20\scripts). Теперь вернемся в Internet Service Manager, изменим рабочий каталог на wwwroot\club и отменим опцию Directory browsing (см. рис. 20.6). Поскольку в каталоге club имеется файл default.htm, вызвать начальную страницу можно, просто указав URL http: //localhost/. Щелкните на связи Join— перед вами появится простейшая форма, в которой вы можете ввести ваше имя (рис. 20.20). Введите ваше имя и щелкните на кнопке Join Now. Вы увидите отладочное сообщение об успешном открытии файла и добавлении в него записи. DLL расширения clubisap.dll записывает данные в обычный файл, который создается при первой записи в него. Вы можете добавить в клуб еще несколько членов, а затем просмотреть список, щелкая на кнопке List Members. При этом вы должны получить список внесенных в файл членов клуба. Естественно, в реальной ситуации вы, вероятно, будете использовать для хранения информации о членах клуба базу данных, а не обычный файл. Мы и в самом деле будем работать с базами данных из Web-сервера, используя приложения СОМ-К
Рис. 20.20. Форма для вступления в клуб ISAPI — технология, которую следует выбирать, если нужно добиться высокой производительности Web-приложения. Программирование ISAPI выполняется на достаточно низком уровне, поэтому Microsoft предоставляет еще одну технологию, более высокого уровня, называемую активными страницами сервера, которую мы рассмотрим в следующем разделе. Active Server Pages (ASP) Active Server Pages (ASP) — самая популярная технология Microsoft для реализации Web-серверов. Эти страницы работают на сервере и, таким образом, позволяют избежать описанных ранее проблем, связанных с клиентскими технологиями. ASP достаточно эффективны в работе и очень просты в использовании. ASP отличаются от CGI и ISAPI способом вызова. На самом деле ASP представляют собой расширенные HTML-файлы с дополнительными сценариями. Они не являются отдельными файлами, выполняемыми Web-сервером, и, таким образом, броузер может непосредственно указывать на них. Обычный HTML-файл можно превратить в ASP-файл путем простого переименования с заменой расширения файла (обратное неверно). Для начала взгляните на пример ASP-файла (все ASP-файлы, с которыми мы работаем в этой главе, находятся в каталоге chap20\ASP). Скопируйте каталог asp в каталог inetpub\wwwroot. Вызовите Internet Services Manager для того, чтобы сделать каталог asp рабочим каталогом сервера, кроме того, убедитесь, что параметру Execute Permissions присвоено значение Scripts, и, для собственного удобства, отметьте опцию Directory browsing. Правильная настройка сервера показана на рис. 20.21. Теперь обратимся к файлу datetime.asp, причем обязательно посредством протокола HTTP, используя URL http: //localhost/datetime.asp. Вы увидите в окне броузера дату и время (рис. 20.22), правда, в несколько ином формате, чем ранее, — поскольку код сценария вызывает функции VBScript, которые отличаются от функций С, использовавшихся в примере работы с CGI. Основное различие между ASP и CGI состоит в том, каким образом осуществляется вызов функций. В случае CGI имелась HTML-форма, в которой пользователь щелкал на кнопке. При этом HTTP-запрос посылался на сервер, который вызывал сценарий, используя CGI. Сценарий, запущенный как отдельный процесс, выдавал данные, которые Web-сервер использовал для создания ответа клиенту. В случае ASP код сценария находится непосредственно на странице, запрошенной пользователем. Распознавая расширение .asp запрашиваемой страницы, Web-сервер вызывает DLL расширения ISAPI asp.dll, которая работает в том же процессе, что и Web-сервер. Эта
DLL выполняет сценарий, вызывающий функции получения даты и времени. Web- сервер строит HTTP-ответ и отправляет его клиенту. Ниже приведен код ASP-файла, который очень похож на HTML-файл, в который добавлен сценарий. <!— datetime.asp --> <html> <head> <title> Date and Time Using ASP </title> </head> <body BGCOLOR=WHITE> Date: <% = Date %> <br> Time: <% = Time %> <br> </body> </html> Рис. 20.21. Каталог, в котором находятся ASP- файлы, должен иметь права выполнения сценариев Рис. 20.22. Вывод ASP-страницы, на которой сценарий вызывает встроенные функции
Код сценария помещен в ограничители <% ... %>. Такой сценарий выполняется сервером, а не клиентом. Впрочем, на одной странице могут находиться как сценарии, выполняющиеся сервером, так и сценарии, выполняющиеся клиентом, которые различаются способом их объявления. ASP и СОМ+ Вся мощь технологии ASP проявляется при ее объединении с технологией СОМ/СОМ+. Мы видели, что ASP выполняются в контексте процесса сервера посредством DLL расширения ISAPI, а значит, эта технология эффективна. Но когда она приступает к работе, тогда, поскольку код сценария интерпретируется, эффективность оказывается не так высока, как при использовании скомпилированного кода. Сценарии — отличное средство, если они невелики по размеру и выполняют свою работу посредством вызываемых ими скомпилированных функций (пример тому мы только что видели при показе даты и времени), однако количество встроенных функций весьма ограничено. Поэтому поднять эффективность применения сценариев может технология, которая обеспечит возможность вызова любого скомпилированного вами кода из сценария. И эта технология — СОМ. Помимо возможности вызова компонентов СОМ, технология ASP сама по себе реализована с использованием СОМ, а значит, применение СОМ-объекгов в сценариях ASP совершенно естественно. Вы используете в сценариях ASP различные встроенные объекты, но с тем же успехом и простотой можете применять свои собственные компоненты СОМ, иногда называемые компонентами активного сервера (Active Server Components). Примером компонентов активного сервера могут служить ADO. ADO могут быть вызваны непосредственно из сценария ASP для программирования работы с базами данных. Однако еще лучше инкапсулировать работу с базами данных в ваш собственный компонент СОМ, который работает с ADO или OLE DB. Эти компоненты СОМ впоследствии могут быть импортированы в СОМ+ и пользоваться всей мощью предоставляемых СОМ+ сервисов — например, автоматическими транзакциями. Объектная модель ASP Имеется пять встроенных объектов ASP. ■ Объект Server, предоставляющий функции общего назначения, а также возможность создания экземпляров невстроенных компонентов СОМ посредством вызова метода CreateObject. ■ Объект Application, применяемый для совместного использования информации всеми пользователями приложения. ■ Объект Session, который можно использовать для хранения информации некоторого пользователя приложения. ■ Объект Request предоставляет всю информацию, связанную с запросом пользователя, и содержит ряд объектов коллекций, включая, например, коллекцию Form. ■ Объект Response может использоваться для отправки информации пользователю и содержит коллекцию Cookies. "Рабочими лошадками" среди перечисленных объектов являются Request и Response. Из объекта Request вы можете считывать информацию, введенную пользователем в HTML-форму. Объект Response используется для записи информации, передаваемой клиенту. Объект Request содержит пять коллекций.
■ ClientCertificate, которая получает информацию, пересылаемую протоколом Secure HTTP (с URL, начинающимся https). ■ Cookies, которая позволяет вам получить значения ключей (cookies), пересылаемые в HTTP-запросе. ■ Form, содержащая значения из HTML-форм. ■ QueryString, которая позволяет получить значения из строки запроса HTTP. ■ ServerVariables, позволяющая вам получить значения переменных среды сервера (используемых в программировании CGI). Запросы и ответы ASP Мы проиллюстрируем использование объектов Request и Response на небольшом примере, который просто возвращает пользователю введенное им в HTML-форме имя. Откройте в броузере файл echo.htm и введите имя (рис. 20.23). Рис. 20.23. Форма для ввода имени, которое сервер возвратит клиенту Щелкните на кнопке Echo и убедитесь том, что все работает, как описано. Вот использованный в этом примере HTML-код: <!-- echo.htm --> <HTML> <HEAD> <TITLE>Name Input</TITLE> </HEAD> <BODY> <FORM ACTION="echoback.asp" METHOD="POST"> <P> Name <INPUT NAME="txtName" VALUE=M" MAXLENGTH="25" SIZE=25> <P> <INPUT TYPE=SUBMIT VALUE="SubmitM> <INPUT TYPE=RESET VALUE="Reset"> </FORM> </BODY> </HTML>
Параметр action формы определяет ASP-файл, который использует объект Request для получения введенного пользователем имени. Коллекция Form применяется для обращения к элементу формы с введенным пользователем именем. Вот как это делается в ASP-файле: <!— echoback.asp --> <%@ LANGUAGE = VBScript %> <HTML> <HEAD> <TITLE>Echo Back</TITLE> </HEAD> <BODY> <% name = Request. Form ("txtName") Response.Write("Hello, " & name) %> </BODY> </HTML> Использование СОМ+ в трехуровневом Web-приложении Теперь у нас имеется весь материал, необходимый для понимания принципов работы трехуровневого Web-приложения, в котором используется СОМ+ в качестве среднего уровня. Концептуально оно такое же, как и трехуровневое приложение DCOM, с некоторыми отличиями, связанными с наличием Web-компонентов. На рис. 20.24 вы можете ознакомиться с архитектурой таких приложений. Пунктирная линия от клиента к бизнес-логике показывает альтернативную архитектуру DCOM. При рассмотрении приведенной диаграммы может показаться, что предпочтительнее использовать технологию DCOM. Однако познакомимся с представленной на рисунке архитектурой поближе. Использование ASP имеет то достоинство, что активные страницы работают в контексте процесса IIS, и запускают сценарии в рамках этого процесса. Конечно, интерпретируемые сценарии несколько снижают общую производительность, но в хорошо разработанной системе сценарии коротки и используются только для вызова компонентов СОМ (представляющих бизнес-логику), которые и выполняют основную работу. Основное достоинство HTTP-клиентов — в простоте размещения. Такой клиент может работать где угодно, лишь бы в этом месте имелась возможность подключения к Internet. В то же время DCOM хорошо работает только в пределах intranet. При попытке воспользоваться DCOM в Internet вы получите множество проблем — начиная с того, что работа может быть просто остановлена ближайшим брандмауэром, и заканчивая проблемой поддержки подключения в глобальной сети. И DCOM, и HTTP имеют свои "экологические ниши". Архитектура с применением HTTP более гибка и обеспечивает работу Web-приложений — вот почему эта глава занимает в книге такое важное место. В главе 21, "Microsoft Message Queue" мы рассмотрим еще один альтернативный путь работы в глобальных сетях — с использованием MSMQ. Приложение — прейскурант СОМ+ Рассмотрим конкретный пример простейшего приложения для получения прейскуранта. Клиент может запросить как цену конкретного товара, так и попросить "огласить весь список". Вся необходимая информация содержится в таб-
лице Products базы данных Games, рассмотренной в главе 18, "SQL Server и ADO". Мы воспользуемся OLE DB-провайдером SQL Server, так что вам не надо беспокоиться о настройке источника данных ODBC. Неплохо бы перед началом работы убедиться, что база данных Game установлена и корректно функционирует на вашем компьютере, и если это не так, — вернуться к главе 18, "SQL Server и ADO" и выполнить все необходимые действия по созданию и настройке базы данных. Затем вызовите SQL Server Enterprise Manager и откройте таблицу Products базы данных Game (рис. 20.25). Заметьте, что мы изменили цену собачьей косточки (dog bone) с 5 на 500 на одном из серверов, оставив первоначальное значение на другом. Такое изменение легко вносится с помощью SQL Server Enterprise Manager путем ввода нового значения после того, как открыта необходимая таблица. Клиент \ нттр ! ! ! г% IIS 1 г ASP ^ ' Сценарий % \ Y Бизнес-логика Данные Рис. 20.24. Архитектура трехуровневого Web-приложения Теперь соберите серверную программу из каталога Chap20\PriceList. Создайте пустое приложение СОМ+ PriceApp и инсталлируйте PriceList.dll. После этого вы должны иметь один компонент — PriceList. Price с интерфейсом _Price и методами FindPrice и ListPrices, как показано на рис. 20.26.
Рис. 20.25. Таблица Products Рис. 20.26. Приложение СОМ+ с компонентом базы данных Game PriceList. Priсе Вот соответствующий исходный код Visual Basic: Public Function FindPrice(ByVal server As String, _ ByVal item As String) _ As Currency Dim strSQL As String Dim rsProd As New Recordset Dim conn As New Connection strSQL = "SELECT * FROM Products WHERE item = " & "'" & item & "," conn.Provider = "SQLOLEDB" conn.ConnectionString = "Server=" & server & "; " _ & "Database=Game;" _ & "uid=sa;" _ & "pwd=;" conn.Open rsProd.Open strSQL, conn, , adLockReadOnly, adCmdText 'Check to see if item is found If rsProd.BOF Then FindPrice = -1 Exit Function End If FindPrice = rsProd("price") End Function Public Function ListPrices(ByVal server As String) _ As Recordset Dim strSQL As String Dim rsProd As New Recordset Dim conn As New Connection strSQL = "SELECT * FROM Products " conn.Provider = "SQLOLEDB" conn.ConnectionString = "Server=" & server & "; " _
& "Database=Game;" _ & "uid=sa;" _ & "pwd=;" conn.Open rsProd.Open strSQL, conn, , adLockReadOnly, adCmdText Set ListPrices = rsProd End Function Вы можете протестировать это приложение СОМ+, воспользовавшись программой-клиентом, которая имеется в каталоге Chap20\PriceClient. При запуске программы вы можете оставить поле имени сервера пустым (в этом случае программа будет работать с сервером, расположенным на локальной машине) либо ввести в него имя удаленного сервера, как показано на рис. 20.27. Рис. 20.27. Работа с сервером Price List стандартного богатого клиента Использование объектов СОМ/СОМ + в ASP Ключом к построению мощных Web-приложений служит возможность использования в ASP объектов СОМ/СОМ+. В главе 11, "Автоматизация и программирование СОМ на Visual Basic" мы видели, как автоматизация позволяет использовать СОМ- объекты из сценариев. ASP управляются сценариями, поэтому они могут создавать и использовать объекты СОМ. Если компонент СОМ представляет собой сконфигурированный компонент СОМ. то к нашим услугам — вся мощь СОМ+. Поскольку языки сценариев, такие как VBScript, используют позднее связывание, для создания экземпляра нашего объекта мы будем использовать метод CreateObject, которому мы должны передать идентификатор программы. После этого мы можем вызывать методы и свойства объекта обычным способом. В качестве иллюстрации приведем пример ASP-страницы, осуществляющей поиск цены товара вызовом метода FindPrice компонента PriceList. Price (файл Chap20\ASP\price.asp). <!— price.asp --> <%@ LANGUAGE = VBScript %> <HTML> <HEAD> <TITLE>Price of an Item</TITLE>
</HEAD> <BODY> <% set objPrice = server.CreateObject("PriceList.Price") serverName = Request.Form("txtServer") item = Request.Form("txtltem") price = objPrice.FindPrice(serverName, item) If price >= 0 Then str = "Price of " & item & " is " & CStr (price) Else str = txtltem & " was not found" End If Response.Write(str) %> </BODY> </HTML> Web-версия приложения-прейскуранта Рассмотрим Web-версию приложения-прейскуранта. Она состоит из следующих частей. ■ Компонент COM PriceList .Price, инсталлированный в приложение СОМ+ PriceApp. ■ Одна HTML-страница — price_query.htm. ■ Две ASP-страницы — price. asp и pricelist. asp. К этому времени мы уже собрали и инсталлировали компонент СОМ. Для построения HTML и ASP-страниц не требуется никаких специальных действий. Если к этому времени вы уже скопировали папку ASP из каталога Chap20 в inetpub\wwwroot и назначили wwwroot\ASP рабочим каталогом, значит, вами сделано все необходимое для работы Web-приложения. Откройте в Internet Explorer страницу price_query.htm, используя URL http://localhost/price_query.htm. При этом вы увидите простую HTML-форму, позволяющую вам указать интересующий вас товар (с начальным значением "dog bone"). В форме имеются две кнопки: Get Price — для поиска цены товара и List Prices — для вывода всего списка товаров. Если поле Server вы оставите пустым, программа будет работать с SQL Server на локальной по отношению к Web-серверу машине. Увидеть форму можно на рис. 20.28. Рис. 20.28. HTML-форма приложения- прейскуранта
Ниже приведен исходный HTML-код этой формы. Обратите внимание на то, что на странице имеются две формы, атрибуты action которых позволяют загрузить различные ASP-страницы для разных кнопок (такое решение использовано для простоты, и не совсем корректно. О том, как должно выглядеть корректное решение, я расскажу чуть позже). <!— price_query.htm --> <HTML> <HEAD> <TITLE>Price of an Item</TITLE> </HEAD> <BODY> <FORM METHOD="POST" ACTION="price.asp"> Server <INPUT TYPE = "text" NAME="txtServer"> <P> Item <INPUT TYPE = "text" NAME="txtItem" VALUE = "dog bone"> <P> <INPUT TYPE = "submit" NAME="btnGetPrice" VALUE = "Get Price"> </FORM> <FORM METHOD="POST" ACTION="pricelist.asp" id=forml name=forml> <INPUT TYPE = "submit" NAME="btnListPrices" VALUE = "List Prices"> </FORM> </BODY> </HTML> Щелкнем вначале на кнопке Get Price. При этом будет загружена страница price.asp, которая осуществляет поиск цены определенного товара и записывает строку обычного текста, которая отправляется обратно на броузер (рис. 20.29). (С кодом этой страницы вы познакомились в предыдущем разделе.) Рис. 20.29. Ответ на запрос о цене конкретного товара Теперь щелкните на кнопке List Prices. При этом будет загружена страница pricelist.asp, которая создает HTML-таблицу с полным списком товаров и их цен, как показано на рис. 20.30.
Рис. 20.30. Ответ на запрос о полном списке цен товаров В этом случае ASP-код вызывает также метод CreateObject, а затем — метод ListPrices, который возвращает набор записей. Код сценария, приведенный ниже, в цикле вставляет полученные данные в HTML-таблицу. <!-- pricelist.asp —> <%@ LANGUAGE = VBScript %> <HTML> <HEAD> <TITLE>Price List</TITLE> </HEAD> <BODY> <% set objPrice = createobject("PriceList.Price") serverName = Request.Form("txtServer") set rs = objPrice.ListPrices(serverName) %> <TABLE BORDER> <CAPTION ALIGN=TOP> <b><FONT size = 6> <BR> </b></FONT> </CAPTION> <TH> item </TH> <TH> price </TH> <% do until rs.eof %> <TR> <% for i = 0 to rs.fields.count - 1 %> <TD> <%= rs.fields(i).value %> </TD> <% next %> </TR> <% rs.movenext %>
<% loop %> </TABLE> </BODY> </HTML> Дополнительные вопросы Web-программирования Как я уже говорил, HTML-страница price_query.htm не совсем корректна. Проблема возникнет при использовании удаленного сервера. Кнопка Get Price будет работать с указанным вами сервером, а кнопка List Prices — с сервером базы данных, локальным по отношению к Web-серверу сервером. Проблема возникает вследствие того, что управляющий элемент input принадлежит только первой форме, соответственно, строка кода serverName = Request.Form("txtServer") возвращает пустую строку, что воспринимается как указание на локальный сервер. Одно из решений состоит в использовании HTTP-метода get для передачи строки запроса серверу в виде части URL. К этому моменту у вас должно быть достаточно опыта, чтобы понять, как работает исправленный, корректный код. <!-- price_query_get.htm --> <HTML> <HEAD> <TITLE>Price of an Item</TITLE> </HEAD> <SCRIPT LANGUAGE="VBScript"> <! — Sub btnListPrices_OnClick() query = "?txtServer=" & Document.Forml.txtServer.Value Window.Navigate "pricelist2.asp" & query End Sub — > </SCRIPT> <BODY> <FORM NAME = Forml METHOD="POST" ACTION="price.asp"> Server <INPUT TYPE = "text" NAME="txtServer"> <P> Item <INPUT TYPE = "text" NAME="txtItem" VALUE = "dog bone"> <P> <INPUT TYPE = "submit" NAME="btnGetPrice" VALUE = "Get Price"> <INPUT TYPE = "button" NAME="btnListPrices" VALUE = "List Prices"> </FORM> </BODY> </HTML> При таком изменении вы должны позаботиться и о внесении изменений в ASP- страницу, которая теперь получает информацию не из коллекции Form, а из коллекции QueryString:
<!— pricelist2.asp --> <%@ LANGUAGE = VBScript %> <HTML> <HEAD> <TITLE>Price List</TITLE> </HEAD> <BODY> <% set objPrice = createobject("PriceList.Price") serverName = Request.QueryString("txtServer") set rs = objPrice.ListPrices(serverName) %> <TABLE BORDER> <CAPTION ALIGN=TOP> <b><FONT size = 6> <BR> </b></FONT> </CAPTION> <TH> item </TH> <TH> price </TH> <% do until rs.eof %> <TR> <% for i = 0 to rs.fields.count - 1 %> <TD> <%= rs.fields(i).value %> </TD> <% next %> </TR> <% rs.movenext %> <% loop %> </TABLE> </BODY> </HTML> Резюме Это глава оказалась самой большой главой книги, что говорит о важности вопроса. Мы начали с рассмотрения классической технологии Web, протокола HTTP, Web- серверов, которые обслуживают запросы клиентов, отправляя в ответ, как правило, HTML-страницы. Эти страницы могут быть как статическими, так и динамическими, т.е. создаваемыми в процессе работы Web-программами. Классическим интерфейсом для Web-программирования при этом служит CGI, недостатком которого является запуск отдельного процесса для каждого запроса. Затем мы изучили Web-технологии Microsoft, которые вначале делали упор на работу на стороне клиента, но со временем упор был перенесен на обработку на сервере. Базовой серверной технологией Microsoft явилась технология ISAPI, обеспечившая эффективный механизм работы DLL в контексте вызывающей программы. Мы завершили наше рассмотрение описанием активных страниц сервера, ASP, которые сочетают эффективность работы с простотой использования языков сценариев и возможностью создания и работы с объектами СОМ/СОМ+. В следующей главе мы рассмотрим еще один протокол — MSMQ, — который является альтернативой DCOM и HTTP.
Глава 21 Microsoft Message Queue Microsoft Message Queue (MSMQ) является третьим из основных протоколов, используемых при построении распределенных приложений на платформах Microsoft Первых два протокола, DCOM и HTTP, хотя и сильно отличаются по архитектуре и использованию, имеют одну общую черту— это синхронные протоколы Когда клиент вызывает метод, возврата из вызова не происходит до тех пор, пока сообщение не будет обработано В случае DCOM сервер обрабатывает запрос и возвращает управление; в случае HTTP Web- сервер посылает ответ в виде HTML-страницы (или в каком-то другом виде) Microsoft Message Queue является асинхронным Клиент осуществляет запрос, посылая сообщение. MSMQ помещает сообщение в очередь и немедленно возвращает управление клиенту, который, таким образом, не дожидается ответа сервера Сервер считывает сообщения из очереди и, если сообщение предусматривает наличие ответа, посылает сообщение-ответ. Наличие очередей сообщений позволяет уменьшить время отклика кл и- ента в распределенном приложении, поскольку ему не приходится ожидать соединения по сети и обработки запроса. Более того, очереди сообщений позволяют клиенту делать запросы, даже не будучи подключенным к серверу. Запросы размещаются в очереди клиента, и когда клиент будет подключен к серверу, система выполнения очереди передаст сообщения из очереди клиента в очередь сервера для дальнейшей обработки. СОМ+ упрощает использование Microsoft Message Queue в приложениях, обеспечивая сервис под названием Queued Components (QC, Компоненты, способные работать с очередями9) Настройка компонента, как способного работать с очередью, позволяет СОМ+ использовать MSMQ от вашего имени. Клиент создает экземпляр объекта, используя специальное имя, а затем делает обычный вызов метода. СОМ+ "маршализует" параметры в структуру сообщения MSMQ и отправляет это сообщение в очередь. Таким образом, программист не должен изучать MSMQ API и может положиться на скрытое использование MSMQ COM+. Эта глава начинается с введения в системы очередей сообщений и MSMQ и включает некоторые примеры программ. Хотя работающие с очередями компоненты и очень полезны, они не могут сделать все, что можно выполнить при непосредственной работе с Microsoft Message Queue. Кроме того, знания основных концепций и понимание работы шей работе. Здесь же мы обсудим работающие с очередями компоненты. у К сожалению, перевести дословно термин "Queued Components " не представляется возможным, поскольку перевод "очередящиеся компоненты" недопустим с точки зрения русского языка, а "очередные компоненты" имеет совершенно неверный смысл — Прим. перев.
Очереди сообщений и Microsoft Message Queue В этом разделе мы рассмотрим системы очередей сообщений и их реализацию Microsoft, называемую Microsoft Message Queue. Очереди сообщений Очереди сообщений представляют собой концептуально простую модель построения распределенных систем. Приложение создает сообщение и посылает его в очередь. Другое приложение может считывать сообщения из очереди, а затем, при необходимости, посылать сообщения еще в одну очередь. Последняя может быть как очередью первоначально отправившего сообщение приложения, так и очередью другого приложения. Очереди сообщений асинхронны. Как только сообщение отправлено в очередь, пославшее приложение тут же переходит к другой работе, не ожидая, когда сообщение будет прочитано адресатом Очереди сообщений позволяют приложениям работать автономно — сообщение может быть поставлено в очередь в тот момент, когда приложение не подключено к сети, а затем передано получателю при первом же подключении. Очереди сообщений — очень устойчивое средство. Сообщения могут храниться в постоянной памяти (например, на жестком диске), и работа очереди в этом случае может быть без потерь восстановлена после каких-либо сбоев. С базовой структурой системы очередей сообщений можно познакомиться на рис. 21.1. Приложение- отправитель Очередь 1 1 1 1 1 Приложение- получатель Рис. 21.1. Базовая структура системы очередей сообщений Очереди сообщений и удаленный вызов процедур (RPC) представляют собой высокоуровневые модели для связи между приложениями, каждая со своей областью применения. DCOM представляет собой объектно-ориентированную модель, построенную на RPC. Типичное использование удаленного вызова процедур — синхронные операции, когда дальнейшая работа клиента зависит от того, какие результаты будут получены от сервера. Если отправляющее и принимающее приложения могут работать в разное время, следует использовать очереди сообщений. Кроме всего прочего, при отправке сообщения в очередь оно может быть прочитано многими различными серверами, в то время как при использовании RPC клиент, как правило, обращается к одному серверу. Очереди сообщений обеспечивают простую модель, реализованную во многих системах. Имеется целая категория продуктов, называемых "message-oriented middleware" (MOM) и разработанных специально для работы в разнотипных системах. Microsoft Message Queue Microsoft Message Queue (MSMQ) представляет собой реализацию очередей сообщений Microsoft на Windows-платформах. MSMQ имеет три основных компонента: ■ API для приложений, отправляющих и принимающих сообщения; ■ сообщения, созданные приложением и отправленные другому приложению; ■ очереди, в которые посылаются и из которых получаются сообщения. Очереди управляются менеджером очередей.
Приложения MSMQ Имеется три основных типа приложений MSMQ. Наиболее полное из них — MSMQ Server, которое содержит очереди и в котором менеджер очередей, поддерживает API, маршрутизирующее программное обеспечение и т.п. MSMQ Server работает только на платформах Windows 2000 (или NT) Server или более высоких. В любой системе MSMQ должен быть, по крайней мере, один сервер MSMQ. Независимый клиент MSMQ включает поддержку очередей и API. Сообщения могут быть поставлены в очередь независимо от того, подключен ли клиент к сети, — они будут отосланы серверу при первой же возможности. Зависимый клиент MSMQ включает только поддержку API и для выполнения любых операций с очередями должен быть подключен к сети. Архитектура MSMQ Полная сеть MSMQ называется предприятием (enterprise), которое делится на подключенные сети, в пределах которых используется один и тот же протокол, например TCP/IP. Подключенная сеть может быть глобальной сетью. Узел (site) состоит из физически заключенных в ЛВС компьютеров. Один из серверов служит первичным контроллером предприятия (Primary Enterprise Controller — PEC) и содержит конфигурационную информацию всего предприятия. Сервер в узле является первичным контроллером узла (Primary Site Controller — PSC) и поддерживает локальную копию конфигурации. Может также быть один или несколько резервных контроллеров (Backup Site Controller, BSC); кроме того, некоторые серверы могут выступать в роли серверов маршрутизации (Routing Servers). Серверы подключений (Connector Servers) обеспечивают соединения с не Windows- системами. Так, например, FalconMQ обеспечивает подключение к ряду не Windows- систем очередей сообщений, включая IBM MQ Series. Хранение очереди сообщений Очередь сообщений хранит транзитные сообщения. Восстановимая очередь основана на хранении информации на диске и используется в системах повышенной надежности. Быстрая очередь хранит информацию в оперативной памяти и обеспечивает высокую производительность. Сообщения хранятся в формате той системы, которой они принадлежат. Для хранения информации о расположении систем требуется хранилище данных. В NT 4.0 для этого используется SQL Server, который, таким образом, становится неотъемлемой частью MSMQ; в Windows 2000 с этой целью применяются сервисы активного каталога, и SQL Server перестает быть необходимым. Указание очередей Очереди могут быть обозначены тремя различными способами указания их путей: ■ Имя_машины\Имя_Очереди; ■ Имя_машины\PRIVATE\Имя_Очереди; ■ . \Имя_Очереди, где точка используется для указания локальной машины. При открытии очереди приложением возвращается дескриптор очереди (queue handle), который используется в качестве параметра при вызове приложением других функций API. Дескриптор очереди непосредственно используется только при программировании с применением API; при работе с СОМ он инкапсулируется объектной моделью СОМ.
Форматное имя очереди представляет собой уникальное имя, генерируемое MSMQ при создании очереди. Оно содержит GUID очереди, гарантирующий глобальную уникальность имени. Получить форматное имя можно посредством различных функций API; это имя используется рядом других функций API. MSMQ API Имеется два вида MSMQ API. Первый представляет собой стандартный API на языке С. В нем вы заполняете поля различных структур и передаете их в качестве параметров вызываемым функциям. Второй API представляет собой набор объектов СОМ, которые могут использоваться различными языками программирования, поддерживающими СОМ, включая Visual Basic и Visual C++. В этой главе мы обратим особое внимание именно на этот вид API. Объектная модель MSMQ Объектная модель MSMQ подобна другим объектным моделям в СОМ — с корневым объектом и различными объектами, создаваемыми из корневого. Особенность заключается в "списке" объектов, который не является СОМ-коллекцией. ■ MSMQQuery — объект высшего уровня MSMQ. Он используется для запроса предприятия для получения очередей. ■ MSMQQueueinfos — "список" (не коллекция Visual Basic!) очередей. ■ MSMQQueuelnfo — представляет отдельный объект очереди. Вся работа выполняется со свойствами и методами этого объекта. ■ MSMQQueue — представляет открытый экземпляр очереди (одновременно очередь может быть открыта несколькими приложениями). ■ MSMQMessage — используется для отправки сообщения, а также для обращения к полученному сообщению. Имеется также ряд других объектов, таких как MSMQApplication, MSMQEvent, MSMQTransaction И пр. MSMQ и транзакции Очень важным свойством MSMQ является то, что очередь сообщений Microsoft представляет собой менеджер ресурсов, а значит, может участвовать в распределенных транзакциях. На рис. 21.2 показано приложение, выполняющее транзакцию, включающую обновление базы данных SQL Server и отправление сообщения в очередь с использованием MSMQ. Использование и программирование Microsoft Message Queue В этом разделе обсуждаются установка и тестирование MSMQ, выполнение администрирования и простейшее программирование системы MSMQ. Для использования QC вы должны установить MSMQ — по умолчанию MSMQ при инсталляции Windows 2000 не устанавливается.
SQL Server MSMQ Рис. 21.2. Транзакция, включающая работу с MSMQ и SQL Server Установка и тестирование MSMQ Для установки MSMQ на вашем основном компьютере должен быть установлен активный каталог, а компьютеру должно быть присвоено "звание" контроллера домена. После этого вы можете инсталлировать сервер MSMQ на основном компьютере. На второй машине с Windows 2000 MSMQ можно установить в качестве независимого клиента. Это означает, что вы будете иметь возможность отправлять сообщения в очередь при отключенном сервере. Однако в этой главе мы ограничимся рассмотрением работы с MSMQ на одном компьютере. После инсталляции MSMQ вы можете выполнить небольшое тестирование с помощью тестовой программы из состава Platform SDK. Инсталляция MSMQ MSMQ устанавливается на основной компьютер под управлением Windows 2000 Server. Вызовите Configure Your Server с помощью команды меню Start^Programs^Administrative Tools. Щелкните на кнопке Advanced и выберите из выпадающего списка Message Queuing. После этого щелкните на Learn More и ознакомьтесь с предлагаемой вашему вниманию документацией. В частности, вы узнаете, что списки опций при установке MSMQ как сервера и как клиента различны. Не перепутайте — мы устанавливаем сервер MSMQ на контроллере домена! При желании вы можете установить клиент MSMQ на машине под управлением Windows 2000 Professional. Тестовая программа MSMQ API В составе Platform SDK имеется полезная тестовая программа, которую вы можете найти в каталоге Samples\COM\MessageQueueing\MqApiTst, расположенном в каталоге, в котором установлен Platform SDK (обычно это Program Files\Platform SDK). Соберите эту программу, используя Visual C++, и запустите MqApiTst.exe. Теперь вы можете выполнить функции API для создания очереди с именем queue 1 и открытия ее в режиме посылки сообщений, а также послать в нее несколько сообщений. На рис. 21.3 показаны ответы программы на наши действия (обратите внимание на форматное имя, выводимое при создании очереди). Приложение
Рис. 21.3. Вызов API функций MSMQ Администрирование MSMQ Администрирование очередей производится с помощью инструмента, иногда называемого MSMQ Explorer. Подобно многим другим административным инструментам Windows 2000, он представляет собой элемент Microsoft Management Console (MMC). Вызвать MSMQ Explorer можно с помощью команды меню Starts Programs^ Administrative Tools«=>Computer Management. Затем откройте в левой панели узел Server Applications and Services, а в нем — Message Queuing, как показано на рис. 21.4. Рис. 21.4. Администрирование очередей с помощью инструмента Computer Management Вы можете открыть общедоступные очереди, найти среди них созданную тестовой программой очередь queue 1 и просмотреть сообщения в этой очереди. Теперь вы можете воспользоваться тестовой программой для получения сообщений из очереди, а затем убедиться, что очередь стала пуста (возможно, при этом вам придется воспользоваться командой Refresh для отображения последних изменений в состоянии очереди).
Демонстрационные программы Для демонстрации программирования MSMQ в число примеров к данной книге включен набор из четырех программ: ■ QueueCreate, создающая очередь; ■ QSendObj — компонент, посылающий сообщение в очередь; ■ QueueSend, предоставляющая графический интерфейс пользователя, позволяющий определить очередь и послать в нее сообщение с применением компонента QSendObj; ■ QueueReceive, предоставляющая графический интерфейс пользователя, позволяющий пользователю получить сообщение из очереди. На рис. 21.5 показана связь между указанными программами и очередью. QueueSend QueueReceive QSendObj Очередь QueueCreate Рис. 21.5. Работа демонстрационных программ с очередью Программы написаны на языке Visual Basic и используют объектную модель, описанную в предыдущем разделе. QueueCreate Программа QueueCreate (каталог Chap21\QueueCreate) используется для создания очереди. Полное имя очереди получается из имени локальной машины и предлагаемого по умолчанию имени очереди queue2 (такого же, как и предлагаемое по умолчанию имя метки очереди). Пользователь может изменить эти имена (рис. 21.6). Рис. 21.6. Графический интерфейс пользователя для создания очереди
Вот исходный код программы: Private Declare Function GetComputerNameAPI _ Lib "kernel32" Alias "GetComputerNameA" _ (ByVal lpBuffer As String, nSize As Long) As Long Function GetComputerName() As String 1 Set or retrieve the name of the computer. Dim strBuffer As String Dim IngLen As Long strBuffer = Space (255 + 1) IngLen = Len(strBuffer) If CBool(GetComputerNameAPI(strBuffer, IngLen)) Then GetComputerName = Left$(strBuffer, IngLen) Else GetComputerName = "" End If End Function Private Sub cmdCreate_Click() If txtLabel = "" Then Dim pos As Integer pos = InStr(l, txtPathName, "\") + 1 txtLabel = Mid(txtPathName, pos) End If Dim objQueuelnfo As New MSMQQueuelnfо objQueuelnfo.PathName = txtPathName objQueuelnfo.Label = txtLabel On Error GoTo ErrorHandler objQueueInfo.Create Exit Sub ErrorHandler: MsgBox Err.Description End Sub Private Sub Form_Load() txtPathName = GetComputerName & "\" & "queue2" End Sub Для создания очереди вначале следует создать объект MSMQQueuelnf о и установить его свойство PathName (кроме того, могут быть определены и другие свойства — так, в нашей программе мы определяем также свойство Label). После этого надо вызвать метод Create, необязательный параметр (типа Boolean) которого может использоваться для определения того, будет ли очередь транзакционной (транзакционная оч е- редь участвует в транзакциях DTC). Программа использует Microsoft Message Queue 2.0 Object Library, как видно из рис. 21.7. Запустите программу для создания очереди queue2. С помощью MSMQ Explorer убедитесь в том, что очередь создана (при необходимости воспользуйтесь командой Refresh). QSendObj СОМ-компонент QSendObj (каталог Chap21\QSendObj) имеет единственный метод Send, используемый для отправления сообщения в очередь. Параметрами метода являются объект MSMQQueue, определяющий очередь, и строки, содержащие метку
очереди и тело сообщения. Сообщение генерируется путем создания экземпляра объекта MSMQMessage и определения его свойств body и label. Отправка сообщения происходит посредством вызова метода Send объекта MSMQMessage с передачей очереди в качестве параметра. Рис. 21.7. Программа использует ссылку на объектную библиотеку MSMQ Public Sub Send(ByVal queue As MSMQQueue, _ ByVal label As String, _ ByVal body As String) Dim message As New MSMQMessage On Error GoTo ErrorHandler message.label = label message.body = body message.Send queue Exit Sub ErrorHandler: MsgBox Err.Description End Sub Зарегистрируйте компонент, либо построив проект, либо запустив файл reg_QSendObj.bat. QueueSend Программа QueueSend (каталог Chap21\QueueSend) используется для отправки сообщений. Очередь определяется ее меткой, и, кроме того, вы можете ввести строки, содержащие метку сообщения и само сообщение. Вы можете послать несколько сообщений, вводя новую информацию и щелкая на кнопке Send (рис. 21.8). Вот соответствующий код Visual Basic: Dim queue As MSMQQueue Private Sub cmdClose_Click() queue.Close cmdOpen.Enabled = True cmdClose.Enabled = False cmdSend.Enabled = False End Sub
Private Sub cmdOpen_Click() Dim query As New MSMQQuery Dim qinfos As MSMQQueuelnfos Dim qinfo As MSMQQueuelnfо On Error GoTo ErrorHandler 'Locate queue with given label Set qinfos = query.LookupQueue(Label:=txtQueueLabel) Set qinfo = qinfos.Next If qinfo Is Nothing Then MsgBox "Queue not found" Exit Sub End If Set queue = qinfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) cmdOpen.Enabled = False cmdClose.Enabled = True cmdSend.Enabled = True Exit Sub ErrorHandler: MsgBox Err.Description End Sub Private Sub cmdSend_Click() Dim objSend As New Sender objSend.Send queue, txtMsgLabel, txtMsgBody On Error GoTo ErrorHandler Exit Sub ErrorHandler: MsgBox Err.Description End Sub Рис. 21.8. Графический интерфейс пользователя для отправки сообщений Главная часть кода — обработчик кнопки Open, иллюстрирующий работу с объектной моделью MSMQ. Вначале создается объект MSMQQuery, являющийся корнем иерархии объектов. Этот объект используется для создания объекта MSMQQueuelnfos,
который содержит список очередей. Затем (в нашем случае — по метке) производится поиск интересующей нас очереди. Найденная очередь присваивается объекту MSMQQueuelnfo, который затем используется для открытия очереди, получая объект MSMQQueue. Сообщение посылается с помощью вызова компонента QSendObj. Воспользуйтесь этой программой для отправки нескольких сообщений в очередь и убедитесь, что все прошло благополучно, с помощью MSMQ Explorer. QueueReceive Программа QueueReceive (каталог Chap21\QueueReceive) используется для получения сообщений. Как и ранее, очередь ищется по ее метке, Если очередь найдена, то она открывается и вы можете получать из нее сообщения. На рис. 21.9 показано первое сообщение, полученное из очереди. Рис. 21.9. Графический интерфейс пользователя для полунения сообщений из очереди Вот соответствующий код Visual Basic: Dim queue As MSMQQueue Private Sub cmdClear__Click () txtMsgLabel = "" txtMsgBody = "" End Sub Private Sub cmdClose_Click() queue.Close cmdOpen.Enabled = True cmdClose.Enabled = False cmdReceive.Enabled = False End Sub Private Sub cmdOpen_Click() Dim query As New MSMQQuery Dim qinfos As MSMQQueuelnfos Dim qinfo As MSMQQueuelnfo On Error GoTo ErrorHandler
'Locate queue with given label Set qinfos = query.LookupQueue(Label:=txtQueueLabel) Set qinfo = qinfos.Next If qinfo Is Nothing Then MsgBox "Queue not found" Exit Sub End If Set queue = qinfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) cmdOpen.Enabled = False cmdClose.Enabled = True cmdReceive.Enabled = True Exit Sub ErrorHandler: MsgBox Err.Description End Sub Private Sub cmdReceive_Click() Dim message As MSMQMessage On Error GoTo ErrorHandler Set message = queue.Receive(ReceiveTimeout:=1000) If Not message Is Nothing Then txtMsgLabel = message.Label txtMsgBody = message.Body Else txtMsgLabel = "*** TIMEOUT ***" txtMsgBody = "" End If Exit Sub ErrorHandler: MsgBox Err.Description End Sub Код для открытия очереди сходен с соответствующим кодом отсылающей программы, основная часть которого — поиск очереди по ее метке. При открытии очереди в этой программе вы указываете, что она открывается для получения сообщений. Для того чтобы получить сообщение, создается объект MSMQMessage и вызывается метод Receive, указывающий очередь. Если сообщение не было получено за определенное время, выводится сообщение о тайм-ауте. Полученное сообщение можно прочесть, воспользовавшись соответствующим свойством объекта. Эта программа позволяет получить сообщения, посланные программой QueueSend. Компоненты, работающие с очередями MSMQ предоставляет очень полезную функциональность, обеспечивающую асинхронное сообщение между клиентом и сервером. Программная модель ясна и понятна, но даже для простейшего случая, как вы видели, вам придется написать немалое количество кода. Исходя из общей идеи — упростить все, что можно, и уж тем более то, что нельзя, СОМ+ предоставляет сервис, называемый Queued Components (QC, компоненты, способные работать с очередями), который автоматизирует работу по использованию MSMQ. Используя QC, вы можете вызвать компонент обычным образом, но теперь передача информации произойдет посредством MSMQ, а не RPC. В этом разделе мы вначале рассмотрим общую архитектуру QC, а затем перейдем к деталям конфигурирования и программирования QC.
Архитектура QC СОМ+ обрабатывает вызов метода QC, используя специальный тип прокси, называемый самописцем (Recorder), который экспортирует тот же интерфейс, что и целевой компонент. Однако, в отличие от прокси RPC, в качестве транспортного механизма самописец использует MSMQ. Он получает вызов метода, "маршализует" его параметры в сообщение MSMQ и пересылает сообщение в очередь, связанную с компонентом. На стороне получателя слушатель (Listener) получает сообщение из очереди и передает его проигрывателю (Player). Тот "демаршализует" параметры из сообщения и вызывает целевой компонент (рис. 21.10). Клиент Очередь Самописец Слушатель Проигрыватель Сервер Рис. 21.10. Архитектура компонентов, работающих с очередями Использование QC Использовать QC очень просто. Любой компонент СОМ, удовлетворяющий некоторым требованиям, может быть доступен посредством либо очереди, либо обычного DCOM. Компонент не должен знать, каким образом произошел вызов. Компонент должен быть инсталлирован в приложение СОМ+ и сконфигурирован для работы с очередями в системном Каталоге и с соответствующим образом настроенной системой безопасности. Клиент использует специальный механизм "имени очереди" (queue moniker) для получения начального указателя на интерфейс. И, наконец, сервер активизируется несколько не так, как в случае обычного COM/DCOM. В этом разделе мы рассмотрим основную часть вопросов, оставив вопросы, касающиеся конфигурирования, отдельному разделу.
Требования к QC QC должны удовлетворять обычным требованиям, предъявляемым к компонентам СОМ+, таким как саморегистрация и наличие библиотеки типов. Кроме того, QC должны иметь только входящие параметры во всех своих методах и не должны возвращать результат работы. Это требование вытекает из асинхронной природы QC. Результат работы метода должен помещаться в очередь, поскольку все вызовы "односторонние" и вернуть результат вызывавшей программе невозможно, как невозможно и обратиться к серверу непосредственно. Для того чтобы отправить ответ клиенту, сервер должен осуществить его односторонний вызов точно таким же способом, каким перед этим был вызван сам. Конфигурирование QC Приложение СОМ+, содержащее QC, должно быть сконфигурировано как работающее с очередями (Queued). Это еще не означает, что каждый вызов метода будет производиться через MSMQ. Кроме того, каждый поддерживающий работу с очередями интерфейс также должен быть помечен соответствующим образом. Такая настройка может быть выполнена с помощью инструмента Component Services либо административного API. Интерфейс может быть помечен как поддерживающий работу с очередями и при использовании IDL: [ odl, uuid(3C4B4956-2077-6974-686F-757420436F3E), version(1.0), hidden, dual, nonextensible, oleautomation, QUEUEABLE ] interface _Name : IDispatch { [id(0x60030000)] HRESULT Add([in] BSTR str); }; Безопасность Для каждого пользователя требуется внутренний сертификат безопасности MSMQ. Это цифровая подпись, которую MSMQ выдает аутентифицированным пользователям. Вы можете получить внутренний сертификат MSMQ с помощью аплета MSMQ Control Panel. Выберите вкладку Security и щелкните на Renew Internal Certificate. Подробнее об этом мы поговорим, когда приступим к рассмотрению нашего демонстрационного приложения. Имя очереди Для получения указателя на интерфейс компонента, работающего с очередями, программа-клиент не использует механизм вызова функции CoCreatelnstaceEx. Вместо этого она вызывает CoGetObject, передавая ей "имя очереди" (queue moniker10). Программа на Visual Basic также вместо использования оператора New вызывает функцию Get Object. *0 Moniker (амер сленг) — имя, кличка — Прим. перев.
Имя (moniker) представляет собой специальный вид СОМ-объекта, весь смысл существования которого сводится к получению указателя на интерфейс другого СОМ-объекта. Имена были введены для поддержки связей в OLE (связь в OLE на самом деле представляет собой имя объекта). Технология OLE 1.0 не использовала имена, а для идентификации объекта применяла полный путь к нему. В OLE 2.0 введены имена объектов, которые обеспечивают большую гибкость при работе. Теперь связанный объект — не обязательно файл. Например, связанным именованным объектом может оказаться какой-то диапазон ячеек электронной таблицы. Такое имя можно назвать "интеллектуальным именем", поскольку оно знает, как найти объект, на который ссылается. Когда мы "связываем" имя и объект, получаем указатель на интерфейс объекта с этим именем. После этого имя объекта уходит со сцены и мы работаем с объектом через указатель. После первого применения имен в OLE они стали широко использоваться в СОМ для различных целей. Примером может служить имя URL для ссылки на объект в Internet. Впрочем, вам вряд ли придется сталкиваться с именами непосредственно. QC вводят два новых типа имен. Первый называется "новым"11 именем и используется для получения указателя на интерфейс по данному идентификатору класса или программы. Вот как код Visual Basic получает ссылку на объект с применением функции GetObject и "нового" имени: Dim objName As Name Set objName = GetObject("newiqcServer.Name") Имена могут быть составными. Вы получаете указатель на интерфейс компонента, работающего с очередями, комбинируя имя очереди и новое имени: Dim objName As Name Set objName = GetObject("queue:/new:qcServer.Name") Полученная ссылка на объект указывает на самописец СОМ+, а не на сам целевой объект. Самописец получает вызов метода и помещает соответствующее сообщение в очередь, связанную с компонентом. Обратите внимание на то, что СОМ+ обрабатывает QC несколько иначе, чем обычные компоненты. В этом случае все еще остается применимой декларативная модель с соответствующим атрибутом (Queued), но вместо того чтобы выполнять всю работу через перехватчик, клиент делает выбор между обычным синхронным RPC- вызовом компонента и асинхронным вызовом с использованием MSMQ. В последнем случае для получения указателя на интерфейс клиент использует имя очереди. После того как указатель на интерфейс получен, все последующие вызовы методов компонента ничем не будут отличаться от вызовов в обычном, синхронном случае. Запуск приложения, работающего с очередями QC отличаются от стандартных компонентов и тем, каким образом они начинают работать. В СОМ и DCOM Service Control Manager запускает компоненты, необходимые для удовлетворения вызова метода, что происходит синхронно с вызовом. В случае QC сервер не должен запускаться синхронно. Более того, сервер может не быть запущен еще некоторое время после того, как клиент поместит сообщение в очередь. Таким образом, вызов метода не приводит к активизации сервера. Что же должно произойти, чтобы слушатель оказался запущенным? Прежде всего, он должен быть настроен для работы с Каталогом. Во-вторых, должно быть запущено приложение, которое может запустить слушатель с помощью либо инструмента Component Services, либо посредством административного API. В качестве альтернативного /; В данном случае понятия "нового имени " и "имени очереди " не столько смысловые, сколько указывают на использующийся при передаче имени префикс new: или queue.. — Прим. перев.
пути компонент может быть запущен обычным вызовом, не работающим с очередью. После того как компонент приступит к работе, слушатель приступит к выборке сообщений из очереди (в том порядке, в котором они были помещены в очередь). Программный пример Небольшую иллюстрацию работы QC можно найти в каталогах Chap21\qcServer и Chap21\qcClient, в которых содержится сервер и клиент, работающие с QC. Программа просто добавляет имя в список имен. Вместо использования базы данных (для простоты) сервер просто заносит имена в журнальный файл, используя компонента Logger из главы 12, "Обработка ошибок и отладка". Вот весь код сервера: Public Sub Add(ByVal str As String) Dim objLog As New Log objLog.Write str End Sub Идентификатор программы сервера — qcServer.Name. Программа-клиент обеспечивает графический интерфейс пользователя для ввода имени и три кнопки. Кнопка Add выполняет обычное создание объекта и синхронный вызов метода. Кнопка Add via New получает ссылку на объект с помощью вызова функции Getobject и нового имени. В этом случае также происходит синхронный вызов метода. Кнопка Add via Server Queue получает ссылку на объект с помощью вызова функции Getobject и имени очереди. В этом случае вызов производится через MSMQ и сообщения будут находиться в очереди, пока сервер не будет активизирован. Графический интерфейс пользователя программы-клиента приведен на рис. 21.11. Рис. 21.11. Графический интерфейс пользователя программы для иллюстрации синхронных и асинхронных вызовов Вот соответствующий код Visual Basic: Private Sub cmdAdd_Click() Dim objName As New Name obj Name.Add txtName End Sub Private Sub cmdAddNew_Click() Dim objName As Name Set objName = Getobject("new:qcServer.Name") objName.Add txtName End Sub Private Sub cmdAddQueue_Click() Dim objName As Name Set objName = Getobject("queue:/new:qcServer.Name") obj Name.Add txtName End Sub
Конфигурирование компонента, работающего с очередями Перед тем как работать дальше, убедитесь, что компонент Logger из главы 12, "Обработка ошибок и отладка" зарегистрирован с помощью запуска файла reg_logger.bat. Для запуска демонстрационного примера (в качестве QC) вам сначала надо создать новое приложение СОМ+ qcServer. Инсталлируйте qcServer.dll в ваше новое приложение. Теперь вы должны иметь возможность запускать программу-клиент для добавления имен с помощью кнопок Add и Add via New. Проверьте это, просмотрев содержимое журнального файла с: \logf ile. txt. После того как вы выполните эту часть работы, убедитесь, что сервер не запущен. Для этого вызовите Task Manager и закройте все процессы dllhost.exe, какие сможете. Найти dllhost.exe легко — надо дважды щелкнуть на заголовке Image Name, при этом список процессов станет отсортирован по именам (рис. 21.12). Рис. 21.12. Для завершения работы процесса можно использовать Task Manager Конфигурирование приложения как работающего с очередями Первый специфичный для QC шаг настройки состоит в том, чтобы указать приложение, как работающее с очередями, для чего в окне Component Services щелкните правой кнопкой мыши на qcServer и выберите в контекстном меню Properties. Затем в диалоговом окне Properties выберите вкладку Queuing и отметьте опции Queued и Listen, как показано на рис. 21.13. После этого СОМ+ создаст общедоступную очередь под именем qcserver, как можно убедиться с помощью MSMQ Explorer. Кроме того, будет создан ряд закрытых очередей.
Конфигурирование интерфейса как работающего с очередями Теперь вы должны настроить интерфейс как работающий с очередями (так как мы работали с Visual Basic, у нас нет возможности указать это в IDL-файле. При работе с Visual C++ можно просто указать в IDL опцию queueable, автоматизировав выполнение описываемого в этом разделе шага). Для этого в окне Component Services откройте компонент qcServer.Name, для того чтобы увидеть его интерфейсы, и щелкните правой кнопкой мыши на Name. Выберите в контекстном меню команду Properties, в диалоговом окне — вкладку Queuing и отметьте опцию Queued, как показано на рис. 21.14. Рис. 21.13. Конфигурирование приложения Рис. 21.14. Определение интерфейса как СОМ+ как работающего с очередями работающего с очередями Теперь, если вы запустите программу-клиент и щелкните на кнопке Add via Server Queue, то не получите сообщения об ошибке, но и сообщение в очередь не добавится. Получение сертификата безопасности MSMQ Следующий шаг состоит в получении внутреннего сертификата безопасности Microsoft Message Queue для каждой учетной записи, которая должна иметь возможность работать с QC. Если вы выполняете всю работу как администратор, вам достаточно сделать это один раз. Простейший путь получения сертификата состоит в вызове ап- лета Message Queuing из Control Panel. Выберите в нем вкладку Security и щелкните на кнопке Renew Internal Security Certificate (рис. 21.15). Добавление запроса в очередь Теперь вы можете запустить программу-клиент и убедиться, что она корректно работает, отправив запросы в очередь qcserver. Добавьте три имени с помощью кнопки Add via Server Queue. HE ИСПОЛЬЗУЙТЕ ПРИ ЭТОМ ДРУГИЕ КНОПКИ! Убедитесь, что в очереди находятся три сообщения (рис. 21.16).
Рис. 21.15. Получение внутреннего сертификата безопасности Microsoft Message Queue Рис. 21.16. Вызов метода QC размещает сообщения в очереди Запуск приложения, работающего с очередью Теперь запустим сервер. Это можно сделать различными путями — например, выбрав его на панели Component Services, щелкнув на нем правой кнопкой мыши и выбрав в меню команду Start. Завершить работу приложения можно командой Shutdown того же меню. Запуск происходит немедленно, а вот выключение может занять некоторое время. После того как приложение запущено, очередь должна быть немедленно обработана и отправленные имена должны появиться в файле с: \logf ile. txt.
При первом запуске приложения вы можете "увидеть" побочные эффекты — например, имена будут записаны в файл с небольшой задержкой или не в том порядке, который ожидался. Использование административных объектов Вы можете управлять запуском и остановкой приложения с помощью простого кода. Все, что для этого надо — так это объект COMAdminCatalog и единственный вызов метода. В каталоге Chap21\appStarter вы найдете программу, демонстрирующую программный запуск и останов приложения (рис. 21.17). Рис. 21.17. Графический интерфейс пользователя административной программы для запуска и останова приложения СОМ+ Вот код этой программы: Private Sub txtShutdown_Click() Dim cat Set cat = CreateObject("COMAdmin.COMAdminCatalog") cat.ShutdownApplication txtApp End Sub Private Sub txtStart_Click() Dim cat Set cat = CreateObject("COMAdmin.COMAdminCatalog") cat.StartApplication txtApp End Sub Резюме Очереди сообщений обеспечивают альтернативу RPC для вызова сервисов сервера. В то время как RPC — синхронный метод вызова, очереди сообщений представляют собой асинхронный метод. В распределенной среде очереди сообщений имеют массу достоинств, включая повышенную устойчивость. Полагаться на абсолютную устойчивость сети нельзя, в особенности глобальной сети, и очереди сообщений с надежными методами их хранения позволяют поднять отказоустойчивость распределенных приложений. Реализация очередей сообщений Microsoft называется Microsoft Message Queue и программируется с использованием объектной модели СОМ. Компоненты, работающие с очередями, представляют собой сервис СОМ+, который позволяет автоматизировать использование MSMQ. Мы рассмотрели две альтернативы протоколу COM/DCOM — HTTP и MSMQ. Теперь пришла очередь познакомится с моделью событий СОМ+, упрощающей отправление уведомлений о событиях от сервера клиенту. События СОМ+ и являются темой следующей главы.
Глава 22 События СОМ+ События представляют собой часть стандартной СОМ и являются одной из ключевых концепций в модели программирования Windows. С помощью событий программа может получить уведомление о происшествиях вне самой программы. Графический интерфейс пользователя должен быть способен работать со множеством событий, связанных с действиями пользователя, такими как щелчок мышью, выбор пункта меню, выбор элемента из списка и т.п. Программа реализует обработчики событий, выполняющие определенные действия при их наступлении. Мы уже встречались с событиями в Visual Basic в главе 11, "Автоматизация и программирование СОМ на Visual Basic ". Такая же концепция событий имеется и в распределенных приложениях. Существует множество самых разных программ, для корректной работы которых необходимо получение уведомлений о том, что происходит вне этих программ. Для реализации системы событий в случае распределенной среды могут использоваться события СОМ, но это — не лучшее решение проблемы, поскольку они требуют "жесткой связи" между отправителем и получателем уведомления о событии. СОМ+ обеспечивает свободно связанную (loosely coupled) систему событий, основанную на представлении об издателе (publisher) и подписчике (subscriber). В этой главе вначале мы познакомимся с классической системой событий СОМ, а затем рассмотрим модель событий СОМ+ с издателями и подписчиками и разберем работу демонстрационного приложения. События и точки подключения в СОМ В этом разделе мы рассмотрим стандартную систему событий СОМ, обеспечивающую основу для нашего последующего рассмотрения модели событий СОМ+. Для того чтобы сделать наш рассказ более конкретным, на протяжении главы мы будем работать с одной демонстрационной программой, иллюстрирующей обе технологии. Пример события Наш пример представляет собой прототип структуры события для Electronic Commerce Game™. Мы хотим обеспечить участников возможностью уведомления о вступлении в игру нового игрока и о выходе из нее старых. Это похоже на "чат" (chat) в сети, где вас уведомляют о подключении нового участника или о покинувшем систему.
Для запуска демонстрационной программы зарегистрируйте серверный компонент, расположенный в каталоге Chap22\VbEvent, запустив reg_VbEvent.bat, после чего вы можете запускать программу-клиент из каталога Chap22\VbEventClient, графический интерфейс пользователя которой показан на рис. 22.1. Когда вы щелкаете на кнопке Start Server, создается экземпляр серверного компонента Player, который позволяет послать уведомление клиенту (рис. 22.2). Вы можете ввести имя игрока и щелкнуть либо на кнопке Join, либо на кнопке Quit. При этом в окне клиентского приложения должно появиться сообщение о том, что некоторый участник подключился к игре или вышел из нее. Когда вы завершите работу клиентской программы, щелчок на кнопке Stop Server приведет к уничтожению объекта сервера. Рис. 22.1. Графический интерфейс пользователя демонстрационной программы Рис. 22.2. Графический интерфейс пользователя серверного компонента демонстрационной программы Сервер Серверным компонентом является компонент vbEvent, предоставляющий класс Player, который реализует события Join и Quit. При инициализации класса (создании экземпляра объекта) загружается форма, которая обеспечивает пользовательский интерфейс для запуска события. В Visual Basic событие запускается с помощью оператора RaiseEvent, который должен быть вызван из класса. Для того чтобы события вызывались из формы, класс предоставляет две вспомогательные функции — FireJoin и FireQuit. Вот как в результате выглядит код класса Player: Public Event Join(ByVal name As String) Public Event Quit(ByVal name As String) Private Sub Class_Initialize() Load Forml Set Forml.objEvent = Me Forml.Visible = True End Sub Public Sub FireJoin(ByVal name As String) RaiseEvent Join(name) End Sub Public Sub FireQuit(ByVal name As String) RaiseEvent Quit(name) End Sub Public Sub StopServerO Set Forml.objEvent = Nothing Unload Forml End Sub
Далее приведен простой код формы, который обрабатывает щелчки на кнопках с помощью вызова вспомогательных функций. Public objEvent As Player Private Sub cmdJoin_Click() objEvent.FireJoin txtName End Sub Private Sub cmdQuit_Click() objEvent.FireQuit txtName End Sub Клиент Программа-клиент объявляет объект Player с описателем WithEvents. В ответ на щелчок на кнопке Start Server создается экземпляр объекта Player. Событиям Join и Quit сопоставлены обработчики, которые в нашем случае просто выводят соответствующие сообщения. Вот исходный код клиента: Dim WithEvents objPlayer As Player Attribute objPlayer.VB_VarHelpID = -1 Private Sub cmdClear_Click() txtMessage = "" End Sub Private Sub cmdStartServer_Click() Set objPlayer = New Player End Sub Private Sub cmdStopServer_Click() objPlayer.StopServer Set objPlayer = Nothing End Sub Private Sub objPlayer_Join(ByVal name As String) txtMessage = name & " has joined the game" End Sub Private Sub objPlayer_Quit(ByVal name As String) txtMessage = name & " has quit the game" End Sub Архитектура точек подключения "За сценой" Visual Basic реализует события с помощью точек подключения (connection points). Точки подключения представляют собой механизм общего назначения СОМ, используемый подключаемыми (connectable) объектами для уведомления их клиентов о происходящих событиях. Для отправления таких уведомлений объект определяет "исходящие" интерфейсы (определяемые в библиотеке типов). Хотя интерфейс определяется сервером, реализуется он клиентом. Объект, отправляющий уведомление, называется источником, а получающий уведомление — стоком. На рис. 22.3 показан подключаемый объект с одним обычным, "входящим" интерфейсом и одним исходящим, а также клиент с его стоком.
Клиент Сток Подключаемый объект Рис. 22.3. Подключаемый объект определяет исходящий интерфейс, реализуемый стоковым объектом клиента Входящие и исходящие интерфейсы Обычный объект СОМ поддерживает входящие интерфейсы, а подключаемый объект, кроме того, определяет один или несколько исходящих интерфейсов. Объект определяет интерфейс, а клиент реализует его. Объект вызывает клиент посредством исходящего интерфейса. Исходящий интерфейс иногда называется интерфейсом-источником, поскольку объект сам по себе является источником вызова метода через интерфейс, т.е. объект выполняет вызов метода. Исходящие интерфейсы создаются с использованием атрибута source в IDL. Ниже приведена часть класса IDL VbEvent. Player. Исходящий интерфейс всегда является диспетчерским интерфейсом. coclass Player { [default] interface _Player; [default, source] dispinterface Player; }; [ uuid(3C4 967 6F-7220-4B72-617 3-73696B6F7 63E), version(1.0), hidden, nonextensible ] dispinterface Player { properties: methods: [id(OxOOOOOOOl)] void Join([in] BSTR name); [id(0x00000002)] void Quit([in] BSTR name); }; Клиент—Объект—Сток Во взаимодействии, поддерживаемом подключаемыми объектами, участвуют три компонента. ■ Объект, представляющий собой объект СОМ, поддерживающий один или несколько обычных входящих интерфейсов.
■ Клиент, осуществляющий вызовы входящих интерфейсов, поддерживаемых Объектом. ■ Сток, являющийся объектом, ассоциированным с Клиентом, который реализует вызываемые Объектом интерфейсы. Клиент отвечает за "подключение" Стока к Объекту. Клиент создает экземпляр объекта Стока, получая при этом указатель на интерфейс. Клиент передает этот указатель на интерфейс Объекту, вызывая метод одного из входящих интерфейсов. Затем Объект использует полученный указатель для осуществления вызовов стока. I ConnectionPoint Подключаемый объект имеет объект точки подключения для каждого из своих исходящих интерфейсов. Точка подключения поддерживает интерфейс iConnectionPoint. Этот интерфейс используется клиентом для передачи указателя на интерфейс стока подключаемому объекту. Заметьте, что интерфейс IConnectionPoint поддерживается не подключаемым объектом, а точкой подключения (что отображено на рис. 22.4, который мы рассмотрим немного позже). Методы, предоставляемые интерфейсом IConnectionPoint, следующие: ■ Advise, который используется для установления подключения. Он имеет входной параметр — указатель на интерфейс стока — и выходной — "ключ" (cookie), который может быть сохранен клиентом и использован позже при разрыве соединения; ■ Unadvise, использующийся для прекращения соединения, описанного ключом, который был возвращен вызовом Advise; ■ EnumConnections, который возвращает счетчик, используемый для перечисления всех открытых с помощью метода Advise соединений. IConnectionPointContainer Посредством интерфейса IConnectionPointContainer клиент может получить информацию об исходящих интерфейсах подключаемых объектов. Интерфейс IConnectionPointContainer предоставляется самим подключаемым объектом и имеет два метода. ■ FindConnectionPoint, используемый для получения указателя на интерфейс IConnectionPoint по данному IID; ■ EnumConnectionPoints возвращает указатель на интерфейс счетчика, который может использоваться для перечисления всех точек подключения, поддерживаемых объектом. Архитектура точек подключения Теперь рассмотрим архитектуру точек подключения в целом. Клиент читает библиотеку типов Объекта, отмечая исходящие интерфейсы, определенные в ней. Затем он может создать все необходимые объекты Стока для получения вызовов методов, когда объект генерирует событие. Объект поддерживает интерфейс IConnectionPointContainer и содержит одну или несколько точек подключения, каждая из которых, в свою очередь, поддерживает интерфейс IConnectionPoint. Клиент использует интерфейс IConnectionPointContainer для получения указателя на интерфейс IConnectionPoint точки подключения. Затем клиент может вызвать
метод Advise точки подключения для передачи указателя на интерфейс его объекта Стока, а точка подключения — генерировать события, вызывая методы интерфейса Стока (эта архитектура проиллюстрирована на рис. 22.4). Клиент Сток Объект Точка подключения Рис. 22.4. Объект поддерживает интерфейс IConnectionPointContainer, а точка подключения — интерфейс IConnectionPoint. Точка подключения вызывает Сток Жестко связанные события Предшественник этой архитектуры событий был разработан для технологии OLE 1.0, которая предоставила сток в контейнере составного документа OLE для получения уведомлений об изменениях от внедренного объекта. Архитектура точки подключения разработана как более общий механизм, применимый к управляющим элементам OLE (теперь — управляющие элементы ActiveX). Вид модели событий, использованной в обоих случаях, иногда называют "жестко связанными событиями" (tightly coupled events, TCE). Клиент, ожидающий уведомления, знает, от какого Объекта оно придет, и участники передачи уведомления заранее согласовывают используемые интерфейсы. Этот вид модели событий подходит как для составных документов, так и для управляющих элементов ActiveX, когда на самом деле имеется тесная связь между контейнером и внедренным объектом или управляющим элементом. Но это не самая подходящая модель для приложений уровня предприятия, когда механизм свободного связывания обеспечивает гораздо большую гибкость. Свободно связанные события и модель издатель/подписчик в СОМ+ СОМ+ предоставляет новую, общую модель системы событий, называемую "свободно связанные события" (loosely coupled events — LCE) и разработанную для удовлетворения потребностей распределенных вычислений. Общая проблема решается
своевременной передачей информации заинтересованной стороне без предварительного уведомления о том, что собой эта сторона представляет, и, безусловно, без реализации тесно связанных программных интерфейсов между ними. Для описания программной модели таких систем введены новые термины, поскольку термины "клиент" и "сервер" не точно отражают происходящее. Клиент вызывает сервер, но при наступлении события сервер вызывает клиент. Альтернативная терминология включает термины "издатель" и "подписчик". Издатель предоставляет информацию, а подписчик ее потребляет. Как только новая информация предоставляется издателем, нам надо, чтобы подписчик мог получить ее без постоянного опроса издателя. В СОМ+ издатель и подписчик свободно связаны. Информация события от различных издателей хранится в каталоге СОМ+, а подписчики указывают, какую информацию они намерены получать, регистрируясь в каталоге. Кроме того, они могут фильтровать получаемую информацию, с тем чтобы получать только уведомления о событиях, отобранных по определенным событиям. Такая регистрация известна как "подписка" и существует независимо для издателя и подписчика. На рис. 22.5 показана модель издателя/подписчика СОМ+ с системой событий между издателем и подписчиком. Издатель Подписчик Система событий Подписчик Издатель Подписчик Рис. 22.5. Модель издателя/подписчика СОМ+ В этом разделе описывается архитектура системы событий СОМ+ в целом, а в следующем разделе будет представлен программный пример, иллюстрирующий использование инструмента Component Services для занесения информации подписчика в каталог СОМ+. Архитектура системы событий СОМ+ Как и все в СОМ+, система событий построена таким образом, что основная часть инфраструктуры обеспечивается СОМ+ автоматически, с минимальной работой программиста, и административно декларируемой функциональностью. Вы должны реализовать компонент EventClass и определить подписку. EventClass содержит интерфейсы, чьи методы вызываются издателем для запуска события. Система времени выполнения СОМ+ синтезирует объект события, который реализует интерфейсы EventClass. Подписчики вызываются объектом события, который определяет, какие подписчики должны быть вызваны, по подписке. Как EventClass, так и подписка настраиваются административно — либо с использованием инструмента Component Services, либо посредством административного API. Эта архитектура показана на рис. 22.6.
Подписчик Издатель Система событий Подписчик EventClass Подписка Администрирование Рис. 22.6. Архитектура системы событий СОМ+ EventClass EventClass представляет собой компонент СОМ+, который устанавливает соединение между издателем и подписчиками. Все его интерфейсы называются интерфейсами события, а методы — методами события, или просто событиями. Методы события, подобно методам компонентов, работающих с очередями, могут содержать только входные параметры и должны возвращать значение hresult. (Вы не можете получить результат из метода события, поскольку вызов идет многим подписчикам, и возможно, что разные подписчики вернут разные значения.) Компонент EventClass сам по себе обеспечивает реализацию "пустышек" методов события, которые никогда не вызываются. Вместо этого система событий создает собственную реализацию методов, которые и будут вызываться соответствующими подписчиками. EventClass должен обеспечивать библиотеку типов и быть саморегистрирующимся. Кроме того, он имеет CLSID и должен также предоставлять ProglD. Вы должны зарегистрировать EventClass, чтобы подписчики могли найти его и осуществить подписку. Регистрация может быть произведена с помощью кнопки Install New Event Class инструмента Component Services, доступной при создании нового приложения СОМ+. Кроме того, регистрацию можно осуществить с помощью метода ICOMAdminCatalog: : InstallEventClass административного API. Подписка Подписка представляет собой то, что обеспечивает свободную связь между издателями и подписчиками, осуществляя соединения между ними. Подписка включает следующую информацию: ■ идентификатор издателя; ■ идентификатор EventClass;
■ интерфейс события, ■ метод события; ■ идентификацию подписчика (которая может представлять собой CLSID, имя очереди иди указатель на интерфейс); ■ информацию о фильтровании. Подписка хранится в Каталоге COM+. Подписчики могут указать собственную подписку, воспользовавшись административным API, а также выполнить ее, прибегнув к инструменту Component Services. Постоянная подписка Постоянная (persistent) подписка отличается тем, что хранится в Каталоге, как следует из ее названия, постоянно, т.е. в состоянии пережить перезапуск системы. Такая подписка не зависит от времени жизни подписчика и будет сама создавать подписчика по его CLSID (или по имени очереди, если подписчик представляет собой компонент, работающий с очередями). Объект подписчика освобождается после завершения метода события. Постоянная подписка создается с помощью инструмента Component Services. Временная подписка Временная (transient) подписка привязана к определенному подписчику, который должен существовать до ее создания. Подписка осуществляется посредством административного API. Подписчик определяется путем передачи указателя на интерфейс объекта подписчика. Управлять временем жизни временной подписки должны вы сами; кроме того, вы не можете использовать Component Services для создания временной подписки. Подписчики В СОМ+ подписчик представляет собой обычным образом настроенный компонент СОМ. Вы не должны реализовывать никакие специфичные интерфейсы, а только каким-либо способом настроить подписку. В случае временной подписки вы вызываете административный API, в случае постоянной — используете Component Services. В разделе интерфейсов компонента в этом инструменте имеется раздел Subscriptions, показанный на рис. 22.8. Издатели В этом случае вы также не должны реализовывать никакие специальные интерфейсы. Подписчики находят издателей посредством подписки и системы сообщений COM+, а не по специальным интерфейсам издателя. Для генерации событий издатели просто создают экземпляры объектов Eventciass и вызывают методы его интерфейсов. Система событий COM+ принимает меры к передаче событий соответствующим подписчикам. Фильтрация Полезным свойством системы событий COM+ является возможность фильтрации, которой нет в архитектуре жестко связанных событий. Для фильтрации используются две технологии: фильтрация издателей и фильтрация параметров, и обе они могут применяться одновременно.
Фильтрация издателей Фильтрация издателей предоставляет полный программный контроль над генерацией событий. Вы можете управлять тем, какие подписчики будут получать уведомления о событии, и порядком генерации событий. Вам надо реализовать класс фильтра издателя, который встраивается в систему событий путем установки свойства MultiPublisherFilterCLSID класса EventClass либо с помощью административного API, либо вызовом IEventControl: : SetPublisherFilter. Первый метод предпочтительнее, поскольку позволяет вашим событиям работать с компонентами, работающими с очередями. Класс фильтра издателя должен поддерживать один из интерфейсов IPublisherFilter или IMultiPublisherFilter. Последний может поддерживать несколько интерфейсов в EventClass Метод Initialize вызывается системой событий разово; ему передается указатель на интерфейс lEventSystem, который может быть использован для получения счетчика списка подписок. Когда издатель генерирует событие, система событий вызывает метод PrepareToFire класса фильтра, передавая ему имя генерируемого события и указатель на интерфейс iFiringControl. Затем вы можете вызвать iFiringControl: :FileSubscription для передачи сообщения тем подписчикам, которым хотите, и в том порядке, который вам нужен. Для более тонкого контроля можно реализовать интерфейсы событий в фильтре издателя, после чего вы можете использовать параметры в вызовах методов событий в вашей логике обработки событий. Фильтрация параметров Более простой механизм фильтрации использует строку условия фильтрации, являющуюся свойством подписки. Такая фильтрация выполняется для каждого метода и каждой подписки. Вы можете определить строку, используя имена параметров из библиотеки типов. Распознаются также стандартные операторы отношений, вложенные скобки и ключевые слова and, or и not. Строка может быть определена с помощью инструмента Component Services или с использованием административного API. Пример события СОМ+ В этом разделе мы рассмотрим полнофункциональный программный пример, иллюстрирующий работу системы событий СОМ+. Это тот же пример, который мы использовали при рассмотрении старой технологии событий на основе точек подключения. Мы вновь используем Visual Basic с тем, чтобы сосредоточиться на рассмотрении концепций, а не на деталях кодирования. Наш пример служит прототипом структуры событий Electronic Commerce Game™. Мы хотим обеспечить участников игры возможностью получать уведомления о присоединении к игре новых участников и выходе из нее старых. Нам требуется предоставление трех компонентов. ■ EventClass, определяющий интерфейс события с методами событий Join и Quit. Реализуются только заглушки методов; создается ActiveX DLL, которая устанавливается в приложение СОМ+ в качестве EventClass. ■ Подписчик, обеспечивающий реальную реализацию методов событий. Он инсталлируется как приложение СОМ+, а для создания постоянных подписок применяется инструмент Component Services. ■ Издатель, обеспечивающий интерфейс пользователя для генерации события. Его задача — создание экземпляра объекта EventClass и вызов методов событий.
EventClass EventClass можно найти в каталоге Chap22\ecEventVb. Код просто обеспечивает заглушки методов Join и Quit класса Player. Public Sub Join(ByVal name As String) ' пусто End Sub Public Sub Quit(ByVal name As String) 'пусто End Sub Выполните следующие действия для регистрации этого компонента в качестве EventClass в каталоге СОМ+ с помощью инструмента Component Services. 1. Создайте пустое приложение СОМ+ под названием ecEventApp. 2. Выберите для вашего нового приложения Components и щелкните на нем правой кнопкой мыши. В контекстном меню выберите New^Component, а в СОМ Component Install Wizard — Install new event class(es) (рис. 22.7). Рис. 22.7. Использование Component Services для инсталляции класса события 3. В диалоговом окне Select files to install выберите файл Chap22\ecEventVb\ecEventVb.dll и щелкните на кнопке Open. 4. Вы увидите ваш файл и найденный в нем класс события Player. Щелкните на кнопке Next, а затем — на кнопке Finish. Подписчик Подписчик может быть найден в каталоге Chap22\SubDemoVb. Этот проект содержит ссылку на ecEventVb и обеспечивает реализацию методов интерфейса событий. В нашей демонстрационной программе мы просто выводим окно с соответствующим сообщением.
Implements Player Private Sub Player_Join(ByVal name As String) MsgBox name & " has joined the game" End Sub Private Sub Player_Quit(ByVal name As String) MsgBox name & " has quit the game" End Sub Выполните следующие действия для регистрации подписчика в качестве компонента СОМ+ с использованием инструмента Component Services и создания необходимых подписок. 1. Создайте пустое приложение СОМ+ под именем ecSubApp. 2. Инсталлируйте SubDemoVb.dll как обычный компонент (не класс события). 3. В дереве просмотра откройте новый компонент SubDemoVb.SubEventVb. Вы увидите папки для интерфейсов и подписок (рис. 22.8). Рис. 22.8. Управлять подписками можно с помощью инструмента Component Services 4. Выберите Subscriptions и щелкните правой кнопкой мыши. Выберите в контекстном меню New^Subscriptions, это приведет к вызову COM New Subscription Wizard. Выберите в нем интерфейс _Р1ауег, что приведет к подписке на все методы событий интерфейса (рис. 22.9). Щелкните на кнопке Next. 5. Теперь надо выбрать класс события для нужного вами интерфейса. У нас есть только один класс, ProgID которого— ecEventVB. Player. Выберите его и щелкните на кнопке Next. 6. Присвойте этой подписки имя All Events и отметьте опцию Enable this subscription immediately. Затем щелкните на кнопке Next, а потом — на кнопке Finish. 7. Теперь повторите указанные выше действия для создания второй подписки. В этот раз подпишитесь только на метод Join (рис. 22.10) и дайте подписке имя Only Join.
Рис. 22.9. Подписка на интерфейс, включающая все методы Рис. 22.10. Подписка на метод Join Добавление фильтра Теперь добавим фильтр к подписке All Events. Вспоминая, что наш пример — это прототип Electronic Commerce Game™, логично предположить, что не имеет смысла посылать сообщение о присоединении к игре нового игрока этому игроку. Для решения данной проблемы можно назначить фильтр, который разрешает передачу уведомления при условии, что имя отличается от определенного нами (в демонстрационном примере мы для простоты определяем его в коде). В режиме дерева просмотра откройте папку Subscriptions и выберите All Events. Щелкните правой кнопкой мыши и выберите в контекстном меню Properties, а в диалоговом окне — вкладку Options. Введите в поле Filter criteria строку name != "Bob" (рис. 22.11). Щелкните на кнопке ОК.
Рис. 22.11. Определение фильтра пара- метра Издатель И, наконец, рассмотрим код издателя. Он очень прост и находится в каталоге Chap22\PubDemo. Издатель обеспечивает пользовательский интерфейс для запуска методов событий Join и Quit, передавая им имя, введенное в текстовом поле. Издатель просто создает экземпляр объекта события и вызывает методы. Вот его код: Dim objEventVb As New ecEventVB.Player Private Sub cmdJoin_Click() objEventVb.Join txtName End Sub Private Sub cmdQuit_Click() objEventVb.Quit txtName End Sub Попробуйте запустить программу. Вы увидите пользовательский интерфейс, показанный на рис. 22.12. Рис. 22.12. Пользовательский интерфейс программы издателя
Попытайтесь щелкнуть на кнопке Join при введенном имени John Smith. Должны появиться два окна сообщения, по одному для каждой подписки. Теперь щелкните на кнопке Quit — в этот раз вы увидите только одно окно сообщения, так как вторая подписка работает только с методом Join. В качестве последнего эксперимента введите имя Bob и снова щелкните на кнопке Join. Теперь вы увидите только одно окно сообщения из-за фильтрации событий с именем Bob. При щелчке на кнопке Quit вы не увидите ни одного окна сообщения. Резюме Общие требования к распределенным системам включают обеспечение уведомления о происходящих в системе событиях. СОМ предоставляет для этого "жестко связанную" систему событий, использующую при работе точки подключения. СОМ+ обеспечивает другую, более гибкую архитектуру свободно связанных событий, которая к тому же более легка для программирования. Вы должны предоставить системе класс события, который является ActiveX DLL с заглушками методов событий. Этот класс должен быть зарегистрирован в Каталоге СОМ+, что можно осуществить с помощью административного API или инструмента Component Services. Реальная реализация методов событий обеспечивается подписчиком и вызывается системой событий в соответствии с подпиской, которая также осуществляется с помощью административного API или инструмента Component Services. Издатель генерирует событие простым созданием экземпляра объекта EventClass и вызовом его методов. События могут фильтроваться как программно, так и административно, простым определением строки условия фильтрации. В последней главе книги мы поговорим о некоторых дополнительных возможностях СОМ+, которые позволяют увеличить масштабируемость приложений.
Глава 23 СОМ+ и масштабируемость Задана СОМ+ состоит в упрощении разработки распределенных приложений уровня предприятия. СОМ+ предоставляет множество полезных сервисов, изученных нами в этой книге. Один из важнейших вопросов при рассмотрении СОМ+ — масштабируемость. Мы уже осветили некоторые возможности СОМ+ по увеличению масштабируемости (например, такие как активизация по необходимости или пул подключений). В этой главе книги мы более детально рассмотрим пул объектов и познакомимся с кластеризацией, которая позволяет увеличить масштабируемость добавлением дополнительных узлов обработки Microsoft обеспечивает несколько различных технологий кластеризации, с которыми мы вкратце познакомимся. Мы ознакомимся также с возможностью СОМ+ распределять загрузку компонентов по многим узлам СОМ+ предоставляет сервис балансировки загрузки для оптимизации назначения компонентов узлам. Технология кластеризации Microsoft При увеличении нагрузки на вычислительную систему естественное решение напрашивается само собой — купить дополнительное аппаратное обеспечение. Можно постоянно покупать все более мощные и мощные компьютеры, но, скорее всего, это приведет только к опустошению вашего кошелька. Кроме того, вы очень быстро купите "самый-самый" компьютер, и на этом исчерпаются возможности наращивания вычислительных мощностей. Более логичный путь — использовать многопроцессорные системы Имеется два типа многопроцессорных систем — жестко связанные и свободно связанные. Наиболее распространенным типом жестко связанных систем является симметричная многопроцессорная система с разделяемой памятью (Shared-memory Multiprocessor — SMP). Такие системы работают с одной копией операционной системы и прозрачно для пользователя распределяют потоки между процессорами. Ограничение масштабируемости SMP связано с проблемами доступа к общей памяти посредством единой шины. Кроме того, SMP не обеспечивают никакой защиты от сбоев. Свободно связанные микропроцессоры, или кластеры, предоставляют решение с большей масштабируемостью и обеспечивают помехоустойчивость системы Кластер состоит из независимых узлов, каждый из которых имеет собственный процессор и память и работает со своей копией операционной системы. Поскольку узлы в кластере не разделяют общую память, система кластеров обладает более высокой по сравнению с SMP
масштабируемостью. Кроме того, благодаря использованию каждым узлом своей копии операционной системы выход из строя одного узла не приведет к неработоспособности в целом. В этом разделе мы рассмотрим три различные кластерные технологии, предлагаемые Microsoft. Каждая из них имеет собственную область применения, и все они могут применяться совместно для построения высокомасштабируемых и надежных Web- приложений. Microsoft Cluster Server Первым продуктом Microsoft в области кластерных технологий был Microsoft Cluster Server. Этот продукт использует испытанные технологии, приобретенные Microsoft у Digital и Tandem и входит в поставку NT Server 4.0 Enterprise Edition. Базовая архитектура Microsoft Cluster Server показана на рис. 23.1. Клиент Клиент Клиент "Пульс" Сервер 1 Сервер 2 SCSI Диск Диск Рис. 23.1. Архитектура Microsoft Cluster Server Клиенты подключаются к серверам с помощью локальной вычислительной сети. Серверы связаны высокоскоростным каналом, поддерживающим "пульс" (heartbeat). Сервера работают независимо друг от друга, однако посредством "пульса" отслеживают корректность работы друг друга. Каждый сервер имеет собственный диск (или дисковый массив). Каждый диск подключен к обоим серверам посредством SCSI, но в любой момент времени диск работает только с одним сервером. Учтите также, что серверы могут представлять собой SMP-микропроцессоры. Имеется две программные модели для разделения ресурсов в кластере. В модели "разделяемого диска" программное обеспечение, работающее на любом из серверов, может получить доступ к любому диску. Таким образом, данные могут разделяться серверами, и, следовательно, обращение к данным должно быть упорядочено с помощью менеджера распределенных блокировок (Distributed Lock Manager — DLM). На-
кладные расходы, возникающие из-за использования DLM, снижают масштабируемость. Microsoft Cluster Server использует модель, в которой каждый сервер кластера владеет некоторым подмножеством ресурсов, и к определенному ресурсу одновременно может обращаться только один сервер. В случае сбоя в работе сервера система динамически перенастраивается таким образом, чтобы ресурсы контролировались другим сервером. "Пульс" между серверами используется для обнаружения сбоя в работе приложения или сервера. В случае сбоя в работе приложения Microsoft Cluster Server может попытаться перезапустить приложение на том же сервере. Если же обнаружен сбой в работе сервера, Microsoft Cluster Server перемещает ресурсы приложения на другой сервер и уже там пытается его перезапустить. Если сервер вышел из строя, на оставшийся в работоспособном состоянии сервер переносятся все приложения. Такая операция обычно занимает немного времени, как правило, не более минуты. Microsoft Cluster Server может быть настроен таким образом, что в случае возврата сервера в рабочее состояние все приложения на нем будут автоматически восстановлены. Сбой приложения или сервера не является прозрачным для клиентского приложения. Например, происходит сбой удаленного вызова процедуры. Приложение-клиент может повторить его несколько раз, и в результате, по прошествии определенного периода времени вызов будет неудачным. Если период тайм-аута у клиента установлен достаточно большим, то за это время возможен перенос приложения на другой сервер и, таким образом, произойдет восстановление нормальной работы клиента. Такой подход, конечно, требует соответствующего кодирования клиентского приложения. В некоторых случаях конечному пользователю может быть выведено типичное окно сообщения "Abort, Retry or Cancel" и при выборе продолжения работы вызов будет перенесен на другой сервер. SQL Server 7.0 сконструирован для работы с Microsoft Cluster Server, так что два работающих вместе SQL-сервера могут оказаться неплохим решением для повышения надежности и масштабируемости уровня данных в трехуровневом приложении. Windows Load Balancing Service Сервис баланса загрузки обеспечивает кластеризацию с балансировкой 1Р-трафика. Типичное применение Windows Load Balancing Service — повышение масштабируемости Web-серверов путем распределения входящего IP-трафика между несколькими узлами кластера. Повышение загрузки при такой системе кластеризации может компенсироваться добавлением новых узлов (Windows Load Balancing Service поддерживает до 32 узлов). Как и Microsoft Cluster Server, Windows Load Balancing Service обеспечивает возможность автоматического преодоления сбоев, т.е. при сбоях в работе одного из узлов его трафик будет перенаправлен другим узлам кластера. Windows Load Balancing Service основан на Convoy Cluster Software, разработанном фирмой Valence Research Inc. и приобретенном Microsoft. Архитектура Windows Load Balancing Service (входящего в поставку Windows NT Server 4.0 Enterprise Edition) показана на рис. 23.2. Windows Load Balancing Service представляет собой стандартный сетевой драйвер NT, устанавливаемый на каждом сервере (узле) кластера. Эти серверы располагаются в одной подсети. Windows Load Balancing Service прозрачен как для клиента, так и для сервера. Клиент обращается к серверу с использованием одного IP-адреса, и входящие запросы отправляются одному из узлов. Если он сбоит, сетевой трафик перенаправляется другим узлам. Windows Load Balancing Service не в состоянии обеспечить непосредственную балансировку загрузки сервисов с запоминанием состояния, но обеспечивает баланс загрузки сервисов без запоминания состояния — например, таких как Web-сервер с доступом к статическим Web-страницам — поскольку такие серверы обрабатывают отдельные запросы независимо один от другого. Windows Load Balancing Service хорошо работает в многоуровневой архитектуре.
Клиент Клиент Клиент Internet TCP/IP Маршрутизатор/шлюз Узел Узел Узел Узел Рис. 23.2. Архитектура Windows Load Balancing Service Component Load Balancing Третье кластерное решение Microsoft представляет собой баланс загрузки компонентов (Component Load Balancing — CLB), один из сервисов, обеспечиваемых СОМ+. Изначально Component Load Balancing был частью Windows 2000, а сейчас является частью продукта под названием AppCenter Server. Посредством Component Load Balancing вы можете конфигурировать серверы Windows 2000 в обычной локальной сети для участия в качестве членов "кластера приложения". Вы устанавливаете приложение СОМ+ на нескольких узлах, а затем входящие запросы на создание объекта распределяются по всем этим узлам. Входящий запрос обрабатывается сервером Component Load Balancing, который используется в качестве маршрутизатора для направления запроса одному из серверов приложения в кластере. На рис. 23.3 приведена архитектура Component Load Balancing, которую мы обсудим в следующем разделе. Заметьте, что CLB-сервер имеет равные права с серверами приложения, а потому может выступать одновременно и в роли сервера приложения. На серверах могут работать как приложения с балансировкой загрузки (запросы к которым пойдут через сервер Component Load Balancing), так и без балансировки (запросы к которым пойдут непосредственно к соответствующему серверу).
Клиент Клиент Сервер CLB Клиент Сервер приложения Локальная или - глобальная вычислительная сеть -Локальная вычислительная сеть Сервер приложения Рис. 23.3. Архитектура СОМ+ Component Load Balancing СОМ+ Component Load Balancing Теперь вы должны представлять себе, какие технологии Microsoft могут использоваться для кластеризации Вы можете применять Microsoft Cluster Server для создания высокопроизводительного уровня данных и в других местах, где важна постоянная доступность программного обеспечения. Windows Load Balancing Service может использоваться для масштабирования входящих IP-запросов к Web-приложениям. В случае приложений среднего уровня, в которых используются компоненты СОМ+, для повышения масштабируемости можно воспользоваться Component Load Balancing. В этом разделе мы рассмотрим технологию Component Load Balancing немного подробнее. Балансировка загрузки Основная идея кластера состоит в том, что работа в нем распределяется между разными узлами, что позволяет увеличить общее количество выполняемой работы. Кроме того, увеличивается гибкость системы, поскольку вы можете добавлять или удалять узлы, как того требуют сложившиеся условия работы. Однако для получения хорошего результата необходимо обеспечить баланс загрузки узлов работой. Если один узел делает всю работу, а другой простаивает, то смысла в такой "кластеризации" нет никакого. Баланс загрузки может быть возложен на клиенты. Обычный DCOM способен распределить работу между несколькими серверами без привлечения какого-либо дополнительного программного обеспечения, просто назначая различным клиентам разные серверы Такой подход, однако, может оказаться слишком трудным для администрирования. Гораздо лучший подход состоит в наличии серверов, которые обеспечивают баланс загрузки незаметно для клиента. В СОМ+ это обеспечивается наличием маршрутизирующего сервера Component Load Balancing, показанного на рис. 23.3. Этот сервер использует определенный алгоритм (который будет рассмотрен чуть ниже) для определения того, какой из серверов приложений наименее загружен, и передает ему пришедший запрос.
При использовании балансировки загрузки становится важной "зернистость" заданий. При очень больших заданиях становится сложно обеспечить требуемый уровень распределения работы; при малых — хорошая сбалансированность сопровождается высокими накладными расходами, поскольку приходится маршрутизировать множество мелких заданий. Единицей работы в балансировке загрузки СОМ+ является активизация компонента. Входящий вызов CoCreatelnstance (Ex) или CoGetObject обрабатывается сервером Component Load Balancing, который определяет целевую машину с наименьшей загрузкой в кластере и перенаправляет ей запрос. Полученный указатель на интерфейс возвращается назад клиенту. После этого клиент обращается к компоненту на машине, на которой был создан экземпляр объекта, без участия сервера Component Load Balancing. После того как объект активизируется, он остается связанным с создавшим его сервером на все время жизни объекта. Заметим, что наличие балансировки загрузки не изменяет программную модель клиента. Клиент может получить ссылку на объект и хранить ее продолжительное время. При активизации по необходимости объект деактивизируется по завершении вызова метода. Новый вызов метода приводит к активизации объекта и, следовательно, к новой балансировке загрузки. Объект при этом может быть создан на другом сервере. Ссылка на объект у клиента представляет собой прокси, а реальный объект может каждый раз оказываться на другой машине — при этом с точки зрения клиента все происходит так, как если бы он работал с одним и тем же объектом. Алгоритм балансировки загрузки Двумя подходами выбора сервера для входящих запросов являются круговой (round robin) и по времени отклика. В случае кругового подхода маршрутизирующий сервер просто поддерживает список серверов приложения и направляет каждый запрос очередному серверу из списка. По достижении конца списка маршрутизатор переходит в его начало. В случае алгоритма с использованием времени отклика маршрутизатор собирает данные о времени отклика серверов приложения и направляет запрос тому серверу, у которого время отклика минимально. СОМ+ в настоящее время использует гибридный алгоритм. Сервер Component Load Balancing периодически опрашивает серверы приложения и строит список, упорядоченный по их времени отклика, после чего до следующего опроса использует круговой подход. В зависимости от значения интервала времени между опросами се р- веров этот алгоритм может оказаться близок к круговому (при большом значении и н- тервала) или к алгоритму по времени отклика (при малом интервале). Составляющие кластерные технологии Одним из достоинств кластерных технологий Microsoft является то, что они не ставят пользователя перед выбором "или—или", а могут работать совместно, дополняя возможности друг друга. Так, при использовании технологии Component Load Balancing маршрутизатор — это самое "тонкое" место, и его выход из строя способен привести в неработоспособное состояние всю систему (система будет продолжать работать, но о балансировке загрузки в этой ситуации говорить не приходится). Следовательно, маршрутизатор является кандидатом для использования технологии Microsoft Cluster Server, которая обеспечивает повышенную надежность. Кроме того, Microsoft Cluster Server — хороший кандидат для использования на уровне данных. На этом уровне он обеспечивает высокую надежность и доступность.
Если ваше распределенное приложение использует Web-технологии, дополнительную масштабируемость можно получить, применив Windows Load Balancing Service для маршрутизации входящего IP-трафика. И, наконец, индивидуальные машины в кластере могут быть многопроцессорными. Если использовать все описанные технологии и SMP на базе 64-битового процессора Merced, вы увидите, насколько высокой окажется масштабируемость вашей системы! Настройка Component Load Balancing Component Load Balancing легко настраивается в сети Windows 2000. Прежде всего следует запустить Windows 2000 Advanced Server или Windows 2000 Datacenter Server на машине, которая будет выступать в роли сервера Component Load Balancing (как отмечалось ранее, вам необходим дополнительный продукт — AppCenter Server). После этого вы можете сконфигурировать машину как CLB-сервер с помощью инструмента Component Services. Щелкните на пиктограмме My Computer правой кнопкой мыши и выберите в контекстном меню пункт Properties В диалоговом окне Properties выберите вкладку CLBS и в ней отметьте опцию Use this computer as the load balancing server Щелкните на кнопке Add для добавления серверов приложения (на рис. 23.4 показана настройка CLB-сервера с двумя серверами приложения). Вы можете также настроить все компоненты, для которых должна балансироваться загрузка. Для этого в Component Services вызовите диалоговое окно свойств компонента, который вы хотите настроить, выберите в нем вкладку Activation и, как показано на рис. 23.5, отметьте опцию Component supports dynamic load balancing Вы должны установить сконфигурированные приложения с компонентами, использующими технологию Component Load Balancing, на каждом сервере приложения. Затем, при вызове CoCreatelnstanceEx клиентом с использованием DCOM вам нужно указывать в качестве имени сервера сервер Component Load Balancing (используя системный реестр или структуру coserverinfo). Рис. 23.4. Настройка машины в качестве сервера Component Load Balancing Рис. 23.5. Настройка компонента со сбалансированной загрузкой
Защита от сбоев в Component Load Balancing Описываемая технология позволяет обеспечить определенную степень защиты от сбоев в распределенных приложениях. Рассмотрим, что произойдет, если сервер приложения даст сбой между вызовами методов. Если компонент позволяет использование активизации по необходимости, следующий вызов метода приведет к новой активизации объекта. CLB-сервер определит, что давший сбой сервер недоступен, но если в кластере имеется другой работоспособный сервер приложения, запрос будет перенаправлен ему, и с точки зрения клиента никакого отказа сервера просто не наблюдается. Если же активизация по необходимости не используется, то второй вызов метода по тому же сценарию приведет к тайм-ауту. После этого клиент может освободить ссылку на объект и создать новый экземпляр объекта. При этом мы вновь попадаем в ситуацию, когда CLB-серверу поступает запрос на активизацию объекта. Если в кластере есть доступный сервер приложения, вызов осуществится успешно, и клиент может продолжать работу. Вопросы разработки CLB-компонентов При разработке компонентов, которые должны работать с балансировкой загрузки, следует учитывать некоторые моменты. Ваш компонент не должен быть привязан к определенной машине, а логика программы должна быть способна обработать перемещение экземпляра объекта между машинами. Вы не должны хранить состояние объекта во временном файле на машине, на которой работает объект. Кроме того, такой компонент не должен использовать пул объектов, как мы увидим чуть позже. Производительность В случае приложений уровня предприятия производительность оказывается одним из важнейших вопросов. При попытках увеличения масштабируемости приложения не следует забывать о том, что вы должны обеспечить клиентам адекватную производительность. К сожалению, ее трудно измерить, так как обычно при разработке программного обеспечения в вашем распоряжении меньше техники, чем реально используется предприятием для развертывания приложения. Помочь в оценке производительности вам может предоставляемый Microsoft инструментарий Windows DNA Performance kit, содержащий инструменты и документацию, которые помогут вам провести измерения производительности распределенных приложений. В состав инструментария входит драйвер, позволяющий смоделировать одновременную работу многих клиентов, и многие другие полезные утилиты, обеспечивающие возможность всестороннего анализа. Найти Windows DNA Performance kit. можно по адресу: www.microsoft.com/com/ resources/WinDNAPerf.asp. Пул объектов Имеется множество путей достижения высокой масштабируемости приложений. Основной помехой при этом является ограниченность тех или иных ресурсов. Помехой может стать ограничение мощности процессора, и тогда решением будет добавление процессоров — например, с использованием технологии кластеризации. Другие виды ограниченных ресурсов включают память и подключения к базе дан-
ных. Модель активизации СОМ+ решает вопросы преодоления этих ограничений с помощью активизации по необходимости. Объекты, использующие большое количество ресурсов, могут быть деактивизированы, а используемые ими ресурсы — освобождены. Таким образом, может иметься множество клиентов, работающих со ссылками на объекты, но ресурсы при этом используются только тогда, когда это необходимо (вспомним, что, как мы уже упоминали в главе 14, "Основы архитектуры СОМ+", использование ресурсов неактивным компонентом не снижается до нуля — для поддержания объекта в неактивном состоянии может требоваться несколько сотен байтов памяти). Возможной проблемой при активизации по необходимости может стать высокая цена создания и инициализации объекта Решением в этом случае может стать пул экземпляров объектов. Тогда вновь активизируемый объект может получить экземпляр из пула вместо создания его "с нуля". При деактивизации экземпляр объекта возвращается в пул. Использование пула объектов Если ваши компоненты реализованы таким образом, что поддерживают работу с пулом объектов, использовать его очень легко. С помощью инструмента Component Services вы настраиваете каждый компонент, объекты которого могут быть размещены в пуле. Откройте диалоговое окно свойств компонента, выберите вкладку Activation, отметьте опцию Enable object pooling (если объекты компонента не могут размещаться в пуле, данная опция недоступна) и укажите минимальный и максимальный размеры пула, а также величину тайм-аута (рис. 23.6). Рис. 23.6. Настройка компонента для использования пула объектов При запуске приложения пул объектов заполняется созданными экземплярами в количестве, определенном как минимальный размер пула. Каждый компонент имеет собственный пул объектов с одинаковым CLSID. Пока в пуле имеются объекты, процесс активизации сводится к их извлечению оттуда. После того как все объекты из пула извлечены, но их количество не достигло максимального размера пула, создают-
ся новые объекты. При деактивации объектов они вновь возвращаются в пул. Если количество объектов достигло максимального размера пула, запрос ожидает освобождения какого-либо объекта в течение интервала тайм-аута. Если за это время ни один объект не освобожден, клиент получает код ошибки, свидетельствующий о превышении времени ожидания. IObjectControl Объекты компонента, допускающего размещение объектов в пуле, отвечают за отслеживание своего состояния и определение момента, когда они могут быть возвращены в пул. Для этого компонент должен реализовать интерфейс IObjectControl. При вызове метода IObjectControl: :CanBePooled объект должен вернуть соответствующее значение. При создании размещаемый в пуле объект связывается с внешним объектом, управляющим временем жизни объекта. Этот внешний объект вызывает следующие методы интерфейса IObjectControl. ■ Activate вызывается всякий раз при активизации объекта, перед вызовом любых других методов компонента. ■ Deactivate вызывается при деактивизации объекта, например при вызове SetComplete или SetAbort. ■ CanBePooled вызывается при возвращении объекта в пул. Если объект находится в состоянии, не допускающем повторное использование, он должен вернуть false (при этом объект будет освобожден). В противном случае объект снова помещается значение true и возвращается в пул. Нетранзакционные компоненты, унаследованные от СОМ, отвечающие требованиям к размещаемым в пуле объектам, могут быть расположены в пуле без реализации интерфейса IObjectControl. COM+ полагает, что объекты всегда могут быть размещены в пуле и повторно использованы самостоятельно, без помощи со стороны объектов, и управляет их временем жизни. Требования к размещаемым в пуле объектам Для того чтобы объект мог быть размещен в пуле, он должен удовлетворять ряду требований. ■ Отсутствие запоминания состояния. Поскольку объект может использоваться многими клиентами, он не должен запоминать состояние клиента, так как оно может оказаться корректным для одного клиента и совершенно некорректным для другого. ■ Отсутствие привязки к потоку. Объект не должен быть связан с определенным потоком, так как при работе с разными клиентами это может привести к излишним накладным расходам, связанным с переключением потоков. Следовательно, компонент должен быть помечен как работающий в многопоточном или нейтральном апартаменте. Это требование препятствует применению Visual Basic для разработки такого рода компонентов, поскольку в нем всегда используются апартаменты. Кроме того, компонент не должен использовать никакой локальной памяти потока. ■ Агрегация. Во время работы СОМ+ управляет временем жизни объекта агрегацией его в больший объект, который вызывает методы интерфейса IObjectControl. Это требование препятствует использованию Visual J++
для разработки компонентов, поскольку Visual J++ не поддерживает создание агрегируемых компонентов. ■ Обработка транзакций. Самым неприятным моментом в разработке объектов, размещаемых в пуле, оказывается работа с транзакциями, поскольку в данном случае вы не можете полагаться на автоматизацию этого процесса со стороны СОМ+ и должны разрабатывать соответствующий код самостоятельно. Пул объектов и балансировка загрузки Пул объектов и балансировка загрузки — вещи взаимоисключающие, поскольку пул располагается в памяти одной машины, и, следовательно, все объекты также расположены на одной машине, а не распределены по кластеры. Таким образом, балансировка загрузки в данном случае неприменима. Создание же разделяемого пула не имеет смысла в силу очень высоких накладных расходов. Важность СОМ+ Мы с вами проделали немалый путь. В чем смысл того, что мы изучили, и стоило ли изучать весь этот материал вообще? В этом последнем разделе я хотел бы изложить собственный взгляд на вещи. Эффективность СОМ СОМ+ основана на модели составных объектов СОМ, которая доказала свою надежность и эффективность. Изначально СОМ была призвана служить основой для OLE, но, в то время как технология OLE осталась технологией для работы с составными документами, СОМ — поистине вездесущая и используется в архитектуре любой системы Microsoft. Практически все новые функции API от Microsoft основаны на СОМ. СОМ эффективна, поскольку соответствует ставившимся изначально требованиям обеспечения бинарного стандарта для взаимодействия объектов. Интерфейс в стиле языка программирования С отлично работает с программами на C/C++, но не всегда хорош в случае других языков программирования. Классы-оболочки C++ вокруг С- интерфейса обеспечивают упрощение модели программирования (особую роль на этом пути играет библиотека MFC), но, опять-таки, — только для языка программирования C++. Библиотеки же, построенные на базе СОМ, годятся для работы с любым языком программирования, поддерживающим СОМ. В мире Microsoft это особенно важно в силу повсеместной распространенности Visual Basic. C++, Visual Basic, Visual J++ и многие другие языки программирования и инструменты сторонних производителей позволяют легко работать с компонентами СОМ. Компоненты СОМ хорошо разработаны и широко распространены. Управляющие элементы ActiveX реализуют разнообразную функциональность и в то же время просты в использовании, легко интегрируются в различные среды разработки, так же как и встроенные элементы. Эффективность СОМ определяется также использованием вызовов с помощью таблицы указателей на функции. СОМ не является промежуточным программным обеспечением (middleware). Являясь посредником между клиентом и объектом, СОМ после установления соединения "отходит в сторону", и работа клиента с объектом выполняется непосредственно.
СОМ эффективна еще и постольку, поскольку по природе своей является распределенной. Архитектура СОМ обеспечивает прозрачность размещения — клиент работает с объектом одним способом, независимо от того, является ли объект удаленным, находящимся в ЕХЕ на той же машине, что и клиент, или работающим в контексте клиента. Абстракция апартаментов СОМ обеспечивает эффективную работу многопоточного приложения, а механизм согласования интерфейсов позволяет легко решить проблемы обновления версий программного обеспечения. Мощь СОМ+ СОМ+ стоит на мощном фундаменте — СОМ — и добавляет к мощи и эффективности СОМ разнообразные сервисы для разработки распределенных приложений уровня предприятия. Для обеспечения работоспособности этих сервисов СОМ+ требует наличия уровня промежуточного программного обеспечения. Используемый СОМ+ для вызова этого уровня механизм очень хорошо разработан и не снижает ее эффективность (по сравнению с СОМ). Необходимый уровень абстракции обеспечивается контекстами. Вызов метода в пределах контекста обрабатывается обычной СОМ без каких-либо накладных расходов. При пересечении вызовами методов границ контекста СОМ+ вызывает перехватчик в качестве промежуточного программного обеспечения, согласующего контексты. Для определения контекста СОМ+ предлагает декларативную модель. Таким образом, если, например, компонент требует защищенности своей работы транзакцией, то это требование может быть выражено простым указанием соответствующего атрибута компонента, хранящегося в Каталоге СОМ+. Тогда при вызове метода перехватчик выполнит необходимый для осуществления распределенной транзакции код. Автоматическое выполнение таких сложных задач системой времени выполнения СОМ+ при указании соответствующего атрибута существенно упрощает задачи программиста и является одним из главных достоинств СОМ+. Повышение уровня абстракции В стороне от достоинств СОМ/СОМ+ как программной архитектуры находится очень важный с точки зрения разработки программного обеспечения вопрос об уровне абстракции СОМ+. Общая тенденция программной индустрии состоит в постоянном повышении уровня абстракции. Ваши программы работают не сами по себе, а опираются на множество других программ, инструментов, компонентов и пр. Все это программное обеспечение написано не вами, более того — вы не имеете никакого доступа к его коду, так что для вас это просто некоторые "черные ящики" с той или иной документированной функциональностью. В этом смысле СОМ+ представляет собой воплощение абстракции. При работе с СОМ+ для вас едва ли не главными станут вопросы корректной настройки созданных компонентов. СОМ+ предлагает настолько большое количество настроек, что иногда приходится долго разбираться, какая именно опция позволит заработать вашей программе. В качестве утешения могу только сказать, что при написании этой книги мне пришлось потратить немало времени, чтобы заставить работать пример с QC. Мне пришлось практически заново изучить весь материал по этой теме, чтобы убедиться, что мое понимание концепций работы очередей верно, а логика программы — безукоризненна. А оказалось, что я просто упустил из виду необходимость запустить ап- лет Message Queuing и щелкнуть на кнопке Renew Internal Security Certificate.
Важность качества В области разработки программного обеспечения вопросы качества всегда были важны, но сегодня они становятся не просто важны, а жизненно важны. Программные системы в такой мере вошли в жизнь человека, что цена ошибки выросла неим о- верно, порой оборачиваясь потерей человеческих жизней. Одновременно с этим сложность программных систем возросла настолько, что обеспечить отсутствие ошибок практически невозможно. Кроме того, ваш индивидуальный продукт опирается, как мы уже говорили, на множество продуктов других производителей, а значит, вы просто не можете гарантировать отсутствие ошибок в продукте в целом. Распределенные системы делают разработку "безгрешных" продуктов еще более сложной, поскольку функционирование в такой системе зависит не только от логического функционирования вашей программы, но и от физического поведения сложной сетевой системы Поэтому при разработке программного продукта вы должны уделить особое внимание вопросам обработки ошибок и восстановления работоспособности приложения после их возникновения Системы, построенные на использовании компонентов, имеют собственные проблемы, связанные с качеством программирования. С одной стороны, использование компонентов приводит к повышению качества приложения в целом с вязи с модульностью приложения. С другой стороны, хотя разработка и отладка отдельного компонента проще, чем разработка и отладка системы целиком, тестирование компонента имеет свои особенности, в первую очередь, заключающиеся в том, что компоненты тестируются написанием программ, а не работой с конечным продуктом. Кроме того, повторно используемые компоненты должны корректно работать в любых условиях, а не только в конкретной системе. Качество означает нечто большее, чем отсутствие ошибок. Вы так должны разрабатывать систему или компоненты, чтобы пользователь (в случае компонентов — программист) мог представить адекватную концептуальную модель, без которой система становится набором возможностей, но перестает быть системой, несмотря на корректную работу всех ее составных частей. Резюме Эта глава завершает наш экскурс по СОМ+. В ней мы остановились на вопросах масштабируемости. Нами рассмотрены два основных пути повышения масштабируемости — кластеризация и пул объектов. СОМ+ позволяет автоматически регулировать загрузку серверов приложений в кластере, причем предлагаемая технология Component Load Balancing способна работать совместно с другими технологиями кластеризации Microsoft. Пул объектов — несколько менее распространенная технология, реализуемая СОМ+ и работающая совместно с системой активизации объектов по необходимости. В данную главу включен также небольшой обзор основных достоинств технологии СОМ+. Я надеюсь, что эта книга, если даже и не стала вашей любимой, принесла определенную пользу в изучении СОМ+. Мне остается только пожелать вам успеха в использовании СОМ+ в вашей повседневной работе!
Приложение Источники информации Работая над книгой, я стремился сделать ее настолько самодостаточной, насколько это возможно, однако тема СОМ+ слишком велика, чтобы эта задача могла быть полностью выполнена. Поэтому в настоящем приложении приведены некоторые источники информации, которые помогут вам в освоении СОМ+. Основной источник информации — Microsoft Software Developers Network (MSDN). Ежеквартальная библиотека в настоящее время распространяется на компакт-дисках и DVD. Сейчас она занимает три компакт-диска и предоставляет гораздо больше информации, чем вы сможете загрузить из Web. Следующий источник информации — Web. Вот основные узлы, на которых вы сможете найти как интересующую вас информацию, так и ссылки на другие узлы со сходной тематикой: ■ http://msdn.microsoft.com — Web-узел MSDN; ■ http://www.microsoft.com/com — Web-узел Microsoft COM; ■ http://www.ObjectInnovations.com— узел Object Innovations, на котором представлена последняя информация о СОМ+, включая обновления и дополнения к данной книге. Следующий источник информации — периодические издания. Два самых полезных издания — Microsoft Systems Journal и Microsoft Internet Developer. И, наконец, книги. Технология СОМ+ только выходит "в свет", однако имеется множество книг, посвященных технологии СОМ и связанных с ней. Вот некоторые из них: ■ Abernathy Randy. COM/DCOM Unleashed. — Sams Publishing, 1999. ■ Bernstein Philip A. and Newcomer Eric. — Principles of Transaction Processing. — Morgan Kaufmann, 1997. ■ Box, Don. Essential COM. — Addison-Wesley, 1998. ■ Box et al. Effective COM, Addison-Wesley, 1999. ■ Dickman Alan. Designing Applications with MSMQ. — Addison- Wesley, 1998. ■ Gray Jim and Reuter Andreas. Transaction Processing: Concepts and Techniques. — Morgan Kaufmann, 1993. ■ Grimes Richard. Professional ATL COM Programming. — Wrox, 1998. ■ Grimes Richard. Professional DC О М Programming. — Wrox, 1997. ■ Homer Alex and Sussman David. Professional MTS and MSMQ with VB and ASP. — Wrox Press, 1998.
■ Kirtland Mary. Designing Component-Based Applications. — Microsoft Press, 1999. ■ Li Sing and Economopoulos Panos. Professional COM Applications with ATL. — Wrox, 1998. ■ Maloney Jim. Distributed COM Application Development Using Visual Basic 6 0.— Prentice Hall, 1999. ■ Pinnock Jonathan. Professional DC О М Application Development. — Wrox, 1998. ■ Piatt David. Understanding COM+. — Microsoft Press, 1999. ■ Rector Brent and Sells Chris. ATL Internals. — Addison-Wesley,1999. ■ Soukup Ron and Kelaney Karen. Inside Microsoft SQL Server 7.0. — Microsoft Press, 1999. ■ Thui Thuan L. Learning DCOM. — O'Reilly, 1999.
Предметный указатель А Access control list 318 Active Directory 77 Active Object Table 185 Active Server Pages См. ASP ActiveX 279, 406 ActiveX Data Objects См. ADO ADO 69, 357; 362; 385; 387 ASP 65; 82; 362; 394; 413-21 Объектная модель 415 ATL 47; 152; 189; 216; 235; 260, 262; 268, 280, 305 ATL Object Wizard 157 CComBSTR 170 CComPtr 171 В Backup domain controller 322 BSTR 110, 170 Bytecode 33 С Catalog Manager 276 CGI 64; 391; 396-98 CLSID 95; 100, 138 COM 118 COM+ Explorer 276 Component Load Balancing 464 Component Services 41; 276 CORBA 38, 56; 117 D DAO 69, 357 Data Access Objects См. DAO Data Source Name См. DSN DCOM 38; 194-210 DDE 36; 178 Discretionary Access Control List 324 Distributed Transaction Processing См. DTP DLL 34 Механизм выгрузки 145 Экспорт функций 144 DNS 76 Domain Name System 76 DSN 359 DTP 374 Dynamic Data Exchange 36 G Globally unique identifier 94; 100 GUID 94; 100, 126; 379 H HRESULT 230, 367 HTML 391 I IDL 92; 96; 128, 161 HD 100 IIS 63 Interface Definition Language 92; 96 Internet 390 Internet Information Server 63 Internet Information Services 399 Internet Server API См. ISAPI intranet 404 ISAPI 65; 411-13 J Java Beans 117; 279 Java Virtual Machine 33 JIT 380 JVM 33 L Lightweight remote procedure call 37 LRPC 37
м MFC 47; 65; 81; 133; 152 Microsoft Cluster Server 462 Microsoft Distributed Transaction Coordinator 67 Microsoft Management Console 75 Microsoft Message Queue 40, 67; 427-37 Объектная модель 429 Microsoft Office 62 Microsoft Transaction Server См. MTS MIME 395 MMC 75 MOM 40; 56; 68 MSDTC 67 MSMQ 40, 67 MTS 39; 56; 58; 65; 377 N NDS 78 О Object Browser 90 OCX 39 ODBC 32; 35; 55; 68; 356; 359; 387 OLE 36; 179, 440 OLE DB 357; 362; 369 OLE Transactions 376 OLE/COM Object Viewer 91; 112 Open Database ConnectivityJ5. См. ODBC P Platform SDK 82 Primary domain controller 322 R Recordset 363 Registry 111 Remote Method Invocation 56 RPC 38, 56; 182 s SAG 35 SCM 138, 208 SDK 247 Security Reference Monitor 318 Security Support Provider 328 Service Control Manager 138; 333 SGML 391 Shared Property Manager 287 SMP 461 Spy++ 255 SQL Access Group 35 SQL Server 347-55; 376; 418 Enterprise Manager 349 Query Analyzer 348 Блокировки 366 Создание базы данных 351 Создание таблицы 351 Сценарии 353 SSP 328 SSPI 59 System Access Control List 324 и UDA 69 UDT 37; 180 Unicode 106; 109; 170 Uniform Data Access 69, 356 Uniform data transfer 37; 180 Universally unique identifier 94 URL 392 UUID 94; 99; 126 V VBX 34 Visual Basic Extension 34 Visual InterDev 82 Visual Studio 80 w WDNA 58 Web 27; 39, 390-425 Web-броузер 392 Windows DNA 58 Windows DNA Performance kit 469 Windows Load Balancing Service 463 Windows Open System Architecture 55 Windows Terminal Server 62 WOSA 37; 55
X X/Open 375 A Абстракция 31; 473 Автоматизация 213; 221; 240 Авторизация 318 Агрегация 143 Активизация 280; 314 Активизация по необходимости 380 Активность 313 Активный каталог 59, 78, 323; 77 Апартамент 256; 275; 278; 311 Архитектура СОМ+ 280 Атрибут 275; 296 Зависимые атрибуты 380 Аутентификация 318 Б Балансировка загрузки 465 Алгоритмы 466 Бедный клиент 62 Безопасность 42, 59, 196; 208, 318-45; 408, 439 СОМ 328 СОМ+ 335 Kerberos 323; 328 NT 323 Авторизация 328 Аутентификация 331 Группа 321 Домен 323 Имперсонация 344 Объекты NT 324 Подлинность 332 Подмена пользователя 334 Рабочая группа 322 Роль 340 Учетная запись 319 Библиотека типов 168 Богатый клиент 61 Г Гипертекст 391 Границы транзакции 381 д Данные 31 Двойной интерфейс 214 Декларативная модель 275 Динамический HTML 63 Домен 323 Ж Жестко связанные события 451 3 Заглушка 184; 191 И Идентификатор транзакции 379 Издатель 452 Имперсонация 335 Инкапсуляция 32, 119 Интеллектуальный указатель 171; 172, 239 Интерфейс 38; 95; 96; 119, 122; 278 IClassFactory 136 IConnectionPoint 450 ICreateTypelnfo 214 IDispatch 213; 219 IErrorlnfo 234 I Marshal 209 IMultiQI 207 IObjectConstruct 304 IObjectContext 341 IObjectControl 304; 470 IPersist 189 ITransaction 378 ITypelnfo 214 ITypeLib 214 IUnknown 96; 126; 153; 279 Входящий и исходящий 449 Множественные интерфейсы 167 Реализация 129 Итератор 227 К Каталог СОМ+ 276 Класс 31; 97; 119, 278 Кластер 461 Коллекция 227 Компонент 34; 279 Инсталляция 292, 294
Контекст 42; 272, 280, 305; 341 Контекст выполнения 136; 146; 202 Контракт 32 Контроллер домена 77 Критический раздел 269 Л Локальная вычислительная сеть 52 Локальный сервер 136 м Маршалинг 183; 184; 209, 254; 261; 280 Масштабируемость 273; 461—71 Менеджер очередей 427 ресурсов 375 транзакций 3 74 Метод 31; 91; 95; 122, 278 Многопоточность 209, 311 Множественное наследование 119 Множественные интерфейсы 167 Модели потоков 257 Апартаментная 259 Модель компонентных объектов См. СОМ Н Набор записей 363; 387 Надежность 273 Нейтральный апартамент 312 О Обработка ошибок 132, 174; 238, 367 Visual Basic 243 Объект 97; 278 Время жизни 101 Оптимизация 205 Отладка 246 Очередь сообщений 427 П Первичный контроллер домена 322 Перехват 42; 280 Перехватчик 61 Поведение 31 Подписка 453 Подписчик 452 Позднее связывание 213 Полиморфизм 35 Политика 318 Приложение 279 Провайдер 357, 369 Производительность 469 Прокси 184; 191 Пространства имен 175 Протокол DCOM 59 FTP 394 Gopher 394 HTTP 50, 59, 64; 78, 391, 395-96 LDAP 78 Lightweight Directory Access Protocol 78 NetBios/NetBEUI 56 SPX/IPX 56 TCP/IP 56; 59, 76 Пул объектов 469 P Резервный контроллер домена 322 С Саморегистрация 165; 190; 279 Свободно связанные события 451 Свойства 95; 222 Связывание и внедрение объектов 36 Сервер 278 транзакций Microsoft Cm MTS Сервис NT 333 Синхронизация 311, 313 Система событий СОМ 446 Системный реестр Windows 111; 138, 1 98, 204 Интерфейсы 141 События 223 Согласование интерфейсов 120 Согласованность 43 Сообщения Windows 178 Станция Windows 334 Суррогат 182, 208 Счетчик ссылок 118, 127 Т Таблица активных объектов 185 Точки подключения 448 Транзакционный компонент 379
Транзакция 42; 65; 274; 373—89 ACID-правила 373 Автоматическая обработка в СОМ+ 379 Двухфазное принятие 3 76 Программирование 383 Распределенная 66; 374 Трассировка 246 У Унифицированный доступ к данным555 локатор ресурса 392 Учетная запись 319 Ф Фабрика классов 120; 127; 137; 166; 185; 279 Фильтрация событий 454 Э Экспорт приложения 309 Я Языки программирования Ada 48 Algol 32 С 32; 68 C++ 32; 68, 116; 118-35; 141-47; 152; 268, 472 Delphi 48 Eiffel 33 HTML 63 Java 27; 33; 56; 117; 119 JavaScript 63; 406 Object Pascal 48 Objective С 33 Simula 32 Smalltalk 33; 116 SQL 35; 347-55 VBA 62 VBScript 63; 99, 217; 218; 406 Visual BasicJJ; 34; 44; 69; 80, 98-, 148; 221; 302; 362; 448, 472 VARIANT 214 Visual C++ 81; 98, 156; 172, 219, 239, 303 Классы поддержки COM 176 Visual J++ 27; 119, 472
Учебное пособие Роберт Дж. Оберг Технология СОМ+. Основы и программирование Издательский дом "Вильяме". 101509, Москва, ул. Лесная, д. 43, стр. 1. Изд. лиц. ЛР № 090230 от 23.06.99 Госкомитета РФ по печати. Подписано в печать 08.06.00. Формат 70X100/16. Гарнитура Times Печать офсетная. Усл. печ. л. 31,93. Уч-изд л. 41,5. Тираж 5000 экз. Заказ № 1007. Отпечатано с диапозитивов в ГПП "Печатный Двор" Министерства РФ по делам печати, телерадиовещания и средств массовых коммуникаций. 197110, Санкт-Петербург, Чкаловский пр., 15.
VISUAL C++ Microsoft Роберт Дж. Оберг С0М+ ТЕХНОЛОГИЯ ОСНОВЫ И ПРОГРАММИРОВАНИЕ ПРАКТИЧЕСКОЕ РУКОВОДСТВО ПО WINDOWS 2000 DNA • Обзор Microsoft DNA и C0M+ • Концепции C0M/DC0M как основа C0M+ • Принципы и возможности декларативного программирования; основанного на атрибутах • Контексты, активизация и перехват • Управление распределенными транзакциями с помощью C0M+ • Разнообразные службы C0M+ Категория: программирование Предмет рассмотрения: Microsoft Windows 2000, СОМ+ Уровень: для пользователей средней и высокой квалификации ISBN 5-8459-0084-0 Технологии Microsoft COM+, DNA и Windows 2000 в ваших руках! Эта книга позволит вам повысить уровень знаний и мастерства программирования в области Windows 2000 С0М+ и распределенных сетевых приложений (DNA). Автор книги умело сочетает теоретический обзор С0М+ с практическими примерами, обеспечивая вас знаниями и навыками, необходимыми для разработки крупных приложений уровня предприятия. Книга начинается с рассмотрения архитектуры С0М+ и Microsoft DNA. Затем автор описывает ключевые концепции C0M/DC0M, лежащие в основе С0М+, иллюстрирует их примерами построения серверов и клиентов СОМ на языках программирования Visual C++ и Visual Basic, уделяя внимание таким важным вопросам, как обработка ошибок и отладка, знакомит с построением многоуровневых приложений. В книге также освещаются такие вопросы, как работа с базами данных с использованием ADO и SQL Server, безопасность Windows 2000 и С0М+. Не обойдены вниманием и современные Internet-технологии, включая создание приложений Web с использованием ASP и многое другое. Об авторе РОБЕРТ Дж. ОБЕРГ является президентом фирмы OBJECT INNOVATIONS, разрабатывающей программы учебных курсов в области объектно-ориентированных технологий, таких как C++, MFC, C0M/C0M+ и Java Им были созданы обучающие курсы для UCI Corporation. Роберт Дж. Оберг имеет степень доктора философии Гарвардского университета Компания OBJECT INNOVATIONS специализируется на разработке учебных курсов в области передовых программных технологий. Основана в 1993 году. Начальная страница: www.objectinnovations.com. www.phptr.com www.williamspublishing.com