Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2920

.pdf
Скачиваний:
1
Добавлен:
15.11.2022
Размер:
2.61 Mб
Скачать

("{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} ",

где X – шестнадцатеричная цифра), то CLSID можно получить, вызвав функцию CLSIDFromString. Для случая с ProgID информация о CoClass’е должна содержаться в реестре машины, на которой производится вызов функции. В реестр информация заносится автоматически при регистрации объекта (во время процедуры инсталляции или при компиляции).

Перевести CLSID, IID или любой другой GUID в строку можно с помощью функции StringFromGUID2. Как уже говорилось выше, практически все необходимые GUID генерируются автоматически, но при необходимости можно сгенерировать GUID вручную, с помощью утилиты guidgen.

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

2.3.2. СОМ-интерфейс

COM проводит фундаментальное различие между определением интерфейса и его реализацией.

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

Реализация интерфейса (interface implementation) – это код, который программист создает для выполнения действий, оговоренных в определении интерфейса. Реализации интерфейсов, помещенные в COM-библиотеки или exe-

91

модули, могут использоваться при создании объектноориентированных приложений. Разумеется, программист может игнорировать эти реализации и создать собственные. Интерфейсы ассоциируются с CoClass’ами. Чтобы воспользоваться реализацией функциональности интерфейса, нужно создать экземпляр объекта соответствующего класса, и запросить у этого объекта ссылку на соответствующий интерфейс.

Например, для описания взаимодействия с некоторым абстрактным стеком можно определить интерфейс IStack (в COM стало доброй традицией начинать названия интерфейсов с «I»). Этот интерфейс может содержать два метода, скажем, Push и Pop. Вызов метода Pop возвращает значения, заложенные до этого методом Push в стек, но в обратном порядке. Это определение интерфейса не говорит, как функции будут реализованы в коде. Один программист может реализовать стек как массив, а методы Push и Pop – как методы доступа к этому массиву. Другому же взбредет в голову использовать связанный список и соответствующую реализацию методов. Независимо от конкретной реализации методов, представление в памяти указателя на интерфейс IStack, и, соответственно, его использование клиентом полностью специфицируется определением интерфейса.

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

Слово «интерфейс» используется в COM не в том смысле, что в С++. Интерфейс в С++ ссылается на все функции, поддерживаемые классом. COM-интерфейс ссылается на предварительно оговоренную группу связанных функций, реализуемых COM-классом, но не обязательно на ВСЕ функции, поддерживаемые классом.

92

2.3.3. MIDL

Для описания COM-интерфейсов используется MIDL (MS Interface Definition Language). При создании COM-

объектов на C++ использование MIDL является стандартной практикой. Некоторые среды программирования (Delphi, VB) обходятся без MIDL. Delphi имеет свой, Паскале-подобный синтаксис, а в VB любой класс априори является COMобъектом и дополнительное описание не требуется. MIDL является развитием OSF DCE IDL и имеет обратную совместимость с ним.

Как видно из примера, MIDL-описание очень похоже на C++. Главные отличия MIDL от C++ в том, что MIDL позволяет задать только описание интерфейса, и в том, что MIDL содержит дополнительные атрибуты, помещаемые в квадратные скобки. Самым главным атрибутом интерфейса является uuid. Он задает IID интерфейса.

Ниже приведен пример описания интерфейса на MIDL.

[

uuid(F3792A83-69C9-11D2-AC8C-525400DDA17A), helpstring("Этот интерфейс определяет методы работы со стеком.")

]

interface IStack : IUnknown

{

HRESULT Push([in] VARIANT Val); HRESULT Pop([out, retval] VARIANT *pVal);

}

Экземпляр «реализации интерфейса» на самом деле является указателем на массив указателей на методы (таблицу функций, ссылающуюся на реализации всех методов, определенных в интерфейсе, также называемую виртуальной таблицей – VTbl). Объекты с несколькими интерфейсами могут предоставлять указатели на несколько таблиц функций.

93

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

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

Если компилятор позволяет (что типично для С/С++), клиент может вызвать метод интерфейса по имени, а не по позиции в массиве. Поскольку интерфейс – это тип, зная имена методов, компилятор может проверить типы параметров и возвращаемого значения каждого из методов интерфейса. Напротив, такая проверка типов недоступна даже в С/С++, если клиент использует схему вызова по позиции. К счастью, все современные средства разработки (C++, VB, Delphi, Java,

...) позволяют вызывать методы интерфейсов по имени. Вызов методов интерфейсов по позиции может понадобиться только при формировании COM-вызова, что называется, на лету. Естественно, в повседневной работе прикладного программиста такое практически не случается. На практике для реализации динамического связывания используется специальный интерфейс IDispatch, но о нем речь пойдет позже (в разделе «Динамическое связывание»).

Интерфейс имеет свое имя. Как мы уже говорили, каждый интерфейс – это неизменяемый контракт, во время исполнения определяемый по глобально уникальному идентификатору интерфейса, IID. IID, это особая разновидность GUID, позволяет клиентам точно и без ненужных сложностей, узнать у объекта, поддерживает ли он некоторый интерфейс. Идентификация интерфейсов по IID решает проблему неуникальности имен интерфейсов. Это

94

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

Интересной задачей является определение того, указывают ли два указателя на интерфейсы разных объектов или одного и того же. Сделать это очень просто. Надо запросить от каждого из них указатель на IUnknown и просто сравнить их. Если они равны, значит, они получены от одного и того же объекта. Это верно даже в случае, если указатели получались через дюжину компьютеров, а сам объект находится на другой стороне земного шара. Не меняет этого правила даже то, что объект был получен агрегированием и на самом деле создан из нескольких других. Главное, чтобы указатели на интерфейс были получены легальными путями.

2.3.4. Наследование интерфейсов

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

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

Наследование в стандартных COM-интерфейсах используется довольно скупо. Все интерфейсы должны быть унаследованы от интерфейса IUnknown. IUnknown содержит три жизненно важных для COM метода: QueryInterface, AddRef

иRelease. Все COM-объекты должны реализовать интерфейс IUnknown, поскольку он обеспечивает возможность получать

95

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

AddRef и Release.

Высокоуровневые средства разработки (такие, как VB) полностью скрывают реализацию IUnknown, облегчая тем самым работу программиста. Низкоуровневые средства разработки (например, VC++) напротив, дают полный доступ к исходным текстам, реализующим IUnknown. На C++ или Delphi можно самостоятельно реализовать IUnknown. Но проще воспользоваться стандартными реализациями. Для C++ самый удобный, но в тоже время гибкий и очень компактный способ реализации IUnknown – воспользоваться библиотекой

ATL (Active Template Library). ATL – это библиотека, главным образом состоящая из набора шаблонов C++. ATL упрощает работу с COM, предоставляя реализации для многих стандартных интерфейсов. Эта библиотека входит в поставку

MS VC++ и Borland C++ Builder. В сочетании с визуальными средствами разработки этих сред ATL позволяет очень быстро создавать сложные приложения, базирующиеся на COM.

Чтобы воспользоваться функциональностью COMобъекта, нужно создать его экземпляр (instance).

Экземпляр COM-объекта аналогичен объекту в C++ или VB, за тем исключением, что он может быть создан в другом процессе или на другом компьютере.

Для создания экземпляра COM-объекта на C++

применяются функции CoCreateInstance, CoCreateInstanceEx, CoGetObject, CoGetClassObject, CoGetInstanceFromFile, CoGetInstanceFromIStorage, OleLoadFromStream и некоторые другие, всех и не упомнишь. На VB все немного проще. Там есть две универсальные функции, CreateObject и GetObject, и оператор new. Все объекты VB являются COM-объектами.

Чаще всего применяются функции CoCreateInstance, CoCreateInstanceEx, CreateObject и оператор new .

Чтобы создать экземпляр объекта с помощью CoCreateInstance, этой функции необходимо передать: CLSID

96

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

Контекст создаваемого объекта – это информация о том, где должен создаваться объект: в том же процессе (адресном пространстве EXE-модуля), в другом процессе того же компьютера, на удаленном сервере, или как локальная заглушка для удаленного объекта.

У этой функции есть еще один параметр, pUnkOuter, предназначенный для агрегации объектов, но об этом речь пойдет в разделе "Агрегация".

Функция CoCreateInstance была создана на заре существования COM-модели и предназначена для локального COM. Она не позволяет создавать объекты на конкретном удаленном сервере, вместо этого она берет информацию о сервере из реестра. Информацию о местонахождении сервера и атрибуты защиты можно настроить с помощью утилиты DCOMCNFG или средств администрирования MTS или COM+. К недостаткам CoCreateInstance можно отнести также то, что при ее использовании нет возможности задать учетную запись (и пароль), от имени которой будет создаваться объект, и то, что она позволяет получить указатель только на один интерфейс создаваемого объекта. Для локально создаваемого объекта эти ограничения не существенны – атрибуты защиты при этом не работают (имеется прямой доступ к объекту), а указатель на другой интерфейс можно молниеносно получить с помощью вызова метода IUnknown::QueryInterface (ведь по принципам COM любой COM-интерфейс должен быть унаследован от IUnknown). Вызов же QueryInterface у

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

CoCreateInstanceEx.

Остальные функции позволяют создать объект, загрузив его состояние из разных источников (OleLoadFromStream,

97

CoGetInstanceFromFile, CoGetInstanceFromIStorage), или создать объект с помощью моникера и строки параметра

(CoGetObject).

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

Создание моникера – задача сложная, и по плечу она не каждому. Спасает то, что имеется много стандартных реализаций моникеров. Так, через них делается связывание документов в OLE, создание Queued-объектов (асинхронно работающих объектов повышенной надежности, о них речь пойдет дальше), и получение указателей на WMI-объекты,

интерфейс новой технологии Windows 2000 – Active Directory.

Существует интересная разновидность моникеров – URLмоникеры, которые позволяют получить указатель на объект, основываясь на URL.

2.3.5. Инициализация и сохранение объектов

COM-объекты не имеют конструкторов, привычных для объектно-ориентированных языков программирования. Вместо этого можно инициализировать объекты (или загружать в них заранее сохраненное состояние) с помощью стандартных IPersistXXX COM-интерфейсов. Вот список этих интерфейсов: IPersistStream, IPersistStreamInit, IPersistStorage, IPersistFile, IPersistMemory, IPersistPropertyBag. Всех их объединяет то, что они унаследованы от интерфейса IPersist, а назначение каждого понятно из его названия. Хотелось бы обратить внимание на два интерфейса – IPersistStreamInit и IPersistPropertyBag. Первый имеет метод InitNew,

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

98

встроенных в html-странички и в VB-формы. Эти интерфейсы можно использовать и для сохранения/загрузки объектов в БД на удаленном сервере или для передачи COM-объектов через параметры методов удаленных объектов по значению.

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

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

Здесь есть одна тонкость. Как известно, новый процесс создается только при загрузке EXE-модуля, а DLL грузится в адресное пространство того процесса, в котором требуется его функциональность. Значит, компоненты, помещенные в DLLмодули, не могут создаваться удаленно. Это именно так, но не все так плохо... Если создать COM-объект в одном процессе, то ничто не мешает передать указатель на его интерфейс в другой процесс. Это достигается реализацией одного COMобъекта как EXE-сервера. У этого объекта создается метод, который получает на вход CLSID объекта (объект должен быть зарегистрирован на той же машине, где создан первый COMобъект) и IID интерфейса (который изначально требуется от

99

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

Именно это и сделано в MTS и COM+, причем так искусно, что создание объекта таким образом ничем не отличается от создания объектов традиционным для DCOM образом.

Более того, MTS и, особенно, COM+ предоставляют множество дополнительных функций и упрощают создание и внедрение распределенных приложений.

100

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]