Глава 26
Основы СОМ
Со времени зарождения программирования одной из актуальнейших задач, стоящих перед разработчиками программного обеспечения, было обеспечение взаимодействия между отдельными программами. Для ее решения используется целый арсенал различных способов и приемов.
На заре существования Windows были внедрены разделяемые файлы, буфер обмена и технология динамического обмена данными (Dynamic Data Exchange, DDE).
Для обеспечения обмена данными и предоставления служб был разработан первый вариант технологии связывания и внедрения объектов (Object Linking and Embedding — OLE 1). Эта технология предназначалась для создания составных документов — того, к чему мы все уже давно привыкли. Она была во многом несовершенной, и на смену ей пришла технология OLE 2, которая предоставляет способ решения более общей проблемы — как научить разные программы предоставлять друг другу собственные функции (службы) и как научить их правильно использовать эти функции.
Для решения этой проблемы помимо OLE был разработан целый ряд технологий. В основе этих разработок лежит базовая технология Component Object Model (СОМ) — Многокомпонентная Модель Объектов. Она описывает способ взаимодействия программ любого типа. Одна часть программного обеспечения предоставляет для использования собственные службы, а другая получает к ним доступ. При этом совершенно не важно, где расположены эти части — в одном процессе, в разных процессах на одном компьютере или на разных компьютерах.
Для созданных на основе спецификации СОМ приложений также не важно, какой язык программирования использовался при их разработке — если стандарт СОМ соблюден, взаимодействие осуществляется без помех.
Дополнительные возможности разработчикам распределенных приложений на основе СОМ дает модификация базовой технологии — распределенная модель СОМ (Distributed СОМ, DCOM).
В настоящее время СОМ используется в самых различных областях разработки программного обеспечения. На основе СОМ разработаны технологии Автоматизации (Automation), ActiveX, Active Form Microsoft Transaction Server.
Delphi 5 предоставляет разработчику набор инструментов для создания полноценных приложений СОМ. Далее в этой главе рассматриваются основные части спецификации СОМ и методика создания объектов и интерфейсов СОМ. Значительное внимание уделяется Редактору библиотеки типов — основному инструменту, который облегчает работу с объектами СОМ в проекте.
В этой главе рассматриваются следующие вопросы:
Базовые понятия
В технологии СОМ приложение предоставляет для использования свои службы, применяя для этого объекты СОМ. Одно приложение содержит как минимум один объект. Каждый объект имеет один или несколько интерфейсов. Каждый интерфейс объединяет методы объекта, которые обеспечивают доступ к свойствам (данным) и выполнение операций. Обычно в интерфейсе объединяются все методы, выполняющие операции одного типа или работающие с однородными свойствами.
Клиент получает доступ к службам объекта только через интерфейс и его методы. Этот механизм является ключевым. Клиенту достаточно знать несколько базовых интерфейсов, чтобы получить исчерпывающую информацию о составе свойств и методов объекта. Поэтому любой клиент может работать с любым объектом, независимо от их среды разработки. Согласно спецификации СОМ, уже созданный интерфейс не может быть изменен ни при каких обстоятельствах. Это гарантирует постоянную работоспособность приложений на основе СОМ, невзирая на любые модернизации.
Объект всегда работает в составе сервера СОМ. Сервер может быть динамической библиотекой или исполняемым файлом. Объект может иметь собственные свойства и методы или использовать данные и службы сервера.
Для доступа к методам объекта клиент должен получить указатель на соответствующий интерфейс. Для каждого интерфейса существует собственный указатель. После этого клиент может использовать службы объекта, просто вызывая его методы. Доступ к свойствам объектов осуществляется только через его методы.
Предположим, что объект СОМ встроен в электронную таблицу и обеспечивает доступ к математическим операциям. Будет логично разделить математические функции на группы по типам и создать для каждой группы собственный интерфейс. Например, можно выделить линейные, тригонометрические, агрегатные функции и т. д. На рис. 26.1 объект расположен внутри сервера —
электронной таблицы. Интерфейсы обозначены маленькими кружками, связанными с объектом. Пусть интерфейс линейных функций называется ILinear, а интерфейс агрегатных функций — IAggregate.
Рис. 26.1. Сервер, объект и его интерфейсы
Согласно правилам обозначения объектов СОМ, базовый интерфейс lUnknown (см. ниже), который имеется у любого объекта, обозначается как кружок, примыкающий к верхней стороне прямоугольника объекта. Остальные интерфейсы обозначаются справа или слева.
Рис. 26.2. Схема взаимодействия клиента и объекта СОМ
На рис. 26.2 представлена схема взаимодействия клиента с объектом СОМ. Чтобы получить доступ к агрегатной функции расчета среднего, клиент должен получить указатель на интерфейс IAggregate, а затем обратиться к этой функции.
Взаимодействие между клиентом и объектом обеспечивается базовыми механизмами СОМ. При этом от клиента скрыто, где именно расположен объект: в адресном пространстве того же процесса, в'другом процессе или на другом компьютере. Поэтому с точки зрения разработчика клиентского ПО использование функций электронной таблицы выглядит как обычное обращение к методу объекта. Механизм обеспечения взаимодействия между удаленными элементами СОМ называется маршалингом (marshalling).
Возникает закономерный вопрос — как проходит создание и инициализация объекта СОМ при первом обращении клиента? Сомнительно, чтобы операционная система самостоятельно создавала экземпляры всех зарегистрированных в ней классов в надежде, что один из них понадобится. А ведь для работы объекта требуются еще и серверы. Представьте, что каждый раз при загрузке Windows настойчиво запускает Word, Excel, Internet Explorer и т. д.
Любой объект СОМ является обычным экземпляром некоторого класса, описывающего его свойства и методы. Информация обо всех зарегистрированных и доступных в данной операционной системе классах СОМ собрана в специальной библиотеке СОМ, которая используется для запуска экземп ляра класса — объекта.
Сначала клиент обращается к библиотеке СОМ, передавая ей имя требуемого класса и необходимого в первую очередь интерфейса. Библиотека находит нужный класс и сначала запускает сервер, который затем создает объект — экземпляр класса. После этого библиотека возвращает клиенту указатели на объект и интерфейс. В последующей работе клиент может обращаться непосредственно к объекту и его интерфейсам.
После создания наступает очередь инициализации — объект должен загрузить необходимые данные, считать настройки из системного реестра и т. д. За это отвечают специальные объекты СОМ, которые называются моникерами (monikers). Они работают скрытно от клиента. Обычно моникер создается вместе с классом.
Довольно реальной представляется ситуация, когда одновременно несколько клиентов обращаются к одному объекту. При соответствующих настройках для каждого клиента создается отдельный экземпляр класса. За выполнение этой операции отвечает специальный объект СОМ, который называется фабрикой класса.
Наконец, остался не рассмотренным последний вопрос — как клиент может получить информацию об объекте. Например, разработчик клиентского ПО знает, что электронная таблица создана в соответствии со спецификацией СОМ, но не имеет понятия об объектах СОМ, которые предоставляют клиентам ее службы. Для разрешения подобных ситуаций разработчик объекта СОМ может распространять вместе с объектом информацию о типе. Она включает сведения об интерфейсах, их свойствах и методах, параметрах методов.
Эта информация содержится в библиотеке типов, которая создается при помощи специального языка описания интерфейса (Interface Definition Language, IDL).
Объект
Центральным элементом СОМ является объект. Приложения, поддерживающие СОМ, имеют в своем составе один или несколько объектов СОМ. Каждый объект представляет собой экземпляр соответствующего класса и содержит один или несколько интерфейсов. Что же такое объект СОМ?
Не вдаваясь пока в подробности реализации объектов СОМ в Delphi, можно сказать, что объект СОМ несколько отличается от обычного объекта.
Любой объект является экземпляром некоторого класса, то есть представляет собой переменную объектного типа. Поэтому объект обладает набором свойств и методов, которые работают с этими свойствами. К объектам применимы три основные характеристики: инкапсуляция, наследование и полиморфизм. Объекты СОМ всем этим требованиям удовлетворяют (существуют особенности наследования).
Применительно к объектам вообще понятие интерфейса объекта, как он был определен выше, не используется. В первом приближении можно считать, что все методы объекта составляют его единственный интерфейс, а указателем интерфейса является указатель на объект.
Объект СОМ может иметь любое число интерфейсов (если это число больше нуля), причем каждый интерфейс обладает собственным указателем. Это первое отличие объектов СОМ от обычных.
Некоторые языки программирования, например, Java, позволяют объекту иметь несколько интерфейсов.
У объектов СОМ имеется особенность еще в одном объектном механизме — наследовании. Вообще различают два способа наследования. Наследование реализации подразумевает передачу родителем потомку всего программного кода. Наследование интерфейса означает передачу только объявления методов, их программный код потомок должен предоставить самостоятельно.
Объекты СОМ поддерживают только наследование интерфейса, избегая тем самым возможного нарушения инкапсуляции родителя. Тем не менее, просто так выбросить наследование реализации нельзя. Вместо нее объекты СОМ используют механизм включения, то есть при необходимости потомок вызывает нужный метод родителя. Также применяется механизм агрегирования, когда один или несколько интерфейсов одного объекта на время включаются в другой объект путем передачи указателей.
Таким образом, объект СОМ с точки зрения ООП несомненно является объектом. Однако, как ключевой элемент технологии СОМ, он обладает рядом особенностей реализации базовых механизмов.
Интерфейс
Если объект СОМ является ключевым элементом реализации СОМ, то интерфейсы являются центральным звеном идеологии СОМ. Как двум принципиально разным объектам обеспечить взаимодействие друг с другом? Ответ прост: им необходимо заранее договориться о том, как они будут общаться. (Авторы намеренно не используют слово "язык", так как оно может вызвать нежелательные ассоциации с языком программирования, а как раз этот фактор не имеет во взаимодействии элементов СОМ никакого значения.)
Интерфейс как раз является тем средством, которое позволяет клиенту правильно обратиться к объекту СОМ, а объекту ответить так, чтобы клиент его понял.
Рассмотрим небольшой пример. На улице случайно встретились два человека: местный житель (объект СОМ) и заблудившийся иностранец (клиент). Предусмотрительный иностранец захватил с собой словарь (библиотека типов или интерфейс IUnknown). Иностранцу нужны знания местного жителя о городе. Он достал ручку и бумагу и, заглянув в словарь, составил фразу и старательно перерисовал незнакомые слова на бумагу. Местный житель оказался не промах. Он прочитал фразу, отобрал у иностранца словарь, составил по нему собственную фразу и тоже написал ее на бумаге. И все закончилось хорошо: довольный клиент (иностранец) получил от объекта СОМ (местного жителя) результат работы службы (информацию о дороге), а местный житель ушел вместе со словарем.
Как вы уже догадались, в этом примере интерфейсом является бумага и ручка: иностранец не знает чужого языка, зато знает, как правильно спросить,. чтобы ему ответили.
Для идентификации каждый интерфейс имеет два атрибута. Во-первых, это его имя, составленное из символов в соответствии с правилами используемого языка программирования. Каждое имя должно начинаться с символа "I". Это имя используется в программном коде. Во-вторых, это глобальный уникальный идентификатор (Globally Unique IDentifier, GUID), который представляет собой гарантированно уникальное сочетание символов, практически не повторяемое ни на одном компьютере в мире. Для интерфейсов такой идентификатор носит название IID (Interface Identifier).
В общем случае клиент может не знать, какие интерфейсы имеются у объекта. Для получения их перечня используется базовый интерфейс lunknown (см. ниже), который есть у любого объекта СОМ.
Затем клиенту необходимо знать, какие методы имеет выбранный им интерфейс. Для этого разработчик должен распространять описание методов интерфейсов вместе с объектом. Эту задачу помогает решать язык IDL (он также используется в библиотеках типов). Его синтаксис очень похож на C++.
Теперь осталось сделать самое важное — правильно вызвать сам метод. Для этого в СОМ описана реализация интерфейса на основе стандартного двоичного формата. Это обеспечивает независимость от языка программирования.
Рис. 26.3. Формат интерфейса СОМ
Доступный клиенту указатель интерфейса ссылается на внутренний указатель объекта и, через него, на специальную виртуальную таблицу (рис. 26.3). Эта таблица содержит указатели на все методы интерфейса. (Не правда ли, очень похоже на таблицу виртуальньк методов объекта в ООП.)
Первые три строки таблицы интерфейса всегда заняты под методы интерфейса lUnknown, так как любой интерфейс СОМ является его наследником этого интерфейса.
В результате, вызов метода клиентом проходит по цепочке указателей и получает указатель на конкретный метод, а затем исполняется соответствующий программный код.
Интерфейс IUnknown
Каждый объект СОМ обязательно имеет интерфейс lUnknown. Этот интерфейс имеет всего три метода, но они играют ключевую роль в функционировании объекта.
Метод Queryinterface возвращает указатель на интерфейс объекта, идентификатор IID которого передается в параметре метода. Если такого интерфейса объект не имеет, метод возвращает Null.
Обычно при первом обращении к объекту клиент получает указатель на интерфейс. Так как любой интерфейс является потомком IUnknown, то любой интерфейс имеет и метод Queryinterface. Поэтому в общем случае не важно, какой именно интерфейс может использовать клиент. При помощи метода Queryinterface он может получить доступ к любому интерфейсу объекта.
Интерфейс IUnknown обеспечивает работу еще одного важного механизма объекта СОМ — механизма учета ссылок. Объект должен существовать до тех пор, пока его использует хотя бы один клиент. При этом клиент не может самостоятельно уничтожить объект, ведь с ним могут работать и другие клиенты.
Поэтому при передаче наружу очередного указателя на интерфейс, объект увеличивает специальный счетчик ссылок на единицу. Если один клиент передает другому указатель на интерфейс этого объекта, то клиент, получающий указатель, обязан еще раз инкрементировать счетчик ссылок. Для этого используется метод AddRef интерфейса lunknown.
При завершении работы с интерфейсом клиент обязан вызвать метод Release интерфейса lunknown. Этот метод уменьшает счетчик ссылок на единицу. После обнуления счетчика объект уничтожает себя.
Сервер
Сервер СОМ представляет собой исполняемый файл: приложение или динамическую библиотеку, который может содержать один или несколько объектов одного или разных классов. Различают три типа серверов.
Рассмотрим локальный сервер. Получаемый клиентом указатель интерфейса в этом случае ссылается на специальный proxy-объект СОМ (назовем его заместителем), который функционирует внутри клиентского процесса. Заместитель предоставляет клиенту те же интерфейсы, что и вызываемый объект СОМ на локальном сервере. Получив вызов от клиента, заместитель упаковывает его параметры, и при помощи служб операционной системы передает вызов в процесс сервера. В локальном сервере вызов передается еще одному специализированному объекту — заглушке (stub), который распаковывает вызов и передает его требуемому объекту СОМ. Результат вызова возвращается клиенту в обратном порядке.
Рассмотрим удаленный сервер. Он функционирует так же, как и локальный сервер. Однако передача вызовов между двумя компьютерами осуществляется средствами DCOM — с помощью механизма вызова удаленных процедур (Remote Procedure Call, RPC).
Для обеспечения работы локальных и удаленных серверов используется механизм маршалинга и демаршалинга. Маршалинг реализует единый в рамках СОМ формат упаковки параметров запроса, демаршалинг отвечает за распаковку. В описанных выше реализациях серверов за выполнение этих операций отвечают заместитель и заглушка. Эти типы объектов создаются совместно с основным объектом СОМ. Для этого применяется IDL.
Библиотека СОМ
Для обеспечения выполнения базовых функций и интерфейсов в операционной системе существует специальная библиотека СОМ (конкретная реализация может быть различной). Доступ к возможностям библиотеки осуществляется стандартным способом — через вызов функций. Согласно спецификации, имена всех библиотечных функций начинаются с приставки со.
При установке поддерживающего СОМ приложения в системный реестр записывается информация обо всех реализуемых им объектах СОМ:
Предположим, что клиент пытается использовать некоторый объект СОМ, который до этого момента не использовался. Следовательно, клиент не имеет указателя на нужный объект и интерфейс. В этом случае он обращается к библиотеке СОМ и вызывает метод CoCreateInstance, передавая ей в качестве параметра CLSID нужного класса, IID интерфейса и требуемый тип сервера.
Библиотека при помощи диспетчера управления службами (Service Control Manager, SCM) обращается к системному реестру, по идентификатору класса находит информацию о сервере и запускает его. Сервер создает экземпляр класса — объект и возвращает библиотеке указатель на запрошенный интерфейс.
Рис. 26.4. Создание первого экземпляра объекта с помощью библиотеки СОМ и системного реестра
Библиотека СОМ передает указатель клиенту, который впоследствии может обращаться непосредственно к объекту. Схема создания первого экземпляра объекта с помощью библиотеки СОМ и системного реестра приведена на рис. 26.4.
Для неявной инициализации созданного объекта (установки значений свойств) может использоваться специальный объект — моникер. Также клиент может инициализировать объект самостоятельно, применив специальные интерфейсы (iPersistFile,IPersistStorage,IPersistStream).
Фабрика класса
Для запуска экземпляра класса используется специальный объект — фабрика класса. С его помощью можно создать как один объект, так и несколько его экземпляров. Для каждого класса должна существовать собственная фабрика класса.
Объект СОМ имеет право называться фабрикой класса, если он поддерживает интерфейс iciassFactory. В нем реализованы всего два метода:
На самом деле общий метод coCreateinstance при помощи переданного ему clsid осуществляет вызов соответствующей фабрики класса и метода CoCreateinstance интерфейса IClassFactory.
Для вызова фабрики класса существует специальная функция CoGetCiassObject. В качестве параметра ей передается clsid нужного класса и iid интерфейса (iClassFactory). Функция ищет требуемую фабрику и возвращает указатель на интерфейс. С его помощью, используя метод CoCreateinstance, клиент заставляет фабрику класса создать объект.
Библиотека типов
Чтобы документировать интерфейсы объекта для пользователей, разработчик создает информацию о типах объекта. Для этого используется язык IDL. Вся информация объединяется в специальной библиотеке типов. Она может описывать свойства и методы (а также их параметры) интерфейсов и содержать сведения о необходимых заглушках и заместителях. Информация об отдельном интерфейсе оформляется в виде отдельного объекта внутри библиотеки.
Для создания библиотеки типов, описанной при помощи операторов IDL, используются специальные компиляторы. Доступ к библиотеке осуществляется по clsid класса объекта. Кроме того, библиотека имеет собственный GUID, который сохраняется в системном реестре при регистрации объекта.
Каждая библиотека типов имеет интерфейс iTypeLib, который дает возможность работать с ней, как с единым объектом. Для доступа к информации об отдельном интерфейсе используется интерфейс ITypeinfo.
Для доступа к библиотеке по GUID используется функция LoadRegTypeLib. Если клиенту известно имя файла библиотеки, то можно воспользоваться функцией LoadTypeLib.
Объекты СОМ в Delphi
Теперь рассмотрим механизм создания объектов СОМ в Delphi. Как говорилось выше, объект СОМ должен обеспечивать возможность создания произвольного числа интерфейсов, где под интерфейсом понимается некоторое объединение методов, доступ к которым осуществляется через указатель на интерфейс.
Реализовать такое требование напрямую в рамках стандартных подходов ООП довольно затруднительно. И разработчики Delphi нашли следующий выход.
Сам объект СОМ описывается обычным классом TComObject, который порожден непосредственно от TObject. Все свойства и методы, реализующие предназначение объекта, объявляются и описываются в его объявлении. Поэтому сам класс нового объекта СОМ .принципиально ничем не отличается от любого другого.
При создании объекта СОМ с ним связывается еще один класс, который описывает все интерфейсы. Этот класс носит общее название CoClass, а при создании реального объекта к его имени добавляется приставка соCoClass объединяет всю информацию о типах, которая представлена в библиотеке типов. Объявление и описание CoClass содержится в библиотеке типов.
Итак, стандартное объявление класса на Object Pascal обеспечивает создание кода объекта — именно оно компилируется в двоичный код. coClass является надстройкой или оболочкой этого кода, он обеспечивает представление экземпляра объекта в соответствии со спецификацией СОМ и гарантирует, что обращение клиента к объекту будет обработано корректно.
Класс TComObject в совокупности с создаваемым для каждого объекта экземпляром CoClass обладает всеми рассмотренными выше признаками объекта СОМ. Он может поддерживать произвольное число интерфейсов и в том числе базовый интерфейс lunknown.
Для обеспечения работы объекта СОМ с библиотекой типов от базового класса TComObject порожден новый класс TTypedComObject. Дополнительно он имеет еще один интерфейс — IProvideClassinfo. Если этот интерфейс имеет доступ к библиотеке типов, то для получения полной информации об объекте достаточно знать его идентификатор класса. Этот класс используется для создания объектов с использованием библиотеки типов (см. ниже).
Класс TComObject
Класс TComObject обеспечивает выполнение базовых функций объекта, которые и делают его объектом СОМ. Его свойства и методы (табл. 26.1) инкапсулируют функциональность интерфейса lunknown. Также он обеспечивает хранение идентификатора класса clsid.
Таблица 26.1. Свойства и методы класса TComObject
Объявление |
Описание |
property Controller: lunknown; |
Определяет управляющий интерфейс lunknown в случае, если объект объединен с другим. Любые вызовы унаследованных от lunknown методов в других интерфейсах внутреннего объекта адресуются в Controller |
property Factory: TComObjectFactory; |
Указывает на фабрику класса |
property RefCount: Integer; |
Возвращает текущее состояние счетчика ссылок |
property ServerExceptionHandler: IServerExceptionHandler; |
Обеспечивает контроль возникающих в объекте ошибок другим объектом или сервером |
constructor Create; |
Конструктор используется, если объект не является частью объединения. Он выделяет память и вызывает конструктор CreateFromFactory |
constructor CreateAggregated(const Controller: lUnknown) ; |
Конструктор используется, если объект является частью объединения. Он выделяет память и вызывает конструктор CreateFromFactory |
constructor CreateFromFactory (Factory: TComObjectFactory; con5t Controller: lUnknown) ; |
Создает экземпляр класса и инициализирует его свойства |
procedure Initialize; virtual; |
Внутренний метод. Обеспечивает интерфейс для инициализации объекта |
function ObjAddRef: Integer; virtual; stdcall; |
Выполняет метод AddRef интерфейса lunknown |
function ObjQueryInterface (const IID: TGUID; out Obj): HResult; virtual; stdcail; |
Выполняет метод Querylnterface интерфейса lUnknown |
function ObjRelease: Integer; virtual; stdcall; |
Выполняет метод Release интерфейса IUnknown |
Для непосредственного создания объекта используется конструктор CreateFromFactory. В него передается необходимый экземпляр фабрики класса (параметр Factory) и указатель на используемый интерфейс (параметр Controller).
Методы базового интерфейса lUnknown представлены в классе одноименными методами с приставкой obj.
Класс TTypedComObject
Этот класс является непосредственным потомком класса TComObject и применяется для создания объектов СОМ с использованием библиотеки типов.
Класс обладает дополнительным интерфейсом IProvideClassinfo, который обеспечивает работу единственного дополнительного метода:
function GetClasslnfofout Typelnfo: ITypelnfo): HResult; stdcall;
Эта функция возвращает указатель на cociass конкретного класса, открывая тем самым доступ ко всей информации о типах.
Интерфейс IUnknown в Delphi
Класс TComObject имеет методы, которые соответствуют методам инкапсулированного интерфейса IUnknown. Однако в Delphi интерфейс IUnknown описан в модуле System:
IUnknown = interface
['{0000000-00000-0000-С000-000000000046}']
function Querylnterfacefconst IID: TGUID; out Obj): HResuit; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
Кроме этого, определены многочисленные интерфейсы, которые обеспечивают функционирование других технологий на основе СОМ (например, IDispatch для технологии Automation) и облегчают труд разработчиков.
Тип глобального идентификатора
Для представления глобального уникального идентификатора guid в Delphi определен специальный тип:
TGUID = record
D1: LongWord; D2: Word;
D3: Word;
D4: array[0..7] of Byte; end;
Чтобы сгенерировать новый идентификатор, можно использовать функцию CoCreateGUID из Win API. Для преобразования идентификатора в строку используется функция
function GUIDToStringfconst ClassID: TGUID): string;
Для обратной операции применяется функция
function StringToGUID(const S: string): TGUID;
Фабрика класса в Delphi
При создании объектов СОМ в Delphi разработчик не должен заботиться о создании фабрики класса для каждого объекта. Как будет показано ниже (листинг 26.1), совместно с объектом создается не только cociass, но и фабрика класса — специализированный объект СОМ, который обладает интерфейсом iciassFactory и используется для создания объектов СОМ.
Внимательный читатель уже обратил внимание, что при создании экземпляра базового класса TComObject в любом случае вызывается конструктор CreateFromFactory, который отвечает за вызов соответствующей фабрики класса. Объект СОМ связывается со своей фабрикой класса при помощи свойства
Factory, которое имеет тип TComObjectFactory. Класс TComObjectFactory является основой для использования фабрик классов в приложениях СОМ в Delphi.
В случаях, когда в рамках одного сервера функционирует несколько фабрик классов, используется специальный диспетчер классов СОМ. Он создается на базе класса TComciassManager и используется в основном для внутренних операций, когда серверу требуется выполнить определенные действия со всеми фабриками классов (например, корректно уничтожить их).
Для создания фабрики класса, объявленного с помощью класса TTypedComObject, Используется класс TTypedComObjectFactory.
Класс TComObjectFactory
Этот класс инкапсулирует функции универсальной фабрики класса для объектов СОМ, создаваемых как экземпляры класса TComObject. Он обеспечивает функционирование интерфейсов lUnknown, iciassFactory, IClassFactory2. Определяющим в работе фабрики класса является интерфейс iciassFactory.
Класс TComObjectFactory, свойства .и методы .которого перечислены в табл. 26.2, наследует непосредственно от TObject.
Таблица 26.2. Свойства и методы класса TComObjectFactory
Объявление |
Описание |
Property ClassID: TGUID; |
Содержит идентификатор класса, с которым работает фабрика |
Property ClassName: string; |
Содержит имя класса, с которым работает фабрика |
Property ComClass: Tclass; |
Позволяет определить тип класса, с которым работает фабрика. Используется во внутренних операциях |
Property ComServer: TcomServerObject; |
Определяет сервер — владелец фабрики класса |
property Description: string; |
Текстовое описание класса |
property ErrorIID: TGUID; |
Определяет интерфейс для CoClass, в котором производится обработка ошибок |
type Tclasslnstancing = (cilnternal, с is ingle Instance, ciMultiInstance); property Instancing: TClassInstancing; |
Определяет способ создания экземпляра класса. Назначение элементов типа Tclasslnstancing рассматривается при создании простого объекта СОМ |
property LicString: WideString; |
Содержит лицензионную информацию об объекте |
property ProgID: string; |
Определяет имя, разработчика и версию фабрики |
property ShowErrors: Boolean; |
При значении True при возникновении ошибок выводятся сообщения |
property SupportsLicensing: Boolean; |
Значение True говорит о том, что экземпляр объекта создан фабрикой в соответствии с лицензионным соглашением |
constructor Create(ComServer: TComServerObject; ComClass: TComClass; const ClassID: TGUID; Instancing: TClassInstancing; ThreadingModel: TThreadingModel == tmSingle); |
Создает экземпляр фабрики класса. Назначение параметров конструктора рассмотрено ниже |
function CreateComObject(const Controller: lunknown): TComObject; virtual; |
Создает экземпляр объекта, возвращает указатель на него. Параметр определяет экземпляр первого интерфейса, указатель на который возвращается клиенту |
procedure RegisterClassObject; |
Регистрирует класс объекта |
procedure UpdateRegistry(Register : Boolean); virtual; |
Регистрирует класс при вызове объекта |
Фабрика класса создается обычно при функционирующем сервере создаваемого ею объекта. Конструктор фабрики описывается в секции инициализации модуля, включающего соответствующий сервер.
Ключевым методом класса является функция createComobject. Она выполняет действия функции CoCreateInstance интерфейса IClassFactory. Метод вызывает конструктор CreateFromFactory связанного с данной фабрикой класса, передавая в него необходимые параметры.
Класс TTypedComObjectFactory
Этот класс порожден от класса TComObjectFactory и используется для создания фабрик классов, классы которых объявлены с помощью класса TTypedcomObject. To есть этот класс применяется для описания фабрики класса в библиотеке типов. Класс TTypedComObjectFactory имеет один дополнительный метод:
function GetInterfaceTypeInfo(TypeFlags: Integer): ITypelnfo;
Функция возвращает указатель на интерфейс ITypeinfo, который содержит информацию об отдельном типе.
Класс TComClassManager
Этот класс используется для управления фабриками класса в составе отдельного сервера СОМ. Он представляет собой класс типизированной переменной ComClassManager В составе модуля ComObj.
Получить ссылку на экземпляр класса можно при помощи функции того же модуля
function ComClassManager: TComClassManager;
Разработчику может быть полезным метод класса:
function GetFactoryFroniClassID(const ClassID: TGUID): TComObjectFactory;
который проводит поиск по идентификатору класса classic среди работающих фабрик классов сервера.
Объект СОМ в Delphi
При создании объекта СОМ в модуль с его описанием автоматически добавляется МОДУЛЬ ComServ. Этот модуль описывает класс TComServer, который инкапсулирует свойства сервера СОМ, в котором работает соответствующий объект. Обращение к свойствам и методам этого класса позволяет получить информацию о работающих в сервере объектах и их состоянии, а также о самом сервере.
При включении модуля ComServ в модуль объекта автоматически создается экземпляр класса TComServer, указатель на который присваивается переменной ComServ. Используя эту переменную, можно получать информацию от сервера.
Класс сервера используется для создания экземпляров фабрик классов, то есть непосредственно участвует в работе механизма взаимодействия объектов СОМ и клиентов. В модуле ComServ объявлены и описаны глобальные переменные, которые автоматически экспортируются в каждый внутренний сервер и выполняют базовые операции регистрации, перерегистрации и выгрузки сервера.
Класс TComServer
Этот класс объединяет свойства и методы (табл. 26.3), которые позволяют получить информацию о самом сервере и функционировании объектов в сервере СОМ. Класс является предком абстрактного класса TComServerObject.
Класс используется для внутренних и локальных серверов.
Таблица 26.3. Свойства и методы класса TComServer
Объявление |
Описание |
property HelpFiieName: strings- |
Определяет имя файла помощи |
property IsInprocServer: Boolean; |
При значении True сервер является внутренним сервером |
property ObjectCount: Integer- |
Определяет число объектов, работающих в сервере |
property ServerFileName: string; |
Содержит полное имя файла сервера |
property ServerKey: string; |
Если сервер является библиотекой DLL, свойство содержит строку lnprocServer32. Если сервер является исполняемым файлом, свойство содержит строку LocalServer32 |
property ServerName: string; |
Содержит имя файла сервера |
type TStartMode = (smStandalone, smAutomation, srnRegServer, smUnregServer) ; |
Задает способ запуска сервера: SmStandalone — сервер запускается пользователем; SmAutomation — сервер запускается на вызов контроллера Автоматизации; |
property StartMode: TStartMode;
|
SrnRegServer — запускается для регистрации в системном реестре; SmUnregServer — запускается для прекращения регистрации в системном реестре |
property TypeLib: ITypeLib; |
Содержит указатель на интерфейс библиотеки типов, связанной с сервером |
procedure Initialize; |
Регистрирует класс объекта в системном реестре. Вызывается автоматически при первом вызове фабрики класса |
procedure LoadTypeLib; |
Загружает библиотеку типов |
procedure SetServerName(const Name: string) ; |
Устанавливает имя сервера |
procedure UpdateRegistry(Register: Boolean); |
Проводит регистрацию работающих в сервере объектов |
Библиотека типов в Delphi
Библиотеки типов обеспечивают хранение информации об объектах, интерфейсах, функциях и т. д. Они применяются в проектах Delphi, которые используют технологии, основанные на СОМ.
Традиционно для создания библиотек типов используется язык описания интерфейсов IDL (Interface Description Language). В Delphi в качестве основного варианта применяется синтаксис и операторы языка Object Pascal. Однако вы всегда можете экспортировать этот код в формат IDL, обеспечив тем самым применение собственных объектов в любых приложениях Windows.
Библиотека типов может использоваться клиентами при обращении к объекту для получения начальной информации о доступных идентификаторах, интерфейсах, методах. В принципе, эти же данные можно получить и программными средствами, используя системный реестр и возможности базовых интерфейсов СОМ. Однако не всегда удобно дополнять приложение довольно сложным блоком кода из-за необходимости применить небольшой объект для вспомогательных целей.
Обычно внутри проекта используется вариант библиотеки на Object Pascal, a при распространении объектов библиотека типов экспортируется в формат IDL. Вариант библиотеки типов на Object Pascal сохраняется в файле с расширением pas и окончанием _tlb в названии.
В Delphi код библиотеки типов генерируется автоматически при создании объекта. Для работы с библиотекой используется специальный Редактор библиотеки типов (рис. 26.5). Все выполняемые в нем операции модифицируют исходный код библиотеки типов и соответствующих объектов и интерфейсов, поэтому разработчику нет необходимости досконально изучать особенности построения кода библиотеки.
Рис. 26.5. Редактор библиотеки типов
Разработчик может использовать или не использовать библиотеку типов в проекте по собственному желанию. Для включения библиотеки типов в проект при создании нового объекта СОМ необходимо установить флажок Include Type Library (рис. 26.6).
Редактор библиотеки типов предоставляет разработчику полный набор инструментов, позволяя автоматизировать процесс создания объектов и интерфейсов. Окно Редактора разделено на три основные части.
Вверху расположена узкая панель инструментов. При помощи кнопо^к панели можно создавать новые элементы СОМ и выполнять общие для всей библиотеки операции. Панель разделена на четыре части. Слева расположены кнопки новых типов, которые можно добавлять к библиотеке типов:
Затем идет группа из двух кнопок, назначение которых меняется в зависимости от текущего типа библиотеки. Эти кнопки предназначены для создания новых элементов типа. Например, для интерфейса можно создавать новые методы и переменные.
Затем следуют две группы кнопок, обеспечивающих выполнение общих операций библиотеки:
Слева помещен иерархический список доступных в библиотеке типов частей проекта. Он используется для выбора нужного типа и управления типами — всплывающее меню содержит набор основных команд редактора.
Справа расположена информационная панель, которая отображает информацию о выбранном в списке элементе и позволяет управлять его параметрами. Ее многостраничный блокнот изменяет состав и содержимое страниц в зависимости от текущего типа.
Для любого типа в панели всегда присутствуют две страницы. Страница Attributes задает основные параметры типа. Страница Text может содержать текстовое описание типа.
Простой объект СОМ в составе внутреннего сервера
Для демонстрации работы объекта СОМ в составе внутреннего сервера в процессе необходимо создать объект в динамической библиотеке. Для этого на странице ActiveX Репозитория требуется выбрать значок ActiveX Library. В результате будет создан новый проект, который мы назовем InProcCOM.
Листинг 26.1 Исходный код динамической библиотеки Active X
library InProcCOM;
uses
CornServ;
exports
DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer;
{$R *.RES}
begin
end.
Четыре экспортируемые библиотекой функции предназначены для обеспечения ее взаимодействия с СОМ:
Затем из Репозитория необходимо выбрать значок СОМ Object на странице ActiveX. После щелчка на значке открывается диалог установки начальных параметров нового объекта (рис. 26.6).
Рис. 26.6. Диалог установки начальных параметров объекта СОМ
Однострочный редактор Class Name должен содержать имя нового класса. Комбинированный список Instancing определяет способ создания объекта:
При использовании объекта внутри процесса установки в этом списке не имеют значения.
Комбинированный список Threading Model определяет способ взаимодействия объекта и клиентов:
Однострочный редактор Implemented Interfaces содержит имена стандартных интерфейсов СОМ, которые разработчик хочет использовать в объекте.
Однострочный редактор Description содержит описание объекта.
Флажок Include Type Library управляет созданием библиотеки типов для объекта.
После установки параметров и щелчка на кнопке ОК к проекту будет добавлен новый модуль с исходным кодом нового класса.
Исходный код модуля объекта СОМ сразу после создания представлен в листинге 26.2.
Листинг 26.2 Исходный код объекта SimpleCom сразу после создания
unit ObjUnit interface
изез
Windows, ActiveX, ComObj, InProcCOM_TLB;
type
TSimpleCOM = class(TTypedComObject, ISimpleCOM) protected
{Declare ISimpleCOM methods here} end;
implementation uses ComServ;
initialization
TTypedComObjectFactory.Create(ComServer, TSimpleCOM, Class_SimpleCOM, ciMultiInstance, tmSingle);
end.
Конструктор объекта TSimpleCOM содержит имя фабрики класса и базовый интерфейс, имя которого совпадает с именем класса. В секции инициализации расположен конструктор фабрики класса. Обратите внимание, что класс фабрики класса был создан автоматически.
Для обеспечения работы сервера использован модуль ComServ и переменная ComServ, указывающая на класс сервера СОМ, использована в конструкторе фабрики класса.
Как видите, в модуле объекта СОМ описан только класс TTypedComObject, который является основой функциональности объекта СОМ. Интерфейс и CoCiass объявлены в библиотеке типов в файле InРrосCом_TLB.PAS.
Листинг 26.3 Исходный код библиотеки типов для объекта SimpleCom
unit SimpleCOM_TLB;
interface uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;
const LIBID_SimpleCOM: TGUID = '(46E48DA6-77E7-11D2-80F3-008048A9D587}';
IID_ISiltlpleCOM: TGUID = '{46E48DA7-77E7--11D2-80F3-008048A9D587}';
CLASS_SimpleCOM: TGUID = '{46E48DA9-77E7-11D2-80F3-008048A9D587}';
type
ISimpleCOM = interface;
SimpleCOM = ISimpleCOM;
ISimpleCOM = interface(lUnknown)
['(46E48DA7-77E7-11D2-80F3-008048A9D587}'] end;
CoSimpleCOM = class
class function Create: ISimpleCOM;
class function CreateRemote(const MachineName: string): ISimpleCOM;
end;
implementation uses ComObj ;
class function CoSimpleCOM.Create: ISimpleCOM;
begin
Result := CreateComObject(CLASS_SimpleCOM)
as ISimpleCOM;
end;
class function CoSimpleCOM.CreateRemote (const MachineName: string):
ISimpleCOM;
begin
Result := CreateRemoteCoitiObject (MachineName, CLASS_SimpleCOM)
as ISimpleCOM;
end;
end.
Библиотека типов содержит идентификатор интерфейса ISimplecoM, затем идет объявление самого интерфейса. Класс coSimpieCOM обеспечивает работу интерфейсов объекта SimpleCOM. Пока он содержит только один интерфейс. В этом классе созданы две функции: create используется для работы в сервере в процессе и в локальном сервере, createRemote — для работы в удаленном сервере. Обе функции возвращают указатели на интерфейс ISimpleCOM.
Создав пустой объект, займемся разработкой методов, которые реализуют его функции. Предположим, что объект предназначен для расчета значений некоторых классов простейших математических функций.
Нам необходимо показать использование нескольких интерфейсов одного объекта, иначе объект СОМ принципиально не будет отличаться от обычного объекта. Пусть первый и второй интерфейсы реализуют различные простейшие линейные и степенные функции.
Для создания второго интерфейса и всех необходимых методов воспользуемся Редактором библиотеки типов. Для этого требуется выполнить следующие действия.
После выполнения этих операций метод готов. Проделаем ту же последовательность действий для еще одного метода — Squarex.
Для того чтобы создать исходный код для этих методов, необходимо щелкнуть на кнопке Refresh панели инструментов. В результате в модуле объекта SimpleCOM автоматически появится исходный код для созданных методов. Остается только ввести в них математические функции (листинг 26.4).
Для создания второго интерфейса isimpiecoM2 необходимо щелкнуть на кнопке Interface панели инструментов. Появившийся в иерархическом списке новый интерфейс необходимо переименовать. На странице Attributes в списке Parent Interface требуется выбрать lunknown — имя интерфейса-предка. Затем в соответствии с описанными выше операциями создаются методы Linear2X И CubeX.
Теперь созданный интерфейс требуется привязать к объекту simplecoM. Для этого в иерархическом списке выбирается объект, а в правой части — страница Implements. На ней расположен список всех интерфейсов объекта, пока в нем только один базовый интерфейс. Щелкните на списке правой кнопкой мыши и из всплывающего меню выберите команду Insert Interface. Из появившегося списка выберите интерфейс isimpiecoM2 и щелкните на кнопке OK — новый интерфейс появится в объекте.
В завершение щелкните на кнопке Refresh для создания соответствующего исходного кода. Затем можно перейти в модуль objunit и заполнить новые методы (листинг 26.4).
Листинг 26.4. Модуль ObjUnit объекта SiapleCOM после создания
unit ObjUnit;
interface
uses
Windows, ActiveX, ConiObj, InProcCOM_TLB;
type
TSimpleCOM = class(TTypedComObject, ISimpleCOM, ISimpleCOM2)
protected
function LinearX(AValue: Integer): Integer; stdcall;
function SquareX(AValue: Integer): Integer; stdcall;
function CubeX(AValue: Integer): Integer; safecall;
function Linear2X(AValue: Integer): Integer; safecall;
{Declare ISimpleCOM methods here}
end;
implementation
. uses ComServ;
function TSimpleCOM.LinearX(AValue:
Integer): Integer;
begin . Result := AValue;
end;
function TSimpleCOM.SquareX(AValue:
Integer): Integer;
begin Result := AValue*AValue;
end;
function TSimpleCOM.CubeX(AValue: Integer): Integer;
begin Result := AValue*AValue*AValue;
end;
function TSimpleCOM.Linear2X(AValue: Integer): Integer;
begin Result := AValue*2;
end;
initialization
TTypedComObjectFactory.Create(ComServer, TSimpleCOM, Class SimpleCOM, ciMultiInstance, tmSingle) ;
end.
Обратите внимание, что в представленном коде нет даже намека на принадлежность методов к разным интерфейсам. Это разделение осуществляется в библиотеке типов. Зато в объявлении класса TSimpleCOM появился второй интерфейс ISimpleCOM2.
Листинг 26.5 Исходный код библиотеки типов после создания
unit InProcCOM_TLB;
interface
uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL;
const
LIBID_InProcCOM: TGUID = '{46E48DBC-77E7-11D2-80F3-008048A9D587}';
IID_ISimpleCOM: TGUID = '{46E48DBD-77E7-11D2-80F3-008048A9D587}';
IID_ISimpleCOM2: TGUID = '(46E48DC1-77E7-11D2-80F3-008048A9D587}';
CLASS_SirnpleCOM: TGUID = '{46E48DBF-77E7-11D2-80F3-008048A9D587}';
tyре
ISimpleCOM = interface;
ISimpleCOM2 = interface;
ISirripleCOM2Disp = dispinterface;
ISimpleCOM = ISimpleCOM;
ISimpleCOM = interface(lUnknown)
['{46E48DBD-77E7-11D2-80F3-008048A9D587}'] function LinearX(AValue: Integer): Integer;
stdcall;
function SquareX(AValue: Integer):
Integer;
stdcall;
end;
ISimpleCOM2 = interface(lUnknown)
['{46E48DC1-77E7-11D2-80F3-008048A9D587}'] function Linear2X(AValue: Integer): Integer;
safecall;
function CubeX(AValue: Integer): Integer; safecall;
end;
ISimpleCOM2Disp = dispinterface
['{46E48DC1-77E7-11D2-80F3-008048A9D587}'] function Linear2X(AValue: Integer): Integer; dispid 1;
function CubeX(AValue: Integer): Integer; dispid 2;
end;
CoSimpleCOM = class
class function Create: ISimpleCOM;
class function CreateRemote(const MachineName: string): ISimpleCOM;
end;
implementation uses ComObj;
class function CoSimpleCOM.Create: ISimpleCOM;
begin
Result := CreateComObject;CLASS_SimpleCOM) as ISimpleCOM;
end;
class function CoSimpleCOM.CreateRemote(const MachineName: string):
ISimpleCOM;
begin
Result := CreateRemoteComObject(MachineName, CLASS_SimpleCOM) as TSim-pleCOM;
end;
end.
Рассмотрим теперь изменения в библиотеке типов. Во-первых, в библиотеке появилось объявление второго интерфейса ISimpleCOM2. Во-вторых, созданные методы объявлены в соответствующих интерфейсах.
После завершения разработки объекта и компиляции библиотеки необходимо зарегистрировать ее в качестве сервера. Для этого используется команда Register ActiveX Server меню Run Главного окна Delphi.
После вызова объекта такая динамическая библиотека подгружается в адресное пространство клиентского приложения и работает как внутренний сервер.
На этом создание объекта SimplecoM закончено, можно переходить к клиентской части проекта. Ее роль сыграет простое приложение ClientInProcCOM с единственной формой MainClient. Необходимо настроить ее таким образом, чтобы можно было протестировать работу двух интерфейсов созданного объекта (рис. 26.7).
Компоненты TSpinEdit предназначены для задания значения Х для функций. Кнопки должны вызывать соответствующие методы интерфейсов объекта. Для отображения результата использованы компоненты TLabel.
Рис. 26.7. Главная форма проекта ClientInProcCOM
Для обеспечения использования объекта SimplecoM из библиотеки inproccoM необходимо добавить в проект клиента файл библиотеки типов
InProcCOM.TLB.PAS.
После этого в секции public формы MainClient можно объявить две переменные:
Interface1: ISimpleCOM;
Interface2: ISimpleCOM2 ;
В листинге 26.6 представлен исходный код клиента, обеспечивающий использование методов объекта СОМ.
Листинг 26.6 Секция Implamentation формы MainClient
implementation {$R *.DFM}
procedure TFormI. FomShow (Sender: TObj ect) ;
begin
Interfacel := CoSimpleCOM.Create;
Interfacel.Querylnterface(ISimpleCOM2, Interface2);
end;
procedure TForml.ButtonlClick(Sender: TObject);
begin Labell1.Caption := IntToStr(Interfacel.LinearX(SpinEditl.Value)) ;
Labell2.Caption := IntToStr(Interfacel.SquareX(SpinEditl.Value)) ;
end;
procedure TForml.Button2Click(Sender: TObject);
begin Label21.Caption := IntToStr(Interface2.Linear2X(SpinEdit2.Value));
Label22.Caption :° IntToStr(Interface2.CubeX(SpinEdit2.Value));
end;
end.
При открытии формы создается CoClass класса CoSimpleCOM, конструктор возвращает указатель на базовый интерфейс ISimpleCOM в переменную Interfacel типа ISimpleCOM. Все эти операции выполняются динамической библиотекой 1пргоссом, запись которой в системном реестре можно найти по идентификатору из библиотеки типов inPrосCOM tlb.
Так как любой интерфейс является наследником lunknown, то для получения указателя на второй интерфейс можно воспользоваться методом QueryInterface, в параметрах которого указывается идентификатор нужного интерфейса и переменная, в которую будет возвращен указатель.
После выполнения этих операций клиент имеет указатели на оба интерфейса объекта SimpleCOM. Для выполнения методов этих интерфейсов использованы методы-обработчики нажатия кнопок Button1 и Button2. Результат отображается на форме.
Резюме
На базе СОМ разработаны и успешно работают многие технологии. Это, в первую очередь, OLE, элементы управления ActiveX, технология автоматизации, составные документы. Вопросы создания в Delphi приложений на основе этих технологий рассматриваются в последующих главах.