Автор Анна Евкова
Преподаватель который помогает студентам и школьникам в учёбе.

Технология СОМ (Создание и повторное применение объектов СОМ. Маршалинг)

Содержание:

Введение

Повторное применение - это путь к созданию лучшего программного обеспечения. Результат часто бывает очень хорошим, но мог бы быть и лучше. Создание новых приложений из существующих, протестированных компонентов, вероятно, должно приводить к более надежному коду. И - что не менее важно - оно может быть гораздо быстрее и дешевле.

Именно этот подход и реализуется в COM (Component Object Model). Объекты COM являются эффективным механизмом повторного применения программного обеспечения, так как создают дискретные, повторно используемые компоненты, которые могут играть роль, аналогичную той, что играют микросхемы, используемые проектировщиками аппаратуры.

Идея повторного применения является достаточно старой, но имелись технологические трудности. Наиболее распространенные схемы повторного применения: библиотеки и объекты.

Библиотеки, особенно динамические полезны, но не лишены недостатков. Один из них - сложность расширения функциональных возможностей, как установить новую версию динамической библиотеки, не повредив приложениям, ориентированным на старую и каков способ установки в системе более одной реализации одной и той же библиотеки. К тому же динамические библиотеки не поддерживают объектно-ориентированный подход.

Возможности объектов шире чем у библиотек. Но их повторное использование также проблематично. Первая и, вероятно, самая важная проблема заключается в том, что стандартов для компоновки двоичного кода объектов в единое целое фактически нет. Поэтому нужен исходный код. А распространять исходные коды на гипотетическом рынке, открывая тем самым изобретенные приемы реализации представляется невыгодным. Вторая проблема - несовместимость языков. Третья проблема - необходимость перекомпиляции всех приложений, использующих объект при его изменении.

Все эти проблемы решены в СОМ. Объекты СОМ можно собирать в динамические библиотеки или исполняемые файлы и распространять в двоичном коде (без исходных текстов). СОМ технология определяет стандартный доступ к этим объектам, обеспечивая тем самым независимость от языка программирования. При установке в системе новой версии объекта все его клиенты автоматически получают новый вариант при следующем обращении к этому объекту. Главный плюс СОМ - универсальный способ доступа ко всем типам программных компонентов. Цель данной работы изучить основы технологии СОМ, а так же ознакомиться с технологиями основанными на ней.

1. Понятие СОМ

Рассмотрим следующий вопрос: каким образом одна часть программного обеспечения может взаимодействовать с другой. Вообще, ответ зависит от того, что представляют собой эти части. Например приложение, скомпонованное с динамической библиотекой может вызывать функции этой библиотеки. Два локальных процесса могут взаимодействовать с помощью различных механизмов, таких как DDE, совместное использование общей памяти. Для использования приложением сервисов операционной системы обычно выполняются системные вызовы. Возможно взаимодействие приложений, выполняемых на разных машинах в сети.

Во всех этих случаях проблема общая - одна часть программного обеспечения должна получить доступ к сервисам, предоставляемым другой частью, но в каждом случае механизм доступа разный. Хотелось бы иметь один общий способ, который и предоставляет СОМ.

СОМ определяет стандартный механизм, с помощью которого одна часть программного обеспечения предоставляет свои сервисы другой.

В СОМ любая часть программного обеспечения реализует свои сервисы как один или несколько объектов СОМ. Каждый такой объект поддерживает один или несколько интерфейсов, состоящих из методов. Метод - это процедура шли функция, которая может быть вызвана клиентом объекта. Клиенты могут получить доступ к сервисам объекта СОМ только через вызовы методов интерфейсов объекта - у них нет непосредственного доступа к объекту.

Каждый поддерживаемый объектом интерфейс - это контракт между ним и его клиентом. Они обязуются: объект - поддерживать методы интерфейса в точном соответствии с определениями последнего, а клиент - корректно вызывать методы.

У каждого интерфейса СОМ есть два имени. Одно из них предназначено для людей - строка символов. Другое имя - предназначено для использования в основном программном обеспечении. Символьное имя не является уникальным, имя же используемое программами - уникально, что позволяет точно идентифицировать интерфейс. По соглашению читабельные имена большинства СОМ интерфейсов начинаются с буквы I (от interface). Читабельные имена удобны при упоминании интерфейса в разговоре или выборе имен переменных для указателей интерфейсов. Но они не уникальны и не могут однозначно определить какой именно интерфейс нужен.

В качестве уникального имени используется глобальный уникальный идентификатор (globally unique identifier - (GUID) GUID интерфейса называется идентификатором интерфейса (interface identifier - IID). GUID - это 16-байтовая величина, которая, обычно, генерируется программой-утилитой. При этом гарантируется ее уникальность. При генерации GUID программа использует уникальное значение, имеющееся на большинстве компьютеров - адрес платы сетевого интерфейса. Если в компьютере нет сетевой платы, то из различных случайных характеристик данной системы генерируется фиктивный идентификатор машины.

Единожды определенный интерфейс не может быть изменен. Если при разработке новой версии объекта понадобился интерфейс, похожий на предыдущий, но немного отличающийся, то создается новый интерфейс, имеющий новое имя и новый IID.

Интерфейсы могут наследовать друг другу, причем реализация не наследуется.

Объект и клиент должны иметь заранее согласованный способ описания интерфейсов (не использования, а именно описания для программистов). СОМ не предписывает, как это должно быть сделано. Но предлагает стандартный инструмент - язык описания интерфейсов (Interface Definition Language - IDL). Его синтаксис похож на синтаксис языка C++.

1.1. Реализация интерфейса

СОМ задает стандартный двоичный формат, который каждый СОМ объект должен поддерживать для каждого интерфейса. Наличие стандартного двоичного формата означает, что любой клиент может вызвать методы любого объекта не зависимо от языка программирования.

Для работы с объектом клиент должен иметь указатель на его интерфейс. Как он его получает будет рассмотрено далее. Клиентский указатель на интерфейс фактически является указателем на указатель внутри объекта, который в свою очередь указывает на таблицу указателей на методы - аналога таблицы виртуальных методов C++ (vtable). Что, вообще, означает, что СОМ объекты очень легко создавать на C++.

1.2. Классы

Всякий СОМ объект является экземпляром некоторого класса, и каждому классу может быть присвоен GUID - идентификатор класса (CLSID). При создании клиентом объекта СОМ он должен указать CLSID. Допустимо одновременное существование нескольких объектов одного класса.

В СОМ класс объектов идентифицирует некоторую реализацию группы интерфейсов. Возможно, что два разных класса реализуют одинаковые интерфейсы, но по-разному.

Связь между классом объекта и интерфейсами, которые он поддерживает, не очень прочна. Добавление к набору интерфейсов, поддерживаемых классом, нового не обязательно меняет класс.

Назначение CLSID - идентификация некоторого фрагмента кода, чтобы можно было загружать и активизировать объекты данного класса.

Существует возможность эмуляции классов. Пусть имеется новый класс, который реализует все те же интерфейсы, что и старый, но и некоторые еще, и он имеет свой CLSID. Допустим мы замешан старый класс на новый. Те приложения, которые использовали старый, могли бы использовать новый (с точки зрения поддерживаемых интерфейсов), но загружают они его по старому CLSID, поэтому необходимо организовать эмуляцию старого объекта новым.

1.3. Интерфейс IUnknown

Каждый объект СОМ должен поддерживать интерфейс IUnknown - в противном случае он не будет объектом СОМ. Все интерфейсы наследуют IUnknown, то есть с любым интерфейсом можно работать как с IUnknown, но есть и отдельный интерфейс IUnknown. На диаграммах IUnknown обычно изображается над объектом.

IUnknown содержит только три метода: Querylnterface, AddRef, Release.

Query-Interface. Обычно свой первый указатель на интерфейс объекта клиент получает при создании объекта (см. далее). Для того, чтобы получить указатели на другие интерфейсы используется метод Querylnterface интерфейса IUnknown или любого другого интерфейса. В качестве параметра методу Querylnterface передается IID требуемого интерфейса, Если объект поддерживает этот интерфейс, то будет возвращен указатель на него, иначе NULL.

Возможно Query Interface - самый важный элемент СОМ. Он позволяет заменять версии объектов СОМ так, что это не скажется на приложениях, использующих предыдущие версии объекта, которые помимо старых, реализуют и новые интерфейсы. А те приложения, которые знают о новых интерфейсах могут проанализировать, старая или новая версия СОМ объекта установлена и работать в соответствии с этим.

AddRef, Release - подсчет ссылок. Чтобы воспользоваться объектом СОМ, клиент должен явно инициировать начало работы экземпляра этого объекта (как, см. далее). А каким образом решается, когда завершается работа объекта? Кажущееся очевидным решение - возложить на клиент, запустивший объект на выполнение, обязанность сообщить объекту, когда он должен остановиться - неверно, так как данный клиент может оказаться не единственным, кто этот объект использует. Например, первый клиент может передать один из указателей на интерфейсы этого объекта другому клиенту, тот третьему и т.д.

Таким образом, разрешить клиенту уничтожать объект небезопасно. Только сам объект может решить, когда он может безопасно закончить свою работу, если все клиенты сообщили ему, что они закончили. г>то осуществляется с помощью механизма подсчета ссылок, реализуемого методами AddRef, Release интерфейса IUnknown.

Каждый исполняющийся объект поддерживает счетчик ссылок. Всякий раз, выдав вовне указатель на один из своих интерфейсов, объект увеличивает счетчик ссылок на 1. Если один клиент передает указатель интерфейса другому клиенту, т.е. увеличивает число пользователей объекта без его ведома, то клиент, получающий указатель, должен вызвать с помощью этого указателя метод AddRef. В результате объект увеличит свой счетчик ссылок. Независимо от того, как он получил указатель на интерфейс, клиент всегда обязан вызвать для этого указателя метод Release, закончив работу с ним. Исполнение этого метода объектом состоит в уменьшении числа ссылок на 1. Обычно объект уничтожает сам себя, когда счетчик ссылок становится равным 0.

Подсчет ссылок может вызвать проблемы, если не все клиенты следуют правилам, но это единственный способ управления временем жизни объектов.

1.4. Серверы объектов СОМ

Каждый объект СОМ реализован внутри некоторого сервера, содержащего код, который реализует методы интерфейсов объекта, а также контролирует данные объекта, пока тот активен. Один сервер может поддерживать более одного объекта некоторого класса и даже поддерживать несколько классов. Существует три типа серверов:

- сервер «в процессе» (in-process) или внутризадачный: объекты реализуются в динамической библиотеке и исполняются в том же процессе, что и клиент;

- локальный сервер: объекты реализованы в отдельном процессе, исполняющемся на той же машине, что и клиент;

- удаленный сервер: объекты реализованы либо в динамической библиотеке, либо в отдельном процессе, которые расположены на удаленном по отношению к клиенту' компьютере - возможность создания таких серверов поддерживает распределенная СОМ (DCOM).

С точки зрения клиента, объекты, реализованные любой из трех разновидностей серверов, выглядят одинаково.

При определении способа реализации компонентов следует учитывать, что внутризадачные серверы являются наиболее эффективными, поскольку они выполняются в адресном пространстве клиента и не требуется никаких дополнительных средств для передачи параметров методам клиента.

2. Создание и повторное применение объектов СОМ. Маршалинг

2.1. Создание объектов СОМ

Рассмотрим процесс, в результате которого клиент получит свой первый интерфейс на объект СОМ. Процесс этот состоит из двух частей: создание объекта и его инициализация.

В любой системе, поддерживающей СОМ обязательно имеется некоторая реализация библиотеки СОМ. Эта библиотека содержит функции, предоставляющие базовые сервисы объектам и их клиентам. В том числе, библиотека СОМ предоставляет клиентам способ запуска серверов объектов. Доступ к сервисам библиотеки СОМ осуществляется через вызовы обычных функций. Их имена обычно начинаются с Со - например CoCreateInstance.

Запрашивая создание объекта, клиент передает библиотеке СОМ идентификатор класса данного объекта, используя который, библиотека должна найти сервер этого класса. Понятно, что необходима некоторая таблица, отображающая CLSID в местоположение исполняемого кода сервера. В случае Windows эта таблица содержится в системном реестре. (Registry), другие реализации СОМ могут использовать другие схемы. Добавление записей к этой таблице обычно происходит при установке СОМ серверов.

Создание одного объекта. Для создания одного неинициализированного экземпляра объекта СОМ в локальном или внутризадачном сервере выполняются следующие действия (рис. 1):

  1. Клиент вызывает функцию библиотеки COM CoCreateInstance. Кроме других параметров, при вызове задаются CLSID объекта, который должен быть создан, а также IID некоторого интерфейса, поддерживаемого объектом.
  2. Библиотека COM no CLSID находит в системном реестре запись, соответствующую классу данного объекта. Эта запись содержит информацию о местоположении сервера, способного создать экземпляр класса объекта.
  3. Сервер запускается - это может быть внутризадачный или локальный сервер.
  4. Запущенный сервер создает экземпляр класса объекта и возвращает указатель на запрошенный интерфейс библиотеке СОМ.
  5. Библиотека СОМ передает указатель (6) клиенту.

В случае удаленного сервера процесс подобен, но вместо простого запуска сервера библиотека СОМ устанавливает связь с удаленной машиной, запрос выполняется вызовом удаленной процедуры. Просмотрев свой реестр удаленная система находит исполняемый файл сервера и создает экземпляр объекта. В случае, если объект реализован в динамической библиотеке, запускается суррогатный процесс. Далее также, как и в предыдущем случае возвращается указатель на интерфейс.

Таким образом, для клиента запуск объекта выполняется одинаково для всех трех типов серверов.

Рис. 1. Создание объекта с помощью CoCreateInstance

Фабрики классов. В сервере СОМ должны присутствовать средства, обеспечивающие создание экземпляров компонентов по запросу клиента. Для этого используется стандартный интерфейс ICIassFactory. Как и все СОМ-интерфейсы он наследует от IUnknown и, кроме этого, включает в себя два метода: CreateInstance и LockServer. Первый из них создает экземпляр класса (например с помощью оператора new), а второй обеспечивает блокировку программы-сервера в памяти.

Фабрика классов представляет собой СОМ объект, единственной задачей которого является создание других СОМ объектов одного класса. Всякое хранилище компонента, будь то исполняемый или DLL файл, должно обеспечивать реализацию фабрики классов для каждого класса, который может быть создан по внешним запросам.

Фабрика классов необходима для каждого компонента, а хранилище компонента должно обеспечить для библиотеки СОМ способ доступа к этой фабрике. В зависимости от варианта хранения используется одна из двух основных технологий доступа. DLL-файлы должны предоставлять в общее пользование две функции: DllGetClassObject и DllCanUnloadNow, а исполняемые файлы должны регистрировать свои фабрики классов с помощью функции CoRegisterClassObject из библиотеки СОМ.

При создании объектов с помощью фабрики классов выполняются следующие действия (рис. 2):

  1. Чтобы получить доступ к фабрике класса, клиент вызывает функцию библиотеки COM CoGetClassObject. Этой функции передается CLSID класса объектов, которые будет создавать фабрика и IID интерфейса, нужного ему для работы с фабрикой (обычно IClassFactory) а также тип сервера.
  2. Выполняется поиск и запуск СОМ сервера.
  3. Возвращается указатель на интерфейс IClassFactory созданной фабрики класса.
  4. Далее клиент может создать столько экземпляров класса, сколько необходимо, вызывая метод CreateInstance интерфейса IClassFactory.
  5. После этого следует освободить интерфейс IClassFactory, вызвав его метод Release.

Рис. 2. Создание объекта с помощью фабрики классов

Аналогичные действия происходят при вызове функции библиотеки СОМ CoCreateInstance при создании одного объекта:

CoGetClassObject(... ,&pCF);

рСF->CreateInstance(... ,&pint);

pCF->Release();

Иницшииюцин объектов COM. Фабрика классов создает абстрактный экземпляр данного класса. Его необходимо проинициализировать. Для этого его данные должны где-то храниться и наиболее очевидное место для этого - диск. Первый интерфейс, запрашиваемый клиентом при создании объекта обычно является одним из тех, которые позволяют проинициализировать объект, например IPersistFile, IPersistStorage, IPersistStrearn.

Пример реализации внутризадачного СОМ сервера. В примере будет реализован СОМ сервер для объектов одного класса, поддерживающего помимо интерфейса IUnknown единственный собственный интерфейс INewInterface. Язык реализации примера C++.

Заголовочный файл .h будет содержать:

1. Описание абстрактных классов для собственных интерфейсов:

class INewInterface : public IUnknown

{

public

virtual HRESULT _stdcall Method 1() = О; //абстрактный

}

2. Описание констант GUID

3. Описание класса, реализующего интерфейсы:

class NewClass : public INewInterface

{

protected

//счетчик ссылок

public

//конструктор, деструктор

NewClass();

~NewClass();

//виртуально перекрытые методы интерфейса IUnknown

//Querylntertace, AddRef, Release

//виртуально перекрытый метод собственных интерфейсов

virtual НRESULT Method 1 ();

}

4. Класс, реализующий фабрику классов:

class NewClassFactory

{

protected

//счетчик ссылок

public

//конструктор, деструктор

NewClassFactory();

~NewClassFactory();

//виртуально перекрытые методы интерфейса IUnknown

//Querylntertace, AddRef, Release

//виртуально перекрытые методы фабрики классов

//СreateInstance и LockServer

}

Файл .срр будет содержать:

1. Реализацию методов класса NewClass:

1.1. Конструктор

- увеличение внешнего счетчика объектов на I

- обнуление счетчика ссылок

1.2. Деструктор

- уменьшение внешнего счетчика объектов на 1

1.3. Querylntertace: проверка того, поддерживает ли класс запрашиваемый интерфейс, если да, то вызов AddRef. Возвращает указатель this, приведенный к типу указатель на нужный интерфейс (INewInterface *) this;

1.4. AddRef-увеличивает и возвращает счетчик ссылок;

1.5. Release - уменьшает счетчик ссылок. Если он равен 0, удаляет себя delete this.

1.6. Реализация методов собственных интерфейсов.

2. Реализацию методов класса NewClass:

2.1. Конструктор

- увеличение внешнего счетчика объектов на I

- обнуление счетчика ссылок

2.2. Деструктор

- уменьшение внешнего счетчика объектов на 1

2.3. Querylnterface: проверка того, поддерживает ли класс запрашиваемый интерфейс, если да, то вызов AddRef. Возвращает указатель this, приведенный к типу указатель на нужный интерфейс (INewInterface *) this;

2.4. AddRef - увеличивает и возвращает счетчик ссылок

2.5. Release - уменьшает счетчик ссылок. Если он равен 0, удаляет себя delete this.

2.6. Реализация метода CreateInstance интерфейса IClassFactory.

- создаем экземпляр класса, соответствующего классу фабрики

- вызываем его метод Query Interface

2.7. Реализация метода LockServer интерфейса IClassFactory:

- если передано значение «истина» - увеличиваем внешний счетчик обращений на I, иначе-уменьшаем.

3. Внешний счетчик объектов и внешний счетчик обращений (при начальном присваивании = 0)

4. Процедура динамической библиотеки

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)

{

если CLSID соответствует

- создается экземпляр фабрики классов,

- вызывается ее QueryInterface

}

5. Процедура динамической библиотеки STDAPI DllCanUnIoadNow(void)

{

если внешний счетчик объектов или внешний счетчик ссылок не равны 0, возвращает «ложь» - выгрузить нельзя, иначе «истину» - можно.

}

После компиляции можно зарегистрировать внутризадачный СОМ сервер в системном реестре и использовать соответствующие СОМ объекты, например, следующим образом:

CoInitializeO- инициализация библиотеки СОМ

CoGetClassObject(..,&pCF,..) - получили указатель на интерфейс IclassFactory (pCF)

pCF->CreateInstance(...) - создали экземпляр нужного объекта и получили нужный интерфейс

pCF->Release() - освободили фабрику классов //работаем

//освобождаем все интерфейсы

CoUnInitialize() - освобождаем библиотеку СОМ.

2.2. Повторное применение объектов СОМ.

Рассмотрим возможность повторного применения уже существующего кода в новых объектах СОМ.

В объектно-ориентированном программировании в качестве механизма повторного использования существующего кода применяется механизм наследования реализации. Но в СОМ механизма наследования реализации не предусмотрено, предусмотрено только наследование интерфейсов. Поэтому СОМ предоставляет два других механизма повторного применения: включение и агрегирование.

Под включением (containment), которое также называется делегированием (delegation), внешний объект выступает как обычный клиент внутреннего. Внешний объект вызывает методы внутреннего объекта для выполнения собственных функций. Однако эти методы остаются недоступными клиенту внешнего объекта непосредственно. Интерфейс внешнего объекта содержит методы, вызывающие методы внутреннего (рис. 3).

Реализация включения столь же проста, сколь проста реализация клиента СОМ и является по этому широко распространенным механизмом. Но оно не всегда является самым эффективным механизмом.

Рис. З. Включение

Предположим, объекту нужно предоставлять клиентам некоторый интерфейс, и уже имеется другой объект, который этот интерфейс реализует. Внешний объект мог бы реализовать все методы данного интерфейса так, чтобы они не делали ничего, кроме вызовов соответствующих методов внутреннего объекта, который, в свою очередь, возможно, переадресует вызовы к следующему объекту и т.д. что не эффективно. В этом случае используется механизм повторного применения, называемый агрегацией, (aggregation).

Механизм агрегации позволяет внешнему объекту представлять в качестве собственных интерфейсы, реализованные на самом деле внутренними объектами. Агрегирование повышает эффективность и, как и включение, не видимо для клиента. Но, в отличие от включения, агрегирование требует поддержки со стороны внутреннего объекта.

Имеются две проблемы:

- клиент может вызвать метод Querylnterface внутреннего объекта, чтобы получить указатель на один из интерфейсов внешнего, который об этом ничего не знает;

- клиент может вызвать метод AddRef внутреннего объекта, а внешний об этом ничего не узнает.

Решение этих проблем очевидно: любой внутренний объект должен делегировать вызовы методов своего IUnknown методам IUnknown внешнего объекта. Следовательно, внутреннему объекту надо как-то передать указатель на интерфейс IUnknown внешнего. Эгот указатель передается как параметр либо CoCreatelnstance, либо ICIassFactory::CreateInstance при создании агрегируемого объекта. Если соответствующий параметр NULL, то объект знает, что он не агрегируется, и будет обрабатывать все вызовы методов IUnknown самостоятельно. Если не NULL, новый объект будет функционировать только как агрегированный внутренний объект некоторого объекта и вызовы методов IUnknown внутреннего объекта будут делегироваться методам IUnknown внешнего объекта.

Рис.4. Агрегирование

2.3. Маршалинг (транспортировка)

Существует три вида СОМ серверов, но во всех случаях клиент выбывает методы их интерфейсов одинаков - напрямую. Рассмотрим, какие действия при этом происходят.

Простейший случай, когда сервер реализован в процессе. Указатель интерфейса в этом случае может указывать непосредственно на таблицу виртуальных методов. Вызов метода интерфейса клиентом представляет собой обычный вызов виртуального метода объекта. Вызовы СОМ-объекта, реализованного внутризадачным сервером столь же эффективны, как и вызовы функций C++ в том же процессе (рис.5 ).

Рис.5. Доступ к объекту СОМ, реализованному сервером «в процессе»

Случай реализации объекта СОМ в локальном сервере несколько сложнее. Так как клиентский интерфейс не может указывать непосредственно на интерфейс в объекте, он указывает на заместитель (proxy) внутри клиентского процесса. Заместитель - это просто другой COM-объект (в процессе - .DLL), обычно предоставляющий клиенту те же интерфейсы, что объект в локальном сервере. Вызов клиентом метода через такой указатель на интерфейс на самом деле вызывает исполнение кода заместителя. Заместитель принимает переданные клиентом параметры и упаковывает их для пересылки. Затем с помощью некоего средства коммуникации между процессами (зависит от ОС) передает запрос и его параметры в процесс, который фактически реализует объект.

По прибытии в процесс локального сервера запрос передается заглушке (stub). Заглушка распаковывает параметры и вызывает метод внутри объекта. По завершении выполнения метода результаты возвращаются в обратном направлении по тому же маршруту. С точки зрения клиента вызов аналогичен вызову внутризадачного сервера, но выполняется гораздо медленнее (рис. 6).

Рис.6. Доступ к объекту СОМ, реализованному в локальном сервере

Наиболее сложный вариант - когда реализован удаленный сервер. Архитектура поддержки удаленных серверов (DCOM) во многом похожа на используемую для серверов локальных. Клиент использует заместитель, а сервер - заглушку. Но вызов метода осуществляется через сеть с помощью механизма вызова удаленной процедуры (RPC - remote procedure call). С точки зрения клиента вызов аналогичен предыдущим, но выполняется еще более медленнее (рис. 7).

Рис.7. Доступ к объекту СОМ, реализованному в удаленном сервере

Параметры, пересылаемые между процессами или машинами должны быть упакованы перед пересылкой и распакованы по прибытии. Подобные упаковка и распаковка называются маршалингом (marshaling) и демаршалингом (unmarshaling). Код, знающий как эти операции выполнять, называется кодом маршалинга или маршалером (marshaler). Основная задача заместителей и заглушек - выполнение маршалинга и демаршалинга для конкретного интерфейса.

Имеются программы, которые автоматически позволяют генерировать код маршалинга, разбитый на заместителя и заглушку по описанию интерфейса на IDL, например MIDL (Microsoft IDL). Файлы, полученные с помощью MIDL, можно непосредственно использовать в программах на языках С и C++.

Заместители и заглушки, создаваемые автоматически, используют стандартный маршалинг. В случае необходимости, разработчики программ могут заменить стандартный маршалинг собственным, например для повышения быстродействия.

Разработка заместителей и заглушек производится на этапе разработки объекта и, таким образом, являются статическим вариантом построения маршалеров. Предполагается, что клиент уже знает, какие интерфейсы он будет вызывать. Если же клиент имеет возможности работать с интерфейсами, о существовании которых он узнал уже во время выполнения, то такой вариант связывания не подходит. Используют так называемое «позднее связывание» (late binding). Его идея состоит в том, чтобы позволить клиенту динамически конструировать вызовы вновь обнаруженных интерфейсов.

Как было сказано ранее, описание интерфейса на IDL достаточна для генерации кода заместителя и заглушки, следовательно ее можно использовать и для осуществления динамического маршалинга. Для этого по описанию интерфейса на IDL генерируется библиотека типа (type library) с помощью, например MIDL, которая регистрируется в системном реестре.

3. Технологии, основанные на СОМ

3.1. Автоматизация (automation)

Цель автоматизации - обеспечение программируемости приложений, путем доступа к их сервисам через СОМ интерфейсы. Это позволяет программно управлять любым приложениями из программы на любом языке программирования. Программы, управляющие приложениями, иногда называют сценариями (script).

Часто для создания программ, управляющих другими приложениями используются простые графические инструменты типа Visual Basic. Но при этом возникает проблема. СОМ интерфейсы реализуются с помощью виртуальной таблицы указателей на функции и для вызова метода клиент должен быть способен с ней работать, что может быть недоступно для подобных сред.

Для решения этой проблемы служит стандартный COM-интерфейс IDispatch. Большинство объектов автоматизации поддерживают IDispatch, хотя это и не является обязательным. Автоматизация означает программируемость вообще.

Диспинтерфейсы. Основным методом интерфейса IDispatch является метод Invoke. Через него клиент может вызвать любой метод из некоторой группы методов. Группу методов, которые могут вызываться через IDispatch::Invoke называют днспинтерфейсом. Каждому методу днспинтерфейса ставится в соответствие целое число - диспетчерский идентификатор (dispatch identifier - DISPID). Для вызова метода диспинтерфейса клиент вызывает IDispatch::Invoke, передавая DISPID нужного метода диспинтерфейса, который по DISPID вызывает нужный метод. Таким образом, любой программе, использующей методы диспинтерфейсов, необходимо уметь вызывать только IDispath::Invoke и нет необходимости уметь работать с виртуальной таблицей методов, это за них сделает метод Invoke.

Параметры методов диспинтерфейса должны передаваться при вызове метода Invoke, причем передаваться всегда однотипно. Клиент перед вызовом Invoke упаковывает параметры диспинтерфейса в вариант (variant). Вариант определяет стандартную форму для каждого параметра и идентификатор типа параметра (короткое целое, длинное целое, строка и т.п.). Этот вариант передается методу Invoke вместе с DISPID. Метод Invoke распаковывает вариант и вызывает нужный метод, передавая ему содержащиеся в варианте параметры. Возвращаемы методом результаты упаковываются методом Invoke, а распаковываются клиентом. Таким образом, заглушка и заместитель для IDispath любого диспинтерфейса всегда одни и те же, что позволяет выполнять позднее связывание. Для получения информации о методах диспинтерфейса можно использовать библиотеку типов, а при ее отсутствии - метод IDispatch:

GetlDsOfNames - возвращает DISPID по имени метода.

Методы IDispath::GetTypeInfo возвращает указатель на информацию о типе (Itypelnfo), а метод IDispatch::GetTypeInfoCount - возвращает информацию о том, поддерживает ли данный объект выдачу информации о типе в период выполнения.

Рис. 8. Диспинтерфейсы

Дуальные интерфейсы. Доступа к методам объекта только через диспинтерфейсы во многих случаях недостаточно. Если для клиентов на Visual Basic нужны диспинтерфейсы, то клиенты на С предпочитают виртуальные таблицы (из соображений быстродействия, типов параметров, которые можно паковать в вариант, и необходимости дополнительной работы при вызове методов через IDispatch). В этом случае используется дуальный интерфейс (dual interlace). Методы дуального интерфейса могут вызываться и через IDispatch и напрямую, через виртуальную таблицу.

Каждый дуальный интерфейс является потомков IDispatch. Он включает методы IDispatch и собственные методы интерфейса. Таким образом, эти методы доступны через виртуальную таблицу, и именно их вызывает IDispatch::Invoke.

Рис. 9. Дуальный интерфейс

3.2. Перманентность (persistent)

Объекты состоят из методов и данных, многим объектам необходимо сохранять свои данные в периоды неактивности, сохраняя их, обычно, на диске. Такие данные называют перманентными.

Так как СОМ обеспечивает совместную работу разных объектов в одной программе, то эти отдельные СОМ объекты должны быть способны сохранять информацию в одном общем файле, независимо от того какие именно и сколько объектов его создают. Один из наиболее распространенных путей достичь этого - структурированное хранилище.

Один файл структурированного хранилища состоит из хранилищ (storage) и потоков (stream). Реализацию файлов подобной структуры называют в Microsoft Windows составными файлами (compound file).

Каждый составной файл содержит корневое хранилище, в котором могут располагаться другие потоки и хранилища, в этих хранилищах в свою очередь потоки и хранилища - получается древовидная структура. Объектам, использующим структурированное хранилище, выделяются его потоки или хранилища.

Хранилища и потоки рассматриваются как COM-объекты, и доступ к каждому осуществляется через соответствующий интерфейс: хранилища - IStorage, потоки - IStream.

Обычно нет необходимости писать свою реализацию для этих интерфейсов - она предоставляется системой. Но при желании можно реализовать свою схему хранения.

Реализация структурированного хранилища включает функции:

- StgCreateDocFile - создает новый составной файл,

- StgOpenStorage - открывает существующий составной файл,

- StglsStorageFile - позволяет определить, является ли некоторый файл составным.

Первые две возвращают в качестве параметра указатель на интерфейс IStorage.

Структурированное хранилище - один из способов, каким клиент может организовать перманентное хранение данных всех его объектов. Рассмотрим, каким образом происходит взаимодействие клиента и объектов на предмет сохранения или загрузки данных. Для этого объект должен реализовать один из интерфейсов IPersist*, таких как:

- IPersistStream - загрузка и сохранение данных в потоке (в качестве параметров методов Save и Load передается указатель на IStream)

- IPersistStreamlnit - похож на предыдущий + возможность сообщить о том, что он инициализируется в первый раз

- IPersistStorage - загрузка и сохранение данных в хранилище (в качестве параметров методов Save и Load передается указатель на IStorage)

- IPersistFile - загрузка и сохранение данных в обычном файле (в качестве параметров методов Save и Load передается имя файла)

- IPersistPropertyBag - загрузка и сохранение данных как набора свойств

- IPersistMoniker - загрузка и сохранение данных с помощью моникера

IPersistMemory - загрузка и сохранение данных в у казанной области памяти.

Создав объект СОМ объект клиент обычно первым запрашивает его интерфейс из группы IPersist*, и вызывает его метод Load, передавая соответствующий параметр (например указатель на соответствующий поток IStream в предварительно открытом составном файле). После того, как объект загрузит свои данные, он готов к работе. Когда клиенту необходимо сохранить все данные на диске, например перед выгрузкой, он вызывает метод Save интерфейсов IPersist* всех своих объектов, заставляя тем самым объекты сохранить свои данные там, где у казано.

3.3. Моникеры (moniker)

Моникер представляет собой COM-объект специфического назначения - он знает как создавать и инициализировать экземпляр другого объекта. Вообще, моникеры в среде СОМ не необходимы, но полезны. Создать и проинициализировать COM-объект клиент может, вызвав CoCreatelnstance, а затем метод Load одного из интерфейсов IPersist* для созданного экземпляра объекта. Но проще это сделать с помощью моникера.

Моникер идентифицирует конкретный объект. Он является COM-объектом, который поддерживает интерфейс IMoniker и имеет собственные перманентные данные, которые достаточны, чтобы создать и проинициализировать экземпляр объекта.

Если клиент имеет указатель на IMoniker, то он может получить уже проинициализированный конкретными данными объект, вызвав метод BindToObject этого интерфейса. В качестве параметра передается IID задающего требуемый интерфейс целевого объекта. Этот метод выполнит все обычные действия по созданию и инициализации объекта. Понятно, что перед тем, как использовать методы моникера, клиент должен его создать и проинициализировать. Если эти действия не проще создания и инициализации нужного объекта, то моникер не даст никакого выигрыша. Но иногда использовать его удобно.

Например, при организации составного документа, данные внедренных объектов можно хранить в том же файле или в другом файле - связывание. Тогда файл контейнера должен содержать ссылку на связанный файл, в качестве которой и можно использовать моникер, точнее его перманентные данные. Это позволит контейнеру однотипно работать со связанными документами, создаваемыми любыми приложениями.

Определены различные стандартные классы моникеров: файловые моникеры, моникеры элементов, композитные моникеры и др. Наиболее интересны композитные моникеры, которые содержат указатели на моникеры элементов. Реализации интерфейса IMoniker для стандартных классов предоставляются системой. Разрабатывать их надо только для нестандартных классов.

Для создания экземпляра моникера можно использовать CoCreateInstance с соответствующим CLSID, но имеются и специальные системные функции, типа CreateFileMoniker, CreateltemMoniker. Первой из них в качестве параметра передается полное имя файла, а возвращается в качестве параметра указатель на интерфейс IMoniker. Класс экземпляра объекта, который должен быть создан, файловый моникер может определить по расширению файла.

3.4. Единообразная передача данных

Обмен данными - фундаментальная операция в программировании, поэтому имеется множество схем. Стандартный способ обмена информацией в СОМ - единообразная передача данных (Uniform Data Transfer). Основополагающий интерфейс для единообразной передачи данных - IDataObject.

Любой COM-объект, реализующий IDataObject рассматривается как объект данных (data object). Данные всех таких объектов принимаются и передаются ими по одной стандартной схеме. IDataObject служит для создания одного стандартного интерфейса с одним набором методов, который позволял бы получить доступ к любым данным объекта.

При передаче данных должны использоваться стандартные форматы представления данных. Для описания данных, передаваемых методами интерфейса IDataObject используется структура FORMATETC (от format, etc - формат и прочие).

Наиболее важные методы интерфейса IDataObject:

- GetData - передает данные от объекта его клиенту. Клиент, вызывая этот метод передает структуру FORMATETC, где указывает формат, в котором желает получить данные. Объект возвращает ссылку на данные в параметре типа STGMEDIUM;

- SetData - передает объект)' данные от его клиента, параметры те же, что и в GetData,

- QueryGetData - позволяет клиенту запросить объект данных, поддерживает ли тот пересылку конкретного типа данных;

- EnumFormalEtc - возвращает описание форматов данных (в виде структур FORMATETC), поддерживаемых данным объектом;

- DAdvise - устанавливает связь между объектом данных и объектом приемника уведомлений (advise sink object);

- DUnadvise - разрывает связь, установленную DAdvise.

Необходимо, чтобы объект данных оповещал клиента об изменениях в своих данных. Кроме этого нужно, чтобы клиент как-то сообщил объекту данных, какие именно данные ему нужны. Интерфейса IDataObject для этого не достаточно.

Стандартного интерфейса, через который клиент сообщил бы объекту, об изменениях каких данных он должен ему сообщать нет. Возможно он и не нужен, если это известно заранее. Если нужен, то разработчик объекта данных создает его специально.

Объект данных для оповещения клиента об изменениях в своих данных посылает ему уведомления. Клиент должен реализовать интерфейс, позволяющий источнику данных уведомлять его при их изменениях. Это интерфейс IAdviseSink. Т.е. клиент должен содержать объект, реализующий IAdviseSink и передать его адрес объекту данных с помощью метода IDataObject::DAdvise. После этого объект данных при изменении своих данных выбывает метод IAdviseSink::OnDataChange, параметрами которого являются структура FORMATETC, описывающая формат изменившихся данных и структура STGMEDIUM, указывающая на местонахождение данных (рис. 10). Когда клиенту требуется прекратить получение уведомлений, он вызывает IDataObject::DUnadvise.

Рис. 10. Единообразная передача данных

3.5. Объекты с подключением

Иногда объекту необходимо вызывать методы его клиентов. Технология объектов с подключением (Connectable Objects) обеспечивает более общий механизм обратной связи объекта с клиентом, чем предоставляемый IAdviseSink.

Чтобы обращаться к своему клиенту объект должен поддерживать один или несколько исходящих интерфейсов (outgoing interface). Поддержка исходящего интерфейса означает, что объект может работать с этим интерфейсом в качестве клиента.

Чтобы считаться объектом с подключением, объект должен поддерживать (как входящий) интерфейс IConnectionPointContainer. Через него клиенты объекта могут узнать, какие исходящие интерфейсы поддерживает этот объект. Каждый из этих интерфейсов представляется внутри объекта как отдельный объект точки связи (connection point object). Каждая точка связи отвечает только за один тип исходящего интерфейса и поддерживает (как входящий) интерфейс IConnectionPoint.

Клиент объекта с подключением, в свою очередь, должен реализовать те интерфейсы, которые для объекта с подключением является исходящим. Объекты в клиенте, которые реализуют эти интерфейсы, называются приемниками (sink).

Интерфейс IConnectionPointContainer имеет всего два специальных метода:

- FindConnectionPoint - позволяет клиенту узнать, поддерживает ли объект с подключением конкретный исходящий интерфейс. В качестве параметра передается IID, если интерфейс поддерживается, то возвращается указатель на интерфейс IConnectionPoint соответствующего объекта точки связи.*

- EnumConnectionPoint - позволяет клиенту получить список всех указателей на интерфейсы IConnectionPoint всех точек связи. Просматривая этот список, клиент может выяснить IID любого поддерживаемого объектом исходящего интерфейса с помощью метода интерфейса IConnectionPoint.

Интерфейс IConnectionPoint служит для установления и разрыва связи с объектом с подключением. В него входят методы:

- Advise - устанавливает связь, получает указатель на интерфейс, реализованный объектом-приемником, возвращает идентификатор соединения;

- Unadvise - разрывает связь;

- EnumConnection - возвращает список всех установленных связей;

- GetConnectionPointContainer - возвращает указатель на интерфейс

- IConnectionPointContainer объекта с подключением, которому принадлежит точка связи;

- GetConnectionlnterface - возвращает IID исходящего интерфейса, поддерживаемого данной точкой связи. Именно с помощью него клиент может, получив список указателей на интерфейсы IConnectionPoint от метода IConnectionPointContainer::EnumConnectionPoint, узнать, какие исходящие интерфейсы поддерживаются.

Таким образом, процесс установления связи следующий: клиент, имея указатель на интерфейс IConnectionPointContainer, получает указатель на интерфейс IConnectionPoint нужного объекта точки связи и устанавливает связь, вызывая его метод Advise, передавая ему в качестве параметра указатель на соответствующий интерфейс своего объекта- приемника. На этот интерфейс объект с подключением и будет посылать сообщения - вызывать его методы (рис. 11).

Рис. 11. Объекты с подключением

3.6. Составные OLE-документы

Составной документ включает в себя части, созданные различными приложениями. Технология OLE обеспечивает создание таких приложений. Исторически, именно из необходимости создания составных документов и развились все технологии, называемы в настоящий момент СОМ.

Технология составных OLE-документов использует много других СОМ- ориентированных технологий, включая структурированное хранилище, моникеры и единообразную передачу данных.

При создании составного документа с помощью OLE одно из приложений всегда является контейнером. Другие приложения - серверы - могут размещать свои документы внутри документа-контейнера. Документ сервера может быть либо связан, либо внедрен в документ контейнера. Внедренный документ хранится в том же файле (структурированное хранилище), связанный - в отдельном файле, а в документе контейнера хранится лишь связь с этим файлом (моникер)

Как контейнеры, так и серверы должны поддерживать определенные интерфейсы. Причем все эти интерфейсы носят абсолютно общий характер, не зависящий от того - что это за приложение. Для контейнера не важен тип сервера, для сервера - тип контейнера.

3.7. Управляющие элементы ActiveX

Управляющий элемент ActiveX - независимый программный компонент, выполняющий специфические задачи стандартным способом. Эти компоненты, не являясь независимыми приложениями, могут использоваться при разработке любых приложений. Такие элементы можно подключать к приложениям, создаваемым с помощью систем разработки Visual Basic, Visual C++, Delphi, Visual J++. Также их можно непосредственно вставлять в гипертекстовые HTML страницы или использовать в некоторых программах. Элементы управления ActiveX не могут реализовать свои функции без приложения-контейнера.

Элементы управления ActiveX обладают свойствами, методами и событиями.

События возникают внутри элемента управления, а обрабатываются вовне - подпрограммами контейнера.

Контейнеры элементов ActiveX имеют два основных режима работы: режим проектирования и режим выполнения. В режиме проектирования контейнер создает экземпляр элемента управления и дает пользователю возможность задавать значения его свойств. Методы в этом режиме недоступны. В режиме выполнения создается экземпляр элемента управления, устанавливаются все значения его свойств и становятся доступными его функции.

Элементы управления ActiveX представляют собой внутризадачные СОМ-серверы, т.е. они являются dll-файлами, реализующими наборы СОМ-интерфейсов.

Для того, чтобы COM-объект мог рассматриваться как элемент управления ActiveX он должен:

- поддерживать IUnknown,

- обеспечивать саморегистрацию - т.е. занесения записи о себе в системный реестр.

Кроме этого, он может:

- отображать собственный пользовательский интерфейс;

- посылать сообщения контейнеру управляющих элементов;

- позволять контейнеру изменять значения своих свойств.

Причем делать это он должен в соответствии со спецификацией - используя стандартный СОМ-интерфейсы определенным образом, что гарантирует универсальность его применения.

Для того, чтобы контейнер мог определить возможности и требования управляющего элемента используются категории компонентов (component categories), которые помещаются в системный реестр и задаются соответствующим GUID, называемым идентификатор категорий (category identifier - CATID). Категории компонентов могут использоваться для обозначения любого сочетания функциональных возможностей и требований к контейнеру.

Все СОМ-интерфейсы, поддерживаемые элементом управления и контейнером можно разделить на следующие группы:

- обеспечивающие пользовательский интерфейс;

- обеспечивающие выполнение методов - обычно интерфейс элемента управления IDispatch или дуальный интерфейс;

- обеспечивающие посылку элементом управления событий своему контейнеру;

- обеспечивающие работу со свойствами элемента управления (IPerstst* и др.).

3.8. Интерфейсы сервисов на основе СОМ

Часто удобно получать доступ к разным реализациям некоторого сервиса через общий интерфейс. Microsoft определила несколько таких интерфейсов, в том числе интерфейсы баз данных, обработки транзакций и службы каталогов.

СУБД можно рассматривать как набор сервисов, которые она предоставляет другим программам. Эти сервисы можно рассматривать как СОМ-объекты. Аналогично можно организовать доступ к данным, хранящимся, например, в электронных таблицах.

Технология баз данных на основе COM (OLE Database или OLE DB) определяет стандартные объекты и интерфейсы СОМ для доступа к данным. Эта технология предоставляет всем клиентам универсальное средство доступа к данным, хранящимся в различной форме.

3.9. Определение стандартных интерфейсов с помощью СОМ

Microsoft финансирует программу разработки промышленных стандартов ннтрефейсов - OLE Industry Solutions (промышленные решения на основе OLE). В рамках этой программы группы финансовых компаний, организаций здравоохранения, поставщиков оборудования для торговых точек и др. определили стандартные интерфейсы компонентов в соответствующих областях.

3.10. СОМ и технологии Интернет

Компонентный подход находит различные применения в технологиях Microsoft для Интернет. Например, Internet Explorer, использует расширение технологии составных документов OLE - документы ActiveX. Сценарии ActiveX (ActiveX Scripting) - универсальный способ исполнения клиентами сценариев, написанных на любом языке. Технология Гиперсвязей ActiveX (ActiveX Hyperlinks), в основе которой лежат моникеры, обеспечивает создание гиперсвязей WWW не только между страницами HTML, но и между любыми типами документов.

Заключение

СОМ и связанные с ним технологии проникли в самые основы Windows систем, чья популярность не снижается. Знание технологий, основанных на СОМ, совершенно необходимо для понимания программного обеспечения в ОС Microsoft. В ходе выполнения курсовой работы были изучены основы технологии СОМ, а так же были рассмотрены технологии основанные на ней.

Библиография

  1. Бокс Д. Сущность технологии СОМ. Библиотека программиста. / Д. Бокс. - СПб.: Питер, 2001. - 400 с.
  2. Деннинг А. ActiveX для профессионалов / А. Деннинг. – СПб.: Питер, 2002. – 404 с.
  3. Елманова Н. Delphi и технология COM. Мастер-класс / Н. Елманова, С. Трепалин, А. Тенцер. – СПб.: Питер, 2003. – 698 с.
  4. Михайлов А.В. 1С: Предприятие 7.7/8.0: системное программирование / А.В. Михайлов. – СПб.: БХВ-Петербург, 2005. – 336 с.
  5. Мюллер Д. Технология COM+ / Д. Мюллер. - СПб.: Питер, 2002. - 464с
  6. Оберг Р.Д. Технология СОМ+. Основы и программирование / Р.Д. Оберг. – М.: Вильямс, 2000. – 480 с.
  7. Роджерсон Д. Основы СОМ / Д. Роджерсон. – М.: Русская Редакция, 2000. – 228 с.
  8. Трельсен Э. Модель СОМ и применение ATL 3.0 / Э. Трельсен. – СПб.: БХВ-Петербург, 2001. – 928 с.
  9. Хармон Э. Разработка COM-приложений в среде Delphi / Э. Хармон. – М.: Вильямс, 2000. – 464 с.
  10. Чеппел Д. Технологии ActiveX и OLE / Д. Чеппел. – М.: Русская Редакция, 1997. – 320 с.