Лекция 1 Введение в современные технологии программирования

Вид материалаЛекция

Содержание


Функционирование СОМ
СОМ приложение предоставляет для использования свои сервисы, применяя для этого объекты СОМ
Interface Definition Language
СОМ с точки зрения ООП несомненно является объектом. Однако, как ключевой элемент технологии СОМ
СОМ, то ин­терфейсы являются центральным звеном идеологии СОМ
СОМ) и заблудившийся иностранец (клиент). Предусмотрительный иностранец захватил с собой словарь (библиотека ти­пов или интерфей
Интерфейс — это не класс.
Интерфейс строго типизирован.
Интерфейс является неизменным контрактом.
Интерфейс lUnknown
Release интерфейса IUnknown
Внутренний сервер
Удаленный сервер
Библиотека СОМ
Фабрика класса
СОМ имеет право называться фабрикой класса, если он поддержи­вает интерфейс IClassFactory
IDL, используются специальные компиляторы. Доступ к библиотеке осуществля­ется по clsid
СОМ в Delphi
CoClass обладает всеми рассмотренными выше признаками объ­екта СОМ
GUID Globally Unique Identifier — глобально уникаль­ный идентификатор
...
Полное содержание
Подобный материал:
1   2   3   4

Функционирование СОМ


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

Представьте себе, например, корректор орфографии, реализованный в виде объекта СОМ. Такой объект может поддерживать интерфейс, включающий методы типа LookUpWord (НайтиСлово), AddToDictionary(ДобавитьКСловарю) и RemoveFromDictionary (УдалитьИзСловаря). Если позднее разработчик объекта СОМ захочет добавить к этому объекту поддержку словаря синонимов, то объекту потребуется еще один интерфейс, возможно, с единственным методом, вроде ReturnSynonyrn (НайтиСиноним). Методы каждого из интерфейсов сообща предоставляют связанные друг с другом сервисы: либо корректировку правописания, либо доступ к словарю синонимов.

Или вообразите объект СОМ, представляющий Ваш счет в банке. К одному из его интерфейсов Вы можете иметь непосредственный доступ, и он содержит методы Deposit (ПоложитьНаСчет), Withdraw (СиятьСоСчета) и CheckBalance (ПроверитьОстаток). Тот же объект может поддерживать и другой интерфейс, содержащий методы вроде ChangeAccountNumber (ИзмснитьНомерСчета) и CloseAccount. (ЗакрытьСчет), которые могут вызываться только сотрудниками банка. И здесь каждый интерфейс содержит методы, связанные друг с другом.

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




Рис 1-2 Доступ к сервисам объекта СОМ осуществляется через его интерфейсы

На рис.1.3 один из интерфейсов объекта СОМ показан крупным планом. Он обеспечивает доступ к сервису коррекции орфографии и содержит три метода, упомянутые выше. Если другой интерфейс объекта обеспечивает сервис тезауруса, то на рисунке был бы изображен только метод ReturnSynonym.(Приведенная схема несколько упрощена — на самом деле все интерфейсы содержат несколько стандартных методов, которые здесь не показаны.)




Рис.1.3. Каждый интерфейс содержит один или несколько методов

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




Рис. 1.4. Клиент с указателями на два интерфейса объекта СОМ

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

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

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




Рис 1.5. В СОМ приложение обращается к сервисам объекта (независимо от того, где последний расположен), вызывая методы некоторого интерфейса

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

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

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

Предположим, что объект СОМ встроен в электронную таблицу и обеспечи­вает доступ к математическим операциям. Будет логично разделить математи­ческие функции на группы по типам и создать для каждой группы собствен­ный интерфейс. Например, можно выделить линейные, тригонометрические, агрегатные функции и т. д. На рис. 1.6 объект расположен внутри сервера - электронной таблицы. Интерфейсы обозначены маленькими кружками, связанными с объектом. Пусть интерфейс линейных функций называется ILinear, а интерфейс агрегатных функций - IAggregate.

Примечание

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




Рис. 1.6. Сервер, объект и его интерфейсы


Рис 1.6. Структура сервера

На рис.1.7 представлена схема взаимодействия клиента с объектом COM.

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

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




Рис. 1.7. Схема взаимодействия клиента и объекта СОМ.

Возникает закономерный вопрос — как проходит создание и инициализация объекта СОМ при первом обращении клиента? Сомнительно, чтобы опера­ционная система самостоятельно создавала экземпляры всех зарегистриро­ванных в ней классов в надежде, что один из них понадобится. А ведь для работы объекта требуются еще и серверы. Представьте, что каждый раз при загрузке Windows настойчиво запускает Word, Excel, Internet Explorer и т. д.

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

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

После создания наступает очередь инициализации — объект должен загру­зить необходимые данные, считать настройки из системного реестра и т. д. За это отвечают специальные объекты СОМ, которые называются моникерами (monikers). Они работают скрытно от клиента. Обычно моникер создает­ся вместе с классом.

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

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

Эта информация содержится в библиотеке типов, которая создается при по­мощи специального языка описания интерфейса ( Interface Definition Language, IDL).

Объект

Центральным элементом СОМ является объект. Приложения, поддержи­вающие СОМ, имеют в своем составе один или несколько объектов СОМ. Каждый объект представляет собой экземпляр соответствующего класса и содержит один или несколько интерфейсов. Что же такое объект СОМ?

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

Любой объект является экземпляром некоторого класса, то есть представля­ет собой переменную объектного типа. Поэтому объект обладает набором свойств и методов, которые работают с этими свойствами. К объектам при­менимы три основные характеристики: инкапсуляция, наследование и по­лиморфизм. Объекты СОМ всем этим требованиям удовлетворяют (существуют особенности наследования).

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

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

Примечание

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

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

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

Таким образом, объект СОМ с точки зрения ООП несомненно является объектом. Однако, как ключевой элемент технологии СОМ, он обладает ря­дом особенностей реализации базовых механизмов.

Интерфейс

Если объект СОМ является ключевым элементом реализации СОМ, то ин­терфейсы являются центральным звеном идеологии СОМ. Как двум принци­пиально разным объектам обеспечить взаимодействие друг с другом? Ответ прост: им необходимо заранее договориться о том, как они будут общаться. (Авторы намеренно не используют слово "язык", так как оно может вызвать нежелательные ассоциации с языком программирования, а как раз этот фак­тор не имеет во взаимодействии элементов СОМ никакого значения.)

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

Рассмотрим небольшой пример. На улице случайно встретились два челове­ка: местный житель (объект СОМ) и заблудившийся иностранец (клиент). Предусмотрительный иностранец захватил с собой словарь (библиотека ти­пов или интерфейс IUnknown). Иностранцу нужны знания местного жителя о городе. Он достал ручку и бумагу и, заглянув в словарь, составил фразу и старательно перерисовал незнакомые слова на бумагу. Местный житель ока­зался не промах. Он прочитал фразу, отобрал у иностранца словарь, соста­вил по нему собственную фразу и тоже написал ее на бумаге. И все закон­чилось хорошо: довольный клиент (иностранец) получил от объекта СОМ (местного жителя) результат работы службы (информацию о дороге), а ме­стный житель ушел вместе со словарем.

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

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

Интерфейс является контрактом между программистом и компилятором:
  • программист обязуется реализовать все методы, описанные в интерфейсе, и следовать требованиям на реализацию некоторых из них;
  • компилятор обязуется создать в программе внутренние структуры, позволя­ющие обращаться к методам этого интерфейса из любого поддерживающего те же соглашения средства программирования.

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

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

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

Таким образом, необходимо понять следующее.

Интерфейс — это не класс. Класс может выступать в роли реализации интер­фейса, но класс содержит код методов на конкретном языке программирова­ния, а интерфейс — нет.

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

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

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

Для идентификации каждый интерфейс имеет два атрибута. Во-первых, это его имя, составленное из символов в соответствии с правилами используе­мого языка программирования. Каждое имя должно начинаться с символа "I". Это имя используется в программном коде. Во-вторых, это глобальный уникальный идентификатор (Globally Unique IDentifier, GUID), который представляет собой гарантированно уникальное сочетание символов, прак­тически не повторяемое ни на одном компьютере в мире. Для интерфейсов такой идентификатор носит название IID (Interface Identifier).

В общем случае клиент может не знать, какие интерфейсы имеются у объек­та. Для получения их перечня используется базовый интерфейс IUnknown (см. ниже), который есть у любого объекта СОМ.

Затем клиенту необходимо знать, какие методы имеет выбранный им интерфейс. Для этого разработчик должен распространять описание методов интерфейсов вместе с объектом. Эту задачу помогает решать язык IDL (он также используется в библиотеках типов). Его синтаксис очень похож на C++.

Теперь осталось сделать самое важное — правильно вызвать сам метод. Для этого в СОМ описана реализация интерфейса на основе стандартного двоич­ного формата. Это обеспечивает независимость от языка программирования.




Рис.1.8. Формат интерфейса СОМ

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

Примечание

Первые три строки таблицы интерфейса всегда заняты под методы интерфей­са IUnknown, так как любой интерфейс СОМ является наследником этого интерфейса.

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

Интерфейс lUnknown

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

Метод QueryInterface возвращает указатель на интерфейс объекта, иденти­фикатор IID которого передается в параметре метода. Если такого интер­фейса объект не имеет, метод возвращает Null.

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

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

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

При завершении работы с интерфейсом клиент обязан вызвать метод Release интерфейса IUnknown. Этот метод уменьшает счетчик ссылок на единицу. По­сле обнуления счетчика объект уничтожает себя.

Сервер

Сервер СОМ представляет собой исполняемый файл: приложение или ди­намическую библиотеку, который может содержать один или несколько объектов одного или разных классов. Различают три типа серверов.
  • Внутренний сервер (in-process server) реализуется динамическими библио­теками, которые подключаются к приложению-клиента и работают в од­ном с ним адресном пространстве.
  • Локальный сервер (local server) создается отдельным процессом, который работает на одном компьютере с клиентом.
  • Удаленный сервер (remote server) создается процессом, который работает на другом компьютере по отношению к клиенту.

Рассмотрим локальный сервер. Получаемый клиентом указатель интерфейса в этом случае ссылается на специальный proxy-объект СОМ (назовем его заместителем), который функционирует внутри клиентского процесса. За­меститель предоставляет клиенту те же интерфейсы, что и вызываемый объ­ект СОМ на локальном сервере. Получив вызов от клиента, заместитель упаковывает его параметры, и при помощи служб операционной системы передает вызов в процесс сервера. В локальном сервере вызов передается еще одному специализированному объекту — заглушке (stub), который рас­паковывает вызов и передает его требуемому объекту СОМ. Результат вызо­ва возвращается клиенту в обратном порядке.

Рассмотрим удаленный сервер. Он функционирует так же, как и локальный сервер. Однако передача вызовов между двумя компьютерами осуществляет­ся средствами DCOM — с помощью механизма вызова удаленных процедур (Remote Procedure Call, RPC) (рис.1.9).





Рис.1.9 Организация маршалинга и демаршалинга

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

В описанных выше реализациях серверов за выполнение этих опера­ции отвечают заместитель и заглушка. Эти типы объектов создаются совме­стно с основным объектом СОМ. Для этого применяется IDL.

Библиотека СОМ

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

При установке поддерживающего СОМ приложения в системный реестр записывается информация обо всех реализуемых им объектах СОМ:
  • идентификатор класса (Class Identifier, CLSID), который однозначно оп­ределяет класс объекта;
  • тип сервера объекта — внутренний, локальный или удаленный;
  • для локальных и внутренних серверов сохраняется полное имя динамиче­ской библиотеки или исполняемого файла;
  • для удаленных серверов записывается полный сетевой адрес.

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

Библиотека при помощи диспетчера управления службами (Service Control Manager, SCM) обращается к системному реестру, по идентификатору клас­са находит информацию о сервере и запускает его. Сервер создает экземп­ляр класса — объект и возвращает библиотеке указатель на запрошенный интерфейс.

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

Для неявной инициализации созданного объекта (установки значений свойств) может использоваться специальный объект — моникер. Также кли­ент может инициализировать объект самостоятельно, применив специаль­ные интерфейсы (IPersistFile, IPersistStorage,IPersistStream).

Фабрика класса

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

Объект СОМ имеет право называться фабрикой класса, если он поддержи­вает интерфейс IClassFactory. В нем реализованы всего два метода:
  • CoCreateInstance — создает новый экземпляр класса. Все необходимые параметры, кроме iid, метод получает от фабрики класса. В этом его отличие от одноименной общей функции библиотеки;
  • LockServer — оставляет сервер функционировать после создания объекта.

Примечание

На самом деле общий метод CoCreateInstance при помощи переданного ему clsid осуществляет вызов соответствующей фабрики класса и метода CoCreateInstance интерфейса IClassFactory.

Для вызова фабрики класса существует специальная функция СoGetClassObject. В качестве параметра ей передается clsid нужного клас­са и iid интерфейса (IClassFactory). Функция ищет требуемую фабрику и возвращает указатель на интерфейс. С его помощью, используя метод CoCreatelnstance, клиент заставляет фабрику класса создать объект.





Рис. 1.10. Создание первого экземпляра объекта с помощью библиотеки СОМ и системного реестра

Библиотека типов

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

Дня создания библиотеки типов, описанной при помощи операторов IDL, используются специальные компиляторы. Доступ к библиотеке осуществля­ется по clsid класса объекта. Кроме того, библиотека имеет собственный GUID, который сохраняется в системном реестре при регистрации объекта.

Каждая библиотека типов имеет интерфейс ITypeLib, который дает возмож­ность работать с ней, как с единым объектом. Для доступа к информации об отдельном интерфейсе используется интерфейс ITypeInfo.

Для доступа к библиотеке по GUID используется функция LoadRegTypeLib. Если клиенту известно имя файла библиотеки, то можно воспользоваться функцией LoadTypeLib.

Объекты СОМ в Delphi

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

Реализовать такое требование напрямую в рамках стандартных подходов ООП довольно затруднительно. И разработчики Delphi нашли следующий выход.

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

При создании объекта СОМ с ним связывается еще один класс, который опи­сывает все интерфейсы. Этот класс носит общее название CoClass, а при соз­дании реального объекта к его имени добавляется приставка Cо. CoClass объе­диняет всю информацию о типах, которая представлена в библиотеке типов. Объявление и описание CoClass содержится в библиотеке типов.

Итак, стандартное объявление класса на Object Pascal обеспечивает создание кода объекта — именно оно компилируется в двоичный код. CoClass явля­ется надстройкой или оболочкой этого кода, он обеспечивает представление экземпляра объекта в соответствии со спецификацией СОМ и гарантирует, что обращение клиента к объекту будет обработано корректно.

Класс TComObject в совокупности с создаваемым для каждого объекта эк­земпляром CoClass обладает всеми рассмотренными выше признаками объ­екта СОМ. Он может поддерживать произвольное число интерфейсов и в том числе базовый интерфейс IUnknown.

Для обеспечения работы объекта СОМ с библиотекой типов от базового класса TComObject порожден новый класс TTypedComObject. Дополнительно он имеет еще один интерфейс — IProvideClassInfo. Если этот интерфейс имеет доступ к библиотеке типов, то для получения полной информации об объекте достаточно знать его идентификатор класса. Этот класс использует­ся для создания объектов с использованием библиотеки типов.

GUID Globally Unique Identifier — глобально уникаль­ный идентификатор

Теперь перейдем к рассмотрению проблемы выбора необходимого интерфейса из того многообразия, которое представлено в иерархии интерфейсов. Вот тут имеется существенное расхождение между идентификацией класса и интерфей­са. Эти различия связаны с тем, что классы используются внутри одного и того же модуля, а интерфейсы — в различных модулях. Для того чтобы создать класс с заданными свойствами, его имя просто указывается перед конструктором. Про­граммист сам следит за тем, чтобы имена различающихся классов не совпадали, а при их совпадении — чтобы вызывался конструктор нужного класса. При рабо­те с несколькими модулями такое невозможно, поскольку модули могут созда­ваться разными разработчиками в разное время.

Если бы интерфейсы различались только по именам, то при случайном совпа­дении имен (а это происходило бы довольно часто: имя обычно несет в себе смысло­вую нагрузку) двух интерфейсов, реализованных в разных модулях, вместо одного модуля загружался бы другой. Поэтому для идентификации интерфейса исполь­зуется структура типа GUID (Globally Unique Identifier — глобально уникаль­ный идентификатор), которая имеет размер 16 байт (128 бит). Единственный тип данных, который предопределен для интерфейса, — это GUID. Каждый СОМ-интерфейс содержит собственный уникальный идентификатор GUID. Если разра­ботчик реализует новый интерфейс, то этот интерфейс обязан иметь GUID, при­чем уникальность должна соблюдаться не только в рамках данного компьютера разработчика, но и для всего мира. Эта глобальная уникальность достигается путем генерации GUID как псевдослучайного числа по алгоритму, определенному кон­сорциумом Open Systems Foundation (сейчас он носит название Open Group) и ис­пользующему некоторые специфические характеристики данного компьютера (в частности, IP-адрес сетевого адаптера). Алгоритм учитывает текущую дату и время, номер текущего программного процесса для приложения, создающего GUID, и уникальный идентификатор, хранящийся в недрах сетевой карты, если таковая уста­новлена в компьютере, а также некоторые другие данные. К счастью, нам нет необхо­димости углубляться во все перипетии формирования GUID. Возникает естественный вопрос — можно ли в двух разных местах случайно сгенеририровать два одинаковых иден­тификатора GUID? Ответ на этот вопрос заключается в возможном диапазоне значений GUID. Он настолько огромен, что если генерировать GUID со скоростью 1000 000 идентификаторов в секунду, то за все время существования Вселенной с вероятностью 95% не будут созданы два одинаковых идентификатора GUID.

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

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

Жизненно важно, чтобы при создании нового интерфейса формировался новый, нигде ранее не встречавшийся GUID (т.е. ни на одном компьютере мира, где этот ин­терфейс может гипотетически использоваться). Ни в коем случае нельзя копировать значение GUID из одного интерфейса в другой.

Все заботы о формировании GUID в нашем случае берет на себя среда программирования (Delphi, Visual C++). Например, в Delphi за­прос на формирование посылается в том случае, если вы расположите курсор в том месте программного кода, куда должен быть вставлен GUID, и нажмете <Ctrl+Shift+G>. При этом Delphi сформирует запрос к Windows-API функции CoCreateGuid, а послед­няя уже и сформирует уникальный идентификатор.

Создание приложения в Delphi, демонстрирующего формирование GUID.

1.Запустите Delphi.

2. На форму поместите компоненты TMemo и TButton, разместив их примерно так как, как указано на рис. 1.11.

3. Свойству Name и Caption присвойте значение GenerateGuid и свяжите код процедуры TForm1.btnGenerateClickf(Sender; TObject) из программы на листинге 1.1.

В листинге 1.1. приведен текст программы, которая непосредственно вызывает CoCreateGuid и формирует в результате список уникальных идентификаторов.


Рис. 1.11. GUIDDemo демонстрирует, как вызвать из приложения функцию CoCreateGuid.

Листинг 1.1. Модуль MainForm приложения GUIDDemo

unit MainForm;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComObj, ActiveX;

type TForm1 = class(TForm)

btnGenerate: TButton;

Memol: ТМешо;

procedure btnGenerateClick(Sender: TObject);

private

{ Объявление закрытых членов}

public

{ Объявление общедоступных членов }

end;

var Form1: Tform1;

implementation

{$R *.DFM}

procedure TForm1.btnGenerateClick(Sender; TObject);

var

Guid: TGUID;

begin

CoCreateGuid(Guid);

Memol.Lines.Add(GuidToString(Guid));

end;

end.

На рис. 1.11 показано, что выводит на экран приложение GUIDDemo после запуска на выполнение.

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

Структура TGUID определена в Delphi следующим образом:

TGUID = record

Dl: LongWord;

D2: Word;

D3: Word;

D4: array[0..7] of Byte;

end;

Четыре поля в TGUID соответствуют четырем составным элементам GUID. Уни­кальные идентификаторы в Delphi могут быть представлены двумя способами. На­пример, если объявить некоторый интерфейс IformattedNumber следующим образом: