Лекция 1 Введение в современные технологии программирования
Вид материала | Лекция |
- Лекция №1. Введение, 878.61kb.
- Планы лекций по дисциплине б. 5 Педагогические технологии для специальности/направления, 24kb.
- Лекция 3 Инструментальное по. Классификация языков программирования, 90.16kb.
- Лекция Языки и системы программирования. Структура данных, 436.98kb.
- Лекция Основы программирования Эта лекция введение в Visual Basic for Applications,, 208.31kb.
- Дисциплины, 51.33kb.
- Рабочая программа учебной дисциплины (модуля) Технологии параллельного программирования, 79.5kb.
- Технологии программирования, 30.41kb.
- Лекция 6 Введение в объекты, 370.28kb.
- Программа курса (Syllabus) по дисциплине «технологии программирования» для студентов, 475.21kb.
Функционирование СОМ
В СОМ любая часть программного обеспечения реализует свои сервисы как один или несколько объектов СОМ (Здесь нельзя путать объекты СОМ с объектами в языках программирования типа С++. Несмотря на то, что у них есть общие черты, это разные вещи. В дальнейшем мы рассмотрим соотношение объектов СОМ и объектов других видов). Каждый такой объект поддерживает один или несколько интерфейсов, состоящих из методов. Метод - это функция, или процедура, которая выполняет некоторое действие и может быть вызвана программным обеспечением, использующим данный объект (клиентом объекта). Методы, составляющие каждый из интерфейсов, обычно определенным образом взаимосвязаны. Клиенты могут получить доступ к сервисам объекта СОМ только через вызовы методов интерфейсов объекта - у них нет непосредственного доступа к данным объекта.
Представьте себе, например, корректор орфографии, реализованный в виде объекта СОМ. Такой объект может поддерживать интерфейс, включающий методы типа 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 следующим образом: