Королевство Дельфи"Knowledge itself is power"
F.Bacon
 Лицей
  
Главная
О лицее

Список семинаров

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  00:45[Войти] | [Зарегистрироваться]

Урок 4. Сервер, кокласс, интерфейс

Инна Аринович
Антон Григорьев
дата публикации 28-01-2005 06:01

урок из цикла: Использование COM/DCOM в Delphi


предыдущий урок содержание семинара следующий урок

Урок 4. Сервер, кокласс, интерфейс

В модели COM/DCOM серверы делятся на внутренние (in-process), локальные (local) и удалённые (remote). Внутренние серверы - это динамически компонуемые библиотеки (Dynamically Linked Library, DLL), которые при необходимости отображаются в адресное пространство клиентского процесса. Локальные серверы - это исполняемые файлы, которые запускаются независимо от клиента. Удалённые серверы - это исполняемые файлы, работающие на другом компьютере. Один и тот же исполняемый файл может быть и локальным, и удалённым - это зависит от того, какой клиент его использует. Более того, COM/DCOM-серверы могут обслуживать несколько клиентов одновременно, поэтому один и тот же сервер может быть локальным и удалённым одновременно. В дальнейшем мы будем использовать термин "внутренний сервер" для обозначения серверов, размещённых в DLL, и "внешний сервер" - для размещённых в исполняемом файле.

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

COM/DCOM - это объектная модель, вся работа строится на объектах. Каждый COM-объект имеет тип, называемый коклассом (CoClass; префикс "Co" указывает на принадлежность к технологии COM). Не следует путать понятие кокласса в COM с понятиями класса или объекта в объектно-ориентированном программировании. В объектно-ориентированных языках программирования кокласс действительно удобнее всего реализовывать как класс, но COM-сервер может быть написан и на таком языке, как, например, C, не имеющим объектного расширения.

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

Именно кокласс, а не сервер является центральным объектом для COM-клиента. Если клиент работает с разными коклассами, то он не может узнать, реализуются ли они одним сервером или разными. Каждый кокласс имеет своё имя и GUID, называемый в данном случае CLSID. Имя - это любая последовательность символов, называемая ProgID (Обратите внимание, что ProgID не является GUID'ом).

Обычно ProgID состоит из имени разработчика, имени кокласса и номером версии, разделёнными точками (например, "SomeCompany.SomeCoclass.2"). Имя кокласса отражает его функциональное предназначение. При регистрации кокласса в системный реестр заносится и ProgID, и CLSID, поэтому, зная один из этих идентификаторов, можно узнать и второй. Для этого можно использовать системные функции CLSIDFromProgID и ProgIDFromCLSID.

Кроме ProgID, существует ещё и VersionIndependentProgID - независимый от версии ProgID. Обычно он отличается от ProgID только отсутствием номера версии в конце строки. Если на компьютере установлено несколько версий одного кокласса, каждая из них должна иметь свой CLSID и ProgID, но VersionIndependentProgID будет у них общим. Если в функцию CLSIDFromProgID передать в качестве параметра VersionIndependentProgID, она вернёт CLSID самой последней версии кокласса.

Другим не менее важным понятием технологии COM/DCOM является интерфейс. Интерфейс - это список функций. С точки зрения двоичного кода интерфейс представляет собой таблицу, содержащую указатели на функции. Количество элементов в этой таблице соответствует количеству функций в интерфейсе. Сервер реализует функции, создаёт таблицы с указателями на них, а указатель на эту таблицу может быть получен клиентом. Именно через эти указатели клиент получает доступ к функциям сервера. Количество интерфейсов, которые может экспортировать один кокласс, не ограничивается.

На первый взгляд, подобная схема работы выглядит излишним усложнением. Вместо указателей на сами функции клиент получает от сервера указатель на таблицы, в которой хранятся требуемые ему указатели на функции. Однако именно интерфейсы делают COM/DCOM объектной технологией, повышают уровень абстракции и жёстко разделяют клиент и сервер.

Каждый интерфейс имеет имя и уникальный GUID, называемый в данном случае IID. Имя интерфейса должно быть идентификатором, корректным с точки зрения языка, на котором разрабатывается клиент или сервер. Так как различные языки имеют разные требования к идентификаторам, в имени интерфейса рекомендуется использовать только английские буквы и символ подчёркивания "_". Кроме того, многие языки нечувствительны к регистру символов, поэтому нежелательно давать разным интерфейсам имена, отличающиеся лишь регистром символов. И, наконец, названия интерфейсов принято начинать с буквы "I".

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

При создании интерфейсов их можно наследовать от уже имеющихся. Интерфейс-наследник может добавлять новые функции, но перекрытие старых функций не имеет смысла, потому что интерфейс не содержит реализации функций. Сам по себе бинарный код, в отличие от обычного ООП, в модели COM/DCOM никогда не наследуется, наследуются только интерфейсы, т. е. декларация функций.

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

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

Для поддержки интерфейсов в Delphi введено ключевое слово interface, с помощью которого можно объявить интерфейсный тип:

type <InterfaceName> = interface[()]
[<IID>]
  { <Method> | <Property> }
end;

IID интерфейса записывается в виде обрамлённой апострофами строки, заключённой в квадратные скобки, например: ['{00000002-0000-0000-C000-000000000046}']. Указание IID при объявлении интерфейсного типа необязательно, но интерфейс без IID не совместим с COM/DCOM и имеет ограниченное применение внутри программы.

Объявление интерфейса похоже на объявление класса с некоторыми ограничениями:

  • Интерфейс не может содержать полей, свойства должны читаться и записываться только с помощью методов. Нельзя задавать значение по умолчанию.
  • Все элементы интерфейса имеют видимость public, недопустимо явное указание области видимости.
  • Интерфейс не имеет конструкторов и деструкторов.
  • Методы не могут быть объявлены как virtual, dynamic, abstract, override, message.

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

Если не указан предок интерфейса, предком является базовый интерфейс IUnknown (подробно он будет описан в следующем разделе). В отличие от класса, интерфейс никогда не содержит реализации своих методов, только их описания (в этом смысле методы интерфейса схожи с абстрактными методами класса). Реализация интерфейса в Delphi всегда осуществляется в классе. Один класс может реализовывать сразу несколько интерфейсов. Реализуемые интерфейсы перечисляются в строке объявления после указания класса-предка. Обязательно явное указание предка, даже если это TObject.

TMyClass=class(TSomeClass, ISomeInterface1, ISomeInterface2…)

Как и при наследовании от классов с абстрактными методами, класс, реализующий интерфейсы, должен содержать ВСЕ их методы, точно соответствующие по именам и спискам параметров. Если класс реализует два интерфейса, один из которых является наследником другого, их общие методы объявляются и реализуются классом один раз, и эта единственная реализация становится общей для обоих интерфейсов. Но возможны также ситуации, когда два независимых интерфейса содержат одноимённые методы. В этом случае каждый из одноимённых методов должен иметь отдельную реализацию, а чтобы избежать конфликта имён, используются псевдонимы. Пусть интерфейсы ISomeInterface1 и ISomeInterface2 содержат одноимённые методы Test. Следующий пример показывает использование псевдонимов для разрешения конфликта имён в таком случае:

TMyClass=class(TSomeClass, ISomeInterface1, ISomeInterface2)
private
	procedure ISomeInterface1.Test=Test1;
	procedure ISomeInterface2.Test=Test2;
	procedure Test1;
	procedure Test2;
end;

Еще одно ключевое слово, связанное с реализацией интерфейса - это директива implements.

type <ClassName> = class(<ParentClass> {,<Interface>})
property <PropertyName> : <Class> | <Interface> read <Field> | <Method>
            write <Field> | <Method> implements <Interface> {,<Interface>}
end;

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

Получить интерфейс, реализуемый объектом, можно с помощью метода:
TObject.GetInterface(const IID: TGUID; out Obj): Boolean;

В качестве параметра Obj должна передаваться переменная типа интерфейс. Если объект поддерживает интерфейс с заданным IID, функция вернет True, а в параметр Obj будет записан указатель на требуемый интерфейс, в противном случае будет возвращено False, а Obj получит значение nil.

Поскольку IID интерфейса описывается как часть объявления, компилятор знает о том, как получить его. Везде, где необходимы параметры типа TIID или TGUID, можно передать имя интерфейса. Например,

if МуClass.GetInterface(ISomeInterface1, Obj) then
Наследование интерфейсов:

Type
ISomeInterface1=interface
 	// определение ISomeInterface1
	end;
ISomeInterface2=interface(ISomeInterface1)
 	// определение ISomeInterface2
	end;
	TMyClass=class(TSomeClass, ISomeInterface2)

Для реализации ISomeInterface2 TMyClass должен реализовать методы ISomeInterface1 и ISomeInterface2. Но МуClass.GetInterface(ISomeInterface1, Obj) вернет False. Несмотря на то, что начало таблицы ISomeInterface2 совпадает с таблицей ISomeInterface1 и указатель на ISomeInterface2 может использоваться как указатель на ISomeInterface1, функции GetInterface это неизвестно. Класс "знает" об интерфейсах, о поддержке которых он или его предок объявил явно.

Следует отметить, что интерфейсы являются мощным средством программирования и вне технологии COM/DCOM. Интерфейсы являются дальнейшим развитием идеи абстрактных классов, они полностью разделяют объявление методов и их реализацию. При традиционном использовании классов с двумя разными классами можно работать единым образом, если они имеют общего предка. Но для этого всё дерево наследования классов должно с самого начала строиться таким образом, чтобы нужные классы имели общего предка. Если же работа строится на интерфейсах, требование к наличию общего предка-класса отпадает, для единообразной работы с разными классами достаточно, чтобы они реализовывали одинаковые интерфейсы. Это позволяет в любой иерархии порождать новые классы, которые, с одной стороны, наследуют функциональность классов-предков в обычном для ООП смысле, а с другой стороны, становятся совместимы с другими классами, реализующими те же интерфейсы, но не имеющими с ними никаких общих предков, кроме TObject. Это позволяет строить более гибкие взаимосвязи в программе.

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


предыдущий урок содержание семинара следующий урок




Смотрите также материалы по темам:


 Обсуждение материала [ 27-01-2006 02:42 ] 7 сообщений
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования