Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

Типы интерфейсов

Интерфейс 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асе.