- •Раздел 4. Разработка по Тема 4.1. Проектирование интерфейса с пользователем
- •4.1.1. Типы пользовательских интерфейсов.
- •4.1.2. Пользовательская и программная модели интерфейса.
- •4.1.3. Разработка диалогов.
- •4.1.4. Основные компоненты графических пользовательских интерфейсов.
- •Тема 4.2. Реализация графических пользовательских интерфейсов.
- •4.2.1. Диалоги, управляемые пользователем.
- •4.2.2. Диалоги, управляемые системой.
- •4.2.3. Использование метафор.
- •4.2.4. Технология Drag and Drop.
- •4.2.5. Интеллектуальные элементы.
- •4.3.1. Базовые типы данных.
- •Константы
- •Область действия имен
- •4.3.2. Указатели и адресная арифметика.
- •4.3.3. Составные типы данных. Структуры
- •Битовые поля
- •Определение типов
- •Перечислимые типы
- •4.3.4. Выражения и операции.
- •4.3.5. Управляющие конструкции. Условные операторы
- •Операторы циклов
- •4.4.1. Статические одномерные массивы.
- •4.4.2. Статические многомерные массивы.
- •4.4.3. Динамические массивы.
- •4.4.4. Массивы указателей.
- •4.5.1. Стеки.
- •4.5.2. Очереди.
- •4.5.3. Списки.
- •4.5.4. Бинарные деревья.
- •4.6.1. Объявление классов и экземпляров классов.
- •4.6.2. Инкапсуляция данных и методов.
- •4.6.3. Конструкторы классов.
- •Конструктор по умолчанию
- •Конструктор копирования
- •4.6.4. Деструкторы классов.
- •4.7.1. Разделы в описании класса.
- •4.7.2. Friend-конструкции.
- •4.7.3. Статические члены классов.
- •4.7.4. Использование описателя const в классах.
- •4.8.1. Вложенность классов.
- •4.8.2. Наследование данных и методов.
- •4.8.3. Типы наследования.
- •4.9.1. Полиморфизм раннего связывания.
- •4.9.2. Полиморфизм позднего связывания и виртуальные функции.
- •4.9.3. Абстрактные методы и классы.
- •4.10.1. Функции консольного ввода-вывода.
- •4.10.2. Функции файлового ввода-вывода.
- •4.10.3. Использование библиотеки классов потокового ввода-вывода.
- •4.11.1. Перегрузка операций.
- •4.11.2. Шаблоны функций.
- •4.11.3. Шаблоны классов.
- •4.11.4. Обработка исключений.
- •Тема 4.12. Com-технология.
- •4.12.1. Основные понятия.
- •4.12.2. Типы интерфейсов.
- •Свойства интерфейсов
- •Типы интерфейсов
- •4.12.3. Типы com-объектов.
- •4.12.4. Фабрика классов.
- •Тема 4.13. Построение com-сервера.
- •4.13.1. Язык idl.
- •Содержимое файла idl
- •4.13.2. Определение пользовательского интерфейса.
- •4.13.3. Реализация пользовательского интерфейса.
- •4.13.4. Создание тестового клиента.
- •Тема 4.14. Обзор платформы ms .Net.
- •4.14.1. Общая идея архитектуры .Net.
- •4.14.2. Достоинства и недостатки .Net.
- •4.14.3. Схема трансляции программ в .Net.
- •4.14.4. Язык msil.
- •4.14.5. Объектно-ориентированная модель .Net.
Типы интерфейсов
Интерфейс IUnknown
Интерфейс lUnknown наиболее важен по сравнению с остальными интерфейсами. Его должен реализовывать каждый компонент СОМ. Все объекты СОМ реализуют интерфейс IUnknown, так как он управляет всеми интерфейсами объекта.
Интерфейс IUnknown содержит три метода: QueryInterface, AddRef и Release. Эти методы были разработаны для программистов VC++. В среде VC++ метод Querylnterfасе используется для выявления почти всех доступных интерфейсов объекта. Когда нужно использовать один из этих интерфейсов, вызывается метод AddRef. По завершении работы с отдельным интерфейсом вызывается метод Release.
Для перемещения по интерфейсам служит метод Querylnterface. СОМ-объекты могут поддерживать несколько интерфейсов. В этом случае, если у разработчика есть один интерфейсный указатель и он хочет получить другой, он может запросить его у объекта с помощью метода Querylnterface. Этот метод поддерживается всеми интерфейсами как производными от lUnknown.
При использовании метода Querylnterface клиент передает объекту соответствующий идентификатор интерфейса IID. Если объект поддерживает данный интерфейс, он возвращает указатель на него. В противном случае объект возвращает ошибку.
Из-за того, что компоненты и приложения создаются независимо друг от друга, опубликованный интерфейс должен быть неизменным — изменение его синтаксиса и семантики не допускается. Любые модификации, даже числа методов, могут привести к проблемам. Например, новое клиентское приложение может ошибочно считать, что интерфейс включает пять методов. Если оно обратиться к устаревшему компоненту всего с четырьмя функциями, возникнет ошибка. Именно из-за возможности таких конфликтов СОМ-интерфейс не должен изменяться.
Новый интерфейс считается новой версией. Существующие приложения, скорее всего, не будут совместимы с ним, но так как исходный интерфейс остается неизменным, реализация интерфейсов в компонентах не повлияет на эти приложения. В новых программах можно реализовать поддержку обновленных интерфейсов и разрешить им доступ к новым функциям. Если же клиент обратится к старому компоненту, он сможет безопасно узнать об отсутствии новых методов с помощью Querylnterface.
Процесс удаления ненужного объекта довольно сложен. Например, какой-нибудь клиент может получить с помощью метода Querylnterface несколько интерфейсных указателей на один объект. При этом он не сможет определить момент, когда все интерфейсные указатели больше не используются и можно безопасно уничтожить объект — ведь объект, помеченный для удаления, может потребоваться другому клиенту. Ни одно приложение не может выяснить, что другие приложения больше не используют объект, но они могут помочь в этом самому объекту.
Для решения этой проблемы применяется управление временем существования объекта, обычно называемая счетчиком пользователей. Он отслеживает число клиентов, использующих интерфейс. При создании нового интерфейсного указателя вызывается функция AddRef, увеличивающая счетчик пользователей на единицу. Клиентский компьютер, не нуждающийся в интерфейсном указателе, вызывает метод Release интерфейса IUnknown, таким образом уменьшая значение счетчика на единицу. Когда счетчик пользователей оказывается равным нулю, объект уничтожается. Как видите, такое управление временем существования объекта решает все проблемы как в случае одного клиента с несколькими интерфейсными указателями, так и в случае нескольких независимых клиентов. На компьютерах, поддерживающих эту функцию, можно создать объект и получить интерфейсный указатель, а затем вызывать его методы. По окончании работы с объектом необходимо освободить указатель, вызвав функцию Release интерфейса IUnknown.
Интерфейс IDispatch
Интерфейс IDispatch порождается интерфейсом IUnknown. Этот интерфейс первоначально был создан для удобства использования языков сценариев и макросов и применяется в технологии автоматизации (Automation). Его использование не рекомендуется при разработке приложений (по причине роста накладных расходов), к которым не требуется доступ из языков сценариев.
Интерфейс IDispatch содержит функции, которые позволяют обращаться к методам и свойствам объектов COM. IDispatch позволяет VBScript и другим языкам создания сценариев управлять свойствами и методами объекта. Интерфейс IDispatch имеет два важных метода: Invoke и GetIDsOFNames. Метод GetlDsOFNames принимает код ID элемента, а метод Invoke выполняет остальную работу.
В своей простейшей форме, называемой поздним связыванием, автоматизация позволяет клиентам обращаться к объектам, не владея никакой информацией об их методах. Клиентское приложение обычным образом создает объект и запрашивает интерфейс IDispatch. Чтобы вызвать функцию объекта, нужно передать ее полное название методу GetlDsOfNames интерфейса IDispatch. Если запрашиваемая функция поддерживается, будет возвращено идентифицирующее ее число — диспетчерский идентификатор (Dispatch ID, DISPID). После этого клиент сохраняет параметры функции в стандартной структуре и передает ее вместе с DISPID методу Invoke интерфейса IDispatch.
С помощью DISPID объект определяет вызываемую внутреннюю функцию, которой затем передает параметры, выделенные из переданной ему структуры. Если параметры окажутся некорректными, метод Invoke может немедленно завершиться, не допустив возникновения сбоя. Полученные после работы внутренней функции данные и/или информация об ошибке сохраняются в стандартной структуре данных и возвращаются в возвращаемых параметрах метода Invoke.
Этот довольно запутанный процесс, тем не менее, очень полезен для интерпретируемых языков. Для использования объектов автоматизации интерпретатору сценариев нужно уметь создавать объекты, вызывать методы с помощью интерфейса IDispatch, выявлять ошибки и уничтожать объекты. Ему не придется конструировать стековый фрейм для разных соглашений о вызовах, интерпретировать интерфейсные указатели и выяснять, какие функции есть у объекта. Все это можно скрыть внутри интерпретатора, предоставив в распоряжение разработчиков сценариев простую программную модель.
Можно скрыть большинство таких функций и внутри компонента. Многие средства разработки, поддерживающие СОМ, по умолчанию создают компоненты, использующие автоматизацию. Если же и язык программирования совместим с СОМ, как например Visual Basic, можно полностью скрыть детали реализации интерфейса IDispatch, оставив разработчикам только написание открытых функций. Если какой-либо каркас приложения поддерживает СОМ, то в нем, как правило, реализован стандартный интерфейс IDispatch и определен механизм передачи DISPID внутренним функциям. В этом случае, как и в предыдущем, разработчикам остается написать только нужные им методы.
Но за такую гибкость позднего связывания приходится платить. Во-первых, все параметры, передаваемые методу Invoke, должны иметь тип VARIANT. Такие данные, помимо самого значения, содержат тэг, определяющий их тип. В автоматизации определен набор типов, которые можно поместить в VARIANT. Типы, не входящие в этот список, перед передачей методу в качестве параметра нужно преобразовать.
Во-вторых, объекты могут работать только с одним интерфейсом IDispatch. Хотя с помощью разных IID можно добавить дополнительные интерфейсы, основанные на IDispatch, ни один язык сценариев не получит к ним доступ. Поэтому большинство объектов автоматизации разрешают клиентам пользоваться только одним программным интерфейсом. Такое положение вещей может повлиять на проект приложения, особенно при наличии ограничений, связанных с защитой.
И последнее: при использовании позднего связывания возрастают накладные расходы. Каждый вызов метода приводит к двум обращениям к объекту. Первый — к функции GetlDsOfNames для поиска DISPID, второй — вызов метода Invoke. Кроме того, дополнительные издержки связаны с преобразованием параметров в структуру при передаче методу Invoke и обратным преобразованием при возврате из него.
Как правило, такие накладные расходы допустимы в интерпретируемых средах, но чаще всего неприемлемы для других клиентов.
Библиотеки типов
Из-за того что многие клиентские приложения компилируются, им не требуется позднее связывание. В этом случае объекты и методы определяются еще во время разработки.
Вместо того чтобы вызывать GetlDsOfNames со всеми накладными расходами, можно определить все DISPID в коде программы. Такая форма автоматизации называется ранним связыванием. При этом привязка компонентов к клиентским приложениям осуществляется во время их сборки. Чтобы раннее связывание работало, среда разработки должна уметь определять DISPID вызванных методов. В идеале она должна определять и правильность передаваемых параметров, что устранило бы большинство ошибок. Для этого необходимы описания методов компонента, которые и хранятся в библиотеках типов.
Двойственные интерфейсы
Для компилируемых клиентов раннее связывание значительно эффективнее позднего. Однако, если приложения написаны на языке, поддерживающем связывание по виртуальной таблице, вызов метода Invoke может привести к увеличению накладных расходов. Эту проблему решают двойные интерфейсы (dual interfaces), обладающие характеристиками как интерфейсов на основе виртуальных таблиц, так и диспетчерских интерфейсов. Все они являются производными от IDispatch. Методы, определенные в двойном интерфейсе, входят в его виртуальную таблицу, поэтому клиенты, использующие связывание по виртуальной таблице, могут вызывать их напрямую. Кроме того, в двойных интерфейсах присутствует и функция Invoke, поэтому клиенты, использующие только раннее или позднее связывание, также смогут работать с ними. Метод Invoke для определения вызываемой функции по-прежнему использует идентификатор DISPID.
Фирма Microsoft настоятельно рекомендует предоставлять объекту двойственный интерфейс при использовании объекта в средствах автоматизации. Когда в объекте автоматизации реализованы оба интерфейса — IDispatch и VTBL — он называется компонентом с двойственным интерфейсом (dual-interface). В двойственном интерфейсе первые три пункта таблицы VTBL являются элементами (member) интерфейса IUnknown, следующие четыре - элементами IDispatch, а последующие пункты - адресами элементов двойственного интерфейса. На рис. показана таблица виртуальных функций объекта, поддерживающего двойственный интерфейс IInterfасе.