Московский государственный авиационный институт
(технический университет)
реферат
DCOM
Распределенная COM
по курсу
"Сети ЭВМ"
выполнил Хейфец И. М.
1999

Введение

Создание удаленного объекта

Использование CoCreateInstance

Использование CoCreateInstanceEx

Объединение создания и инициализации

Использование моникера

Доступ к удаленному объекту

Объектный RPC

OXID и разрешатели OXID

OBJREF: передача указателей интерфейсов

Роль SCM

Оптимизация IUnknown

Тестовый опрос

Обеспечение безопасного доступа к удаленному объекту

Защита активизации

Защита вызовов

Значение DCOM

Литература


Введение

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

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

Несмотря на отдельные сложные моменты, DCOM вообще проста для понимания. Она добавляет к основам СОМ всего 3 основных элемента: способ создания удаленного объекта, протокол вызова методов этого объекта и механизмы обеспечения безопасного доступа к нему.

Создание удаленного объекта

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

Использование CoCreateInstance

Независимо от того, где исполняется объект, клиент обычно создает его и затем получает указатели на необходимые интерфейсы. Для объектов, реализованных сервером "в процессе" или локальным сервером, это можно сделать, вызвав CoCreateInstance, а затем с помощью Querylnterface запросить указатели на нужные интерфейсы. Клиент может создать объект на удаленной машине, вызвав ту же самую функцию, т.е. клиенту даже не требуется знать, что объект выполняется на другом компьютере. Чтобы создать удаленный объект, клиент вызывает CoCreateInstance, как и обычно, передавая CLSID вместе с IID, указывающим первый интерфейс, указатель на который ему нужен.

Однако для удаленного объекта необходимо задать дополнительный элемент . машину, на которой он должен быть создан. Для объекта, создаваемого на той же машине, системный реестр отображает CLSID в имя DLL или исполняемого файла, который должен быть загружен для данного класса. А при создании объекта на удаленной машине системный реестр может отображать CLSID в имя машины, на которой этот объект должен создаваться. Для создания удаленного объекта устанавливается связь с удаленной машиной, в ее реестре отыскивается данный CLSID, и на этой удаленной машине запускается соответствующий сервер. Если удаленный объект реализован в DLL, то запускается суррогатный процесс, просто загружающий DLL. Иначе запускается процесс объекта, как и в случае локального сервера.

Например, клиент вызывает библиотеку СОМ для создания объекта с CLSID X, запрашивая указатель на интерфейс А этого объекта. Запись в реестре для CLSID X на клиентской машине содержит имя другого компьютера (например, elvis.acme.corn). DCOM предоставляет несколько вариантов идентификации удаленных машин в зависимости от сетевых протоколов, применяемых для доступа к удаленной системе. DCOM поддерживает доменные имена, используемые TCP/IP (типа elvis.acme.corn), а также адреса IP (Internet Protocol), имена NetBIOS и имена, применяемые NetWare IPX/SPX. Независимо от способа идентификации устанавливается связь с удаленной машиной, и там создается объект с учетом информации о CLSID Х реестра удаленной машины. Например, удаленная машина запустит сервер *.EXE, а затем попросит его фабрику класса создать объект и вернуть указатель на интерфейс А. Этот указатель далее возвращается клиенту как обычно. Для клиента все выглядит аналогично процессу создания нового объекта локально.

CoCreateInstance вызывает CoGetClassObject, чтобы получить фабрику данного класса, а затем вызывает метод этой фабрики IClassFactory::CreateInstance для создания объекта на локальной машине. Подобный процесс применяется и при создании объекта на удаленной машине, хотя бы с точки зрения программиста. На самом же деле этот процесс был оптимизирован для повышения производительности, и все эти действия выполняются за один цикл взаимодействия "запрос-ответ" с удаленной машиной.

Использование CoCreateInstanceEx

Применение CoCreateInstance для создания удаленного объекта не всегда наилучший вариант. Сколь велико ни было бы быстродействие сети, доступ к объекту на удаленной машине всегда будет медленнее, чем доступ к объекту на локальной машине. Даже для высокоскоростной сети лучше максимально сократить объем пересылаемых по ней данных. Таким образом, обеспечение производительности, удовлетворяющей пользователей (и администраторов сетей), требует минимизации количества запросов, необходимых для подготовки к использованию удаленного объекта. Важно при этом избежать излишних вызовов Querylnterface для удаленного объекта.

С этой целью DCOM предоставляет функцию CoCreateInstanceEx, альтернативную CoCreateInstance. Как и CoCreateInstance, CoCreateInstanceEx позволяет клиенту задать CLSID класса объекта, который он хочет запустить. Но если CoCreateInstance допускает указание только одного IID, задающего первый нужный интерфейс, то CoCreateInstanceEx дает клиенту возможность задать список IID. После запуска объекта CoCreateInstanceEx запрашивает у него указатели на все интерфейсы из этого списка и возвращает их клиенту одновременно. Вместо того, чтобы заставлять клиент многократно вызывать Querylnterface для получения указателей на интерфейсы объекта, одновременный возврат всех этих указателей может значительно ускорить процесс. И хотя CoCreateInstanceEx создана для работы с удаленными объектами, нет причин, по которым клиенты не могли бы использовать ее для эффективного создания экземпляров объектов, реализованных локальными серверами и серверами "в процессе".

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

Объединение создания и инициализации

После того, как объект создан клиентом, следующим шагом обычно является инициализация объекта путем выдачи ему команды загрузить перманентные данные. Например, клиент может вызвать метод IPersistFile::Load вновь созданного объекта для загрузки данных из файла. Если объект хранит свои перманентные данные, используя структурированное хранилище, клиент может вызвать IPersistStorage::Load, передав указатель на соответствующее хранилище.

Этот двухэтапный процесс создания и затем инициализации при использовании удаленного объекта не претерпевает никаких изменений . клиент по-прежнему выполняет оба шага и волен делать это традиционным способом, вызвав сначала СоCreatelnstance или CoCreateInstanceEx и обратившись затем к соответствующему методу IPersist* для инициализации объекта. Но если объект выполняется на удаленной машине, то выполнение этих шагов требует серии циклов "запрос-ответ", что может быть неприемлемо медленно. Чтобы улучшить ситуацию, DCOM предоставляет клиентам две альтернативные функции, каждая из которых создает и инициализирует объект за один прием. Если объект выполняется локально, то использование этих функций представляет собой главным образом лишь дополнительное удобство (хотя некоторый выигрыш в производительности достигается и здесь за счет сокращений числа вызовов между процессами), но для удаленных объектов реализация этих функций оптимизирована.

Первая функция . CoGetInstanceFromFile . создает новый объект и инициализирует его данными из файла. Параметры функции включают машину, на которой создается объект, CLSID, имя файла и, подобно CoCreateInstanceEx, список IID нужных клиенту интерфейсов. Если CLSID не задан, функция пытается определить его по имени файла так, как это делает файловый моникер. Использование этой функции аналогично вызову CoCreateInstanceEx и последующему обращению к методу IPersistFile::Load объекта. Вторая функция . CoGetInstanceFromIStorage . работает сходным образом за исключением того, что ей передается указатель на IStorage (он задает соответствующее хранилище), а не имя файла. Вызов второй функции аналогичен вызову CoCreateInstanceEx с последующим обращением к методу IPersistStorage::Load объекта.

Хотя обе функции позволяют указать машину, на которой должен быть создан объект, ни для той, ни для другой эта информация не обязательна. Если имя машины не задано, место создания объекта зависит от нескольких факторов. Когда в локальном реестре данный CLSID связан с именем удаленной машины, как было описано выше, объект создается на указанном компьютере. А если запись для класса в реестре содержит значение ActivateAtStorage, то объект создается на той машине, где расположен заданный файл или хранилище. Например, если клиент вызывает CoGetInstanceFromFile, передавая в качестве имени файла \\tegan.acme.com\REPORT.TXT, объект создается на машине tegan.acme.com и инициализируется данными из файла REPORT.TXT, находящегося на этой машине.

Возможно также, что машина, на которой вызывается CoGetInstanceFromFile или CoGetInstanceFromIStorage, вообще не имеет информации о данном классе . локальный реестр может не содержать записи для соответствующего CLSID. Тогда предпринимается попытка создать объект на той машине, где находится указанный файл или хранилище. Если и в реестре той машины нет никакой информации о данном класса, вызов функции завершается с ошибкой. В противном случае объект создается на той же машине, где находится файл или хранилище, как если бы в локальном реестре было значение ActivateAtStorage.

Использование моникера

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

Когда клиент вызывает для моникера IMoniker::BindToObject, последний обычно вызывает CoCreateInstance с CLSID, полученным из своих перманентных данных. Затем моникер инициализирует вновь созданный объект, используя информацию своих перманентных данных . например, имя файла. Если для CLSID, передаваемого моникером CoCreateInstance, в реестре указана удаленная машина, то объект будет создан на этой машине. При этом сам моникер не узнает, что он создал удаленный объект.

Но моникер может быть в курсе того, что создает объект на удаленном компьютере. При вызове клиентом метода моникера IMoniker::BindToObject есть вероятность, что перманентное хранилище объекта, указываемого моникером, находится на удаленной машине и что в реестре клиентской машины для класса объекта указано ActivateAtStorage. В этом случае моникер создает объект на той машине, где находится перманентное хранилище объекта, подобно CoGetlnstanceiromFile и СоGetInstanceFromIStorage.

Например, файловый моникер содержит имя файла, которое обычно задает и местонахождение перманентных данных объекта (файл) и класс объекта (определяется по расширению имени файла или, возможно, по содержимому файла). Если данный файл хранится на удаленном файл-сервере, а не на локальной машине и если в реестре локальной машины присутствует ActivateAtStorage, моникер создаст объект на файл-сервере, а не на локальном компьютере. Моникер URL содержит URL, определяющий местонахождение перманентного хранилища объекта. Если в локальном реестре для класса объекта, идентифицируемого этим моникером URL, задано ActivateAtStorage, то вызов метода моникера IMoniker::BindToObject создаст объект на компьютере, указанном URL, а не на машине, где исполняются моникер и/или его клиент. Затем, моникер приказывает объекту загрузить его перманентные данные по информации, заданной URL. И, подобно CoGetInstanceFromFile и CoGetInstanceFromIStorage, файловый моникер или моникер URL автоматически пытаются создать объект на той же машине, где находится его перманентное хранилище, если в локальном реестре моникером не найдено информации о классе объекта.

Доступ к удаленному объекту

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

Объектный RPC

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

Вызов метода объекта, реализованного в удаленном сервере, также использует заместитель и заглушку, но в данном случае клиенту необходимо выполнить вызов удаленной процедуры (RPC) сервера. Протоколов RPC хватает в избытке, и Microsoft решила не создавать новый, но адаптировать существующий. Этот протокол . Microsoft называет его MS RPC . заимствован из OSF DCE. Как сам MS RPC, так и его модификация для DCOM . объектный RPC (Object RPC) . посылают по сети информацию в том же формате, что и DCE RPC (т.е. используют ту же структуру пакета). Хотя ORPC включает ряд новых соглашений по взаимодействию клиента с сервером, добавляет несколько новых типов данных и использует некоторые поля пакета особым образом, сама структура пакетов осталась прежней.

DCE RPC и MS RPC на самом деле включают в себя два разных протокола, которые поддерживаются и ORPC. Один из них . CN или СО . используется поверх протоколов с установлением логических соединений (connection-oriented protocols), таких как TCP (Transmission Control Protocol). Поскольку CN подразумевает, что нижележащий протокол гарантирует надежную доставку данных, то он не проверяет точность передачи. Другой протокол . DG или CL . используется поверх транспорта без установления логического соединения (также называемого протоколом дейтаграмм), такого как UDP (User Datagram Protocol). Рассматривая нижележащий протокол как совершенно ненадежный, DG реализует собственные механизмы, гарантирующие надежную доставку данных. Для выдающего запрос клиента оба протокола выглядят совершенно одинаково, хотя нижележащие транспортные протоколы ведут себя абсолютно по-разному. Эти различия скрываются соответствующим протоколом RPC.

Независимо от используемого протокола клиент должен обладать информацией связывания (binding information) с пунктом назначения, прежде чем он выполнит вызов ORPC. В составе этой информации обычно сетевой адрес удаленной машины (например, адрес IP) и указание, какая комбинация протоколов должна использоваться (например, CL RPC и UDP). Информация связывания может включать точку назначения транспорта (transport endpoint) . ее часто называют портом . задающую конкретный процесс на удаленной машине. Информацию связывания удобно представлять как строковое связывание (string binding) . символьной строкой, содержащей всю необходимую информацию.

Информацию связывания с данной удаленной системой клиент может получить по-разному. Например, имя машины, передаваемое функции CoCreateInstanceEx, может быть использовано для определения по крайней мере части информации связывания. Кроме того, возможна передача информации связывания (или ссылки на нее) от одного объекта другому. Чтобы понять, как это работает и почему это важно, сначала необходимо разобраться с ролью, которую играют OXID и разрешатели OXID.

OXID и разрешатели OXID

Сервер, реализующий один или несколько объектов СОМ, доступных клиентам на других машинах, можно рассматривать как экспортер объектов (object exporter). Разрешая удаленным клиентам доступ к своим объектам, сервер в некотором смысле "экспортирует" эти объекты, обеспечивая их межмашинное использование. Если сервер . однопоточный процесс с одним или несколькими объектами, то в качестве экспортера выступает сервер в целом. Если же сервер . многопоточный процесс с одной или несколькими комнатами (apartment), содержащими различные объекты, то экспортером является каждая из комнат. В любом случае каждому экспортеру объектов присваивается 8-байтовое значение . идентификатор экспортера объектов (OXID . Object Exporter Identifier). У одного процесса с несколькими комнатами может быть несколько OXID.

Для доступа к удаленному объекту клиент вначале должен получить его OXID. Имея OXID, клиент может использовать разрешатель OXID для отображения OXID в информацию связывания с объектом. На любой машине, поддерживающей DCOM, имеется разрешатель OXID (OXID resolver), и каждый разрешатель OXID поддерживает интерфейс lObjectExporter. Несмотря на свое СОМ-подобное имя, lObjectExporter не является интерфейсом СОМ. На самом деле это интерфейс RPC, и обращение к нему выполняется через вызовы чистого RPC, а не вызовы ORPC. В его составе 3 "метода": ResolveOXID, SimplePing и ComplexPing.

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

OBJREF: передача указателей интерфейсов

Как и вызов локального метода, вызов удаленного метода может содержать параметры. Но разные машины иногда используют для представления одних и тех же данных разные форматы. Например, многие системы для представления символов применяют ASCII, тогда как мэйнфреймы IBM применяют для этого код EBCDIC. А разные компьютеры используют разные форматы представления целых и вещественных чисел. Для обеспечения взаимодействия машин, применяющих разные форматы данных, выполняется маршалинг параметров вызова ORPC с использованием сетевого формата NDR (Network Data Representation). NDR . стандартная часть DCE RPC (и, конечно же, MS RPC) . обеспечивает эффективный способ передачи параметров практически любых типов СОМ IDL между машинами, использующими разные представления этих типов данных. Необходимая трансляция параметров из NDR в локальное представление выполняется машиной, принимающей вызов.

Однако один тип параметров, часто передаваемый в вызовах ORPC, не имеет прямой поддержки NDR . это указатели на интерфейсы. Объекты часто передают указатели на интерфейсы своим клиентам, а клиент имеет право передать имеющийся у него указатель на интерфейс любому другому объекту. Когда указатель на интерфейс ссылается на объект в том же процессе, проблем не возникает: указатель передается, как есть. Когда указатель на интерфейс ссылается на объект в другом процессе на той же машине, передается ссылка на данный интерфейс внутри соответствующего процесса. Но когда указатель на интерфейс ссылается на объект, расположенный на другой машине, то, что должно быть передано, является весьма сложной конструкцией . объектной ссылкой (object reference . OBJREF).

Согласно протоколу DCOM в состав OBJREF входят:

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

Но как объект, получающий указатель на интерфейс, определяет, как связаться с объектом, заданным этим указателем? Точнее, откуда берется информация связывания с объектом, не являющаяся частью самой OBJREF? Есть два варианта. Если любой объект на локальной машине уже связывался недавно с объектом, имеющим тот же OXID, что и объект, на который ссылается указатель, то данный OXID уже есть в таблице локального разрешателя OXID. Если же информация связывания в таблице отсутствует, ее надо запросить у машины, на которой исполняется объект, заданный данной OBJREF.

Чтобы получить необходимую для использования новой OBJREF информацию связывания, DCOM на машине клиента, получившего OBJREF, обращается за помощью к локальному разрешателю OXID, передавая ему эту OBJREF. (Все это, конечно, скрыто от программиста и является частью инфраструктуры, обеспечиваемой DCOM.) Разрешатель OXID выделяет OXID из OBJREF и пытается отыскать его в собственной таблице OXID. Если объект с тем же OXID уже используется кем-то на этой машине, то в таблице будет запись для данного OXID с необходимым строковым связыванием. Предположим, объект А получил OBJREF интерфейса, поддерживаемого объектом X, расположенным на другой машине. Чтобы получить информацию связывания для осуществления вызовов с помощью нового указателя, реципиент передает OBJREF своему локальному разрешателю OXID. В данном случае разрешатель OXID может сразу возвратить нужную информацию.

Однако допустим, что OBJREF ссылается на интерфейс удаленного объекта Q, для которого у локального разрешателя OXID информации нет. Тогда разрешатель OXID извлекает из OBJREF информацию связывания с разрешателем OXID на машине, где исполняется объект Q, и с помощью ее связывается с этим разрешателем. Для этого локальный разрешатель OXID вызывает метод ResolveOxid интерфейса lObjectExporter разрешателя OXID на удаленной машине. В качестве параметра этого вызова передается OXID, выделенный из только что полученной OBJREF. Данный вызов возвращает строковое связывание для данного OXID, которое затем добавляется локальным разрешателем в свою таблицу. Вновь полученный указатель на интерфейс теперь можно использовать.

Здесь возникает естественный вопрос: почему в составе OBJREF передается не строковое связывание самого объекта, но связывание разрешателя OXID на машине объекта? Ведь тогда клиентскому разрешателю OXID вообще не пришлось бы связываться с разрешателем OXID объекта . полученная OBJREF уже содержала бы все необходимое для связи с объектом.

Чтобы понять, почему архитекторы DCOM приняли иное решение, вспомним, что DCOM позволяет клиенту работать с удаленным объектом с помощью разных протоколов: TCP/IP, UDP/IP, IPX/SPX и др. Для каждого поддерживаемого объектом протокола может потребоваться загрузка отдельной DLL, которой обычно необходимы дополнительные потоки для ожидания поступления запросов по данному протоколу. Так что объекту, работающему с несколькими протоколами, это может дорого обойтись. Не удивительно, что архитекторы DCOM стремились снизить накладные расходы, загружая протоколы только тогда, когда это необходимо, и пытаясь обойтись их минимальным количеством.

С этой целью объекты используют отложенную регистрацию протоколов (lazy protocol registration). Другими словами, объект загружает необходимый для протокола код, лишь когда клиент захочет работать с ним по данному протоколу. Например, когда разрешатель OXID объекта получает от клиентской машины вызов lObjectExporter::ResolveOxid, этот запрос выполняется с помощью протокола, скажем, TCP/IP. Разрешатель OXID на машине объекта может определить, загружен ли уже объектом код TCP/IP. Если нет, разрешатель приказывает объекту загрузить соответствующий код, после чего клиент и объект могут взаимодействовать по TCP/IP. Если затем другой разрешатель OXID запросит связь с тем же объектом по UDP/ IP, объект получит указание загрузить код и этого протокола. В то время как разрешатели OXID обязаны ожидать вызовы по всем протоколам, поддерживаемым данной машиной, конкретный объект загружает код только протоколов, явно запрошенных его клиентами. Данный подход иногда требует дополнительного обмена данными при установлении соединения, но он позволяет объектам избежать напрасной траты ресурсов на не используемые ими протоколы.

Роль SCM

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

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

Оптимизация IUnknown

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

В основе этой оптимизации лежат объекты OXID (OXID objects), каждый из которых поддерживает интерфейс IRemUnknown. OXID идентифицирует группу объектов, с которыми можно связаться через данное строковое связывание. Все такие объекты представлены одним объектом OXID и, следовательно, одним IRemUnknown. Все удаленные вызовы методов всех lUnknown таких объектов вначале обрабатываются интерфейсом IRemUnknown соответствующего объекта OXID. (Поскольку каждый интерфейс наследует от lUnknown, постольку методы lUnknown могут быть вызваны через указатель на любой интерфейс. Эти методы могут вызываться через указатель на сам lUnknown, но чаще вызов осуществляется через указатель на другой интерфейс.)

Интерфейс IRemUnknown аналогичен, но не эквивалентен lUnknown. Как и lUnknown, он содержит 3 метода: RemQueryInterface, RemAddRef и RemRelease. Но в отличие от методов lUnknown они позволяют выполнять групповые запросы. Например, одним вызовом RemQueryInterface можно запросить несколько указателей на интерфейсы нескольких объектов. Аналогично RemAddRef и RemRelease позволяют одновременно увеличивать и уменьшать счетчики ссылок нескольких интерфейсов нескольких объектов (доступ к каждому из которых осуществляется с помощью одной и той же информации связывания).

Клиенты не используют интерфейс IRemUnknown непосредственно. Вместо этого они, как всегда, вызывают методы обычного lUnknown. Реализация этих методов стандартным заместителем группирует вызовы и затем выполняет (минимально возможное количество раз) удаленные вызовы IRemUnknown. Например, вызовы клиентом AddRef и Release обычно не транслируются в вызовы IRemUnknown::RemAddRef и IRemUnknown::RemRelease один к одному. Вместо этого каждый из этих методов IRemUnknown вызывается не более одного раза . после первого вызова AddRef и после последнего вызова Release. Значит, объект могут использовать несколько клиентов, и в то же время его счетчик ссылок может быть равен 1. И никаких проблем: ведь объект не будет разрушен пока его счетчик ссылок не равен 0. С точки зрения объекта и его клиентов, все работает аналогично локальному случаю, но в распределенной среде это решение гораздо эффективнее.

Хотя оптимизация вызовов lUnknown, выполняемая инфраструктурой СОМ, несомненно полезна, иногда и сам клиент может действовать более разумно. Например, когда ему нужны указатели на несколько интерфейсов данного удаленного объекта, быстрее всего получить их за один вызов. Для этой цели DCOM определяет интерфейс IMultiOl, указатель на который клиент получает обычным способом . вызывая QueryInterface через указатель на любой интерфейс. Но IMultiOl обычно реализуется локальным заместителем, а не удаленным объектом, так что создается иллюзия, что его поддерживает любой объект. Этот простой интерфейс содержит один метод (кроме унаследованных от lUnknown) . QueryMultipleInterfaces. Клиент может передать ему список идентификаторов интерфейсов и в ответ получить указатель на каждый из них. Вместо того, чтобы заставлять клиента выполнять несколько удаленных вызовов, вся информация получается в результате одного запроса к удаленному объекту.

Тестовый опрос

Все объекты СОМ используют подсчет ссылок для определения момента, когда они могут безопасно себя разрушить. Но в случае объекта, выполняющегося на удаленной машине, есть одна проблема: что будет, если исполнение клиента завершится внезапно? Когда объект и его клиент выполняются на одной машине, объект может быть уведомлен о внезапной смерти клиента непосредственно. Но в распределенном случае ситуация несколько сложнее.

К счастью, эта проблема давно решена: клиент может производить периодический тестовый опрос (pinging) каждого используемого им объекта путем выдачи ему вызова ORPC. Если в течение достаточно длительного времени от данного клиента не приходит подобный тест, клиент считается "погибшим", и объект может предпринять соответствующие действия. Тестовый опрос может служить простым способом поддержки подсчета ссылок в распределенной среде.

Но тестовый опрос может быть и невероятно неэффективным. Вообразите, например, что на машине работают 10 клиентов и у каждого из них по 30 указателей на интерфейсы 10 разных объектов, причем последние все расположены на второй машине. Наивный (и очень дорогой) метод тестового опроса может требовать от каждого клиента опрашивать все интерфейсы каждого объекта по отдельности. Разумнее, объединив эти опросы, обеспечить гораздо более эффективное использование сети.

Так и поступает DCOM. Вместо того, чтобы требовать от каждого клиента опрашивать все объекты индивидуально, пакеты тестового опроса отправляются и принимаются разрешателями OXID. Разрешатель OXID определяет, какие интерфейсы относятся к одному и тому же объекту и какие объекты находятся на одной машине. Затем он определяет набор тестового опроса (ping set), включающего в себя все интерфейсы этих объектов, обращаясь к IObjectExporter::ComplexPing разрешателя OXID удаленной машины. После этого, разрешатель OXID может выполнять один тестовый опрос другого разрешателя для всего набора с помощью IObjectExporter::SimplePing. Это гораздо эффективнее посылки пакета опроса каждым клиентом каждому объекту и уж тем более . тестового опроса каждого интерфейса каждого объекта.

Обеспечение безопасного доступа к удаленному объекту

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

Здесь имеют место две проблемы. Первая заключается в контроле над тем, кто имеет право запускать серверы различных классов на данной удаленной машине . это сфера действия защиты активизации (activation security). Вторая проблема, состоящая в том, чтобы гарантировать контроль прав на вызовы клиентами методов уже исполняющихся объектов, известна как защита вызовов (call security). DCOM предоставляет решения обеих проблем.

Защита активизации

Параметры реестра машины в точности определяют, кто имеет право запуска серверов на данном компьютере. Более общая установка разрешает или запрещает удаленную активизацию вообще. Если она отключена, ни один удаленный клиент не сможет запускать серверы или подсоединяться к какому-либо объекту на данной машине. Возможно также определение защиты активизации на уровне класса, что позволяет контролировать, какие удаленные клиенты имеют право на запуск сервера некоторого класса. Перечень имеющих разрешение на это содержит список управления доступом (access control list . ACL). И наконец, к классам, для которых не установлена защита активизации на уровне класса, может применяться защита активизации по умолчанию. Как и при защите активизации на уровне класса, защита активизации по умолчанию определяет тех, кто имеет право на запуск сервера в данной системе, с помощью ACL.

Всегда, когда используется ACL, необходимо определить личность пользователя или объекта, выполняющего запрос. Но это приводит к другому вопросу: что такое личность объекта? Или что на жаргоне контроля прав доступа означает слово принципал (principal). По сути, принципал. это некто (например, пользователь) или нечто (например, выполняющийся процесс), имеющий учетную запись (account) в данной среде независимо от его природы. Регистрируясь в системе, Вы идентифицируете своего принципала, вводя свой идентификатор пользователя и пароль. Предположим, после регистрации Вы запустили некий клиент, который создал объект на другой машине. Кем является принципал этого объекта? Ответ на данный вопрос важен, так как принципал объекта может определять, что может делать этот объект.

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

Защита вызовов

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

Сервисы защиты могут обеспечиваться различными механизмами. Например, механизмы защиты, включенные в состав Windows NT, поддерживают все 4 упомянутых сервиса. Альтернативой является Kerberos . система защиты, разработанная в Массачусетском технологическом институте (Massachusetts Institute of Technology). Kerberos обеспечивает аутентификацию, целостность и секретность данных, но не решает вопросы авторизации (но Kerberos поддерживает взаимную аутентификацию . средство, отсутствующее в составе механизмов распределенной защиты Windows NT). Другие механизмы защиты предоставляют другие комбинации сервисов защиты.

В связи с большим количеством механизмов, обеспечивающих защиту вызовов, архитекторы DCOM столкнулись с проблемой: какой из них поддерживать? Было решено не выбирать какой-то один, но определить общие интерфейсы, способные работать со многими из существующих вариантов. Тем не менее первая версия DCOM, выпущенная в 1996 году, поддерживает только механизмы защиты Windows NT. В следующих версиях планируется включить поддержку Kerberos.

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

Автоматическая защита Будет ли процесс клиентом, выдающим запросы, или объектом, принимающим запросы, или и тем и другим, он может задать значения по умолчанию автоматической защиты вызовом CoInitializeSecurity. Вызов этой функции достаточно сложен, но позволяет процессу быстро и полностью задать свои требования по защите. Вот его наиболее интересные параметры:

Вызвав эту единственную функцию, процесс может установить значения широкого диапазона параметров защиты. Но, решив не прибегать к ней, процесс все равно наследует значения по умолчанию для данной системы. В защищенной зоне системного реестра администратор может задать общемашинные значения по умолчанию, применяемые ко всем процессам, не вызывающим CoInitializeSecurity самостоятельно. Эти установки по умолчанию включают уровень аутентификации и ACL, управляющий тем, кто имеет доступ к процессу без собственного ACL.

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

В отличие от автоматической защиты, когда один вызов устанавливает параметры как серверной, так и клиентской защиты, Поинтерфейсная защита выглядит для клиента и сервера по-разному. Чтобы использовать ее, клиент должен получить указатель на интерфейс IClientSecurity, тогда как серверу по- надобится для этого указатель на интерфейс IServerSecurity. Методы этих интерфейсов позволяют клиенту и серверу определить конкретные нужные им сервисы и уровни защиты.

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

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

После того как клиент задал параметры защиты заместителя с помощью IClientSecurity::SetBlanket, эти параметры используются при всех вызовах, выполняемых через данный заместитель. Вызовы через другие заместители осуществляются через параметры, заданные для последних. (Если параметры защиты не были заданы явно, используются значения, установленные CoInitializeSecurity либо общемашинные умолчания.) Вспомогательные функции, в которых заключены типичные последовательности вызовов, несколько облегчают установку этих параметров. Вместо того, например, чтобы запрашивать IClientSecurity, вызывать IClientSecurity::SetBlanket и затем освобождать указатель на интерфейс IClientSecurity, клиент может вызвать CoSetProxyBlanket, помещающую все эти вызовы в "удобную упаковку".

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

Чтобы определить, что имеет право делать данный клиент, надо знать, кем он является и какие параметры защиты он задал для данного вызова. Для получения этой информации код метода объекта начинает с вызова библиотечной функции CoGetCallContext, возвращающей указатель на IServerSecurity . серверный аналог IClientSecurity. Задействовав методы этого интерфейса, объект может получить информацию о клиенте, сделавшем данный вызов, а затем использовать ее для определения того, что этот клиент имеет право делать. Среди методов IServerSecurity отметим следующие:

Как и в случае IClientSecurity, есть несколько вспомогательных функций. Например, объект может вызвать CoQueryClientBlanket, заключающую в себе вызовы CoGetCallContext, IServerSecurity::QueryBlanket и IServerSecurity::Release.


Значение DCOM

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


Литература

  1. Девид Чеппел. Технологии ActiveX и OLE. M.: Издательский отдел .Русская редакция., 1997
  2. Классическая работа, являющаяся хорошим введением в мир COM. Автор вкратце освещает все аспекты ActiveX и OLE без ориентации на какой-либо конкретный язык программирования. Имеется глава по DCOM.

  3. Дейл Роджерсон. Основы COM. M.: Издательский отдел .Русская редакция., 1997
  4. Классическая работа, предназначенная для программистов на Си++, желающих понять, что же такое COM. Автор рассматривает конкретные вопросы, возникающие при программировании COM на Си++. Имеется глава по DCOM.

  5. Коберниченко А. Visual Studio 6. Искусство программирования. М.: .Нолидж., 1999
  6. Вышедшая в свет в этом месяце (ноябрь 1999) первая книга на русском языке, полностью посвященная ATL. (ATL - Active Template Library . библиотека Microsoft, используемая при программировании серьезных COM приложений). Для программистов-профессионалов на Си++, имеющих представление о COM.

  7. Том Армстронг. ActiveX: создание Web-приложений. Bhv, Киев, 1998
  8. Книга является путеводителем по технологиям и инструментарию, применяемому для создания COM и Web - ActiveX приложений . Для программистов, ориентированных на Web, и не только для них.

  9. Справочник по автоматизации. M.: Издательский отдел .Русская редакция., 1998

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