Динамическое взаимодействие клиентов и серверов CORBAАвтор: Александр ЦимбалИсточник: «Технология Клиент-Сервер»Основным способом, используемым для создания CORBA-приложений, является так называемый «статический» подход – основой является набор IDL-объявлений, на базе которого компилятор с языка IDL генерирует все необходимое для взаимодействия программы и CORBA (совокупность сгенерированных классов, функций, типов и пр. на стороне клиента часто называют «стабом»(stub), а не стороне сервера – «скелетоном»). Это означает, что полученный код уже «настроен» на использование конкретных (определенных разработчиком) типов данных, наиболее подходящих и удобных для решения данной конкретной задачи. Совершенно очевидно, что такой подход не может быть использован при создании средств скорее системного, чем прикладного, уровня – например, универсальных мониторов, отслеживающих поведение CORBA-приложений или мостов, обеспечивающих взаимодействие CORBA с другими технологиями. Кроме того, иногда приходится создавать CORBA-объекты в ходе выполнения программы. Например, вы хотите создать объектное представление данных, хранящихся в одной или нескольких записях реляционной базы данных. Скорее всего, такой объект должен быть создан динамически – ясно, что крайне трудно (если вообще возможно) предусмотреть заранее все необходимые IDL-типы. Итак, под «динамическим» взаимодействие понимается способ организации удаленных вызовов, при котором либо клиент, либо сервер, либо они оба не используют сгенерированных на основы IDL-деклараций стабов и скелетонов. Не надо думать, что знание этого механизма совсем не нужно прикладному программисту. Спецификация CORBA и ее конкретные реализации, например, INPRISE VisiBroker, предусматривают, например, возможность использования так называемых «интерсепторов». Интерсепторы представляют из себя обычные callback-функции, т.е. такие функции, которые написаны программистом, но вызываются в определенных ситуацией компонентами самого middleware – например, ORB’ом. Интерсепторы очень удобны во многих ситуациях – например, при определении интенсивности взаимодействия клиентов и серверов, фильтрации посылаемых сообщений на уровне ORB и т.п. Для работы с интерсепторами нужно понимать принципы динамического взаимодействия в CORBA. CORBA содержит две подсистемы, обеспечивающие работу в динамическом режиме. Подсистема на стороне клиента называется DII – Dynamic Invocation Interface («Интерфейс Динамических Вызовов»), на стороне сервера – DSI (Dynamic Skeleton Interface). Они работают независимо друг от друга. Это означает, что как статический, так и DII-клиент может взаимодействовать как со статическим, так и с DSI-сервером, причем ни клиент, ни сервер не знают и не должны знать о том, какой именно механизм используется на противоположной стороне. При использовании DII и DSI часто приходиться обращаться к внешним источникам информации – например, к репозитариям интерфейсов. Какая информация необходима для использования DII/DSIПервое, что абсолютно необходимо для динамического взаимодействия клиентов и серверов - это знание RepositoryID интерфейса, методы которого будут использоваться. Ясно, что перед вызовом методов должны быть тем или иным образом получена объектная ссылка. При использовании стандартных средств CORBA (явная передача объектной ссылки через файл, базу данных или электронную почту, использование Naming Service, Trading Service и др.) репозитарный идентификатор интерфейса может присутствовать неявно, но он, конечно же, известен. Как вы увидите дальше, при изучении DSI, иногда RepositoryID должен быть указан явно (подход, когда это необходимо, сейчас признан устаревшим, но он еще используется довольно широко). В добавление к стандартным средствам CORBA, в VisiBroker предусмотрен нестандартный метод ORB::bind(), который позволяет получить объектную ссылку (тип CORBA::Object). Метод CORBA::ORB::bind() очень похож на все другие методы bind() в VisiBroker. На C++ его объявление выглядит так:
Первым его (и обязательным, в отличие от методов bind() в других классах) аргументом является RepositoryID интерфейса, который должен реализовать объект, на который нужно получить объектную ссылку. После того, как вами получена объектная ссылка, при написании клиентского приложения нужно знать имена и аргументы реализованных на стороне сервера методов. Аналогично, при создании динамического сервера, в общем случае нужно быть к готовым к приходу тех или иных клиентских запросов. any и TypeCodeИтак, мы пытаемся работать в условиях, когда в создаваемой программе имена методов и характеристики их аргументов (имена, типы, атрибуты и текущие значения) являются переменными в терминах используемого языка программирования - в нашем случае - языка C++. Следовательно, необходимо обеспечить передачу, например, аргументов, в некотором обобщенном виде. Такой способ должен позволять передать <самодокументированные> структуры, которые содержат внутри себя всю необходимую информацию. В CORBA для этого используются несколько интерфейсов (т.е. типов данных). Здесь мы подробнее рассмотрим два из них - any и TypeCode. Они тесно связаны друг с другом. Любой объект типа any состоит из двух частей: первая хранит информацию о типе хранящегося в объекте any значения, вторая - само это значение. Первая часть представляет собой объект типа TypeCode. Для того, чтобы у читателя создалось более полное представление о типах any и typeCode, будет рассказано и о том, каким образом они используются на уровне IDL-деклараций и как с ними работает компилятор idl2cpp. anyПоскольку C++ поддерживает концепцию универсальных указателей (void*) и обеспечивает гибкое управление динамической памятью, то при отображении any на C++ просто создается класс CORBA::Any, содержащий в качестве своих private-данных указатель на данное, его признак типа и вспомогательную информацию (например, является ли any владельцем своих данных):
В случаях, когда конфликт CORBA-синонимов типов C++ невозможен, методы выглядят очень просто:
Обратите внимание, что методы извлечения данных возвращают признак типа bool, который имеет значение true, если операция завершилась успешно, или false в противном случае. Поведение any меняется в зависимости от типа находящегося в нем данного, поэтому имеет смысл рассмотреть работу с разными группами типов отдельно. any и string/wstringРабота со string и wstring практически идентична, поэтому рассмотрим правила использования строк на примере типа string.
Формы записи
и
эквивалентны. Первое, на что нужно обратить внимание, это то, что при выполнении записи строки в any программист может явно указать, нужно ли в any создавать динамическую копию строки или достаточно записать в any только указатель на строку. В первом случае при уничтожении объекта (или операции присваивания) автоматически удаляется содержащаяся в any копия, во втором - вызывается операция CORBA::string_free() для указателя, т.е. в любом случае any является владельцем своих данных. Вот несколько очевидных следствий из этого правила:
Следующее важное обстоятельство: строка, извлекаемая из any, по-прежнему находится под управлением any, поэтому не надо ни удалять такую строку, ни даже изменять ее (спецификация требует, что переносимая программа должна трактовать такую строку как константу):
Правильный вариант:
any и структуры, объединения и последовательностиОперации >>= и <<= генерируются для таких типов данных на основе их IDL-деклараций при установке соответствующего ключа компилятора idl2cpp. Главной особенностью этих операций является то, что могут создаваться две отдельные операции записи таких типов данных в any: одна из них приводит к копированию самого данного, другая - к записи только указателя. Спецификация OMG ясно требует наличия операции копирования; наличие второй формы обязательным не является (по крайней мере, текст спецификации можно трактовать именно так). К сожалению, VisiBroker использует только режим с созданием настоящей копии: IDL:
C++:
Для многих других реализаций CORBA (и, может быть, даже для последующих версий VisiBroker) возможно использовать обе формы операции. Их поведение аналогично поведению операций работы со строками, только для строк режим копирования определяется значением последнего аргумента конструктора класса from_string. Так же, как и при работе со строками, никогда не следует считывать данные из any в объект _var-класса:
Причина неизбежного появления ошибки совершенно ясна: и any, и msv сейчас отвечают за освобождение динамической памяти, в которой находится объект типа MyStruct. any и массивыДля массивов генерируется специальный класс, специально предназначенный для организации взаимодействия с any - класс, имя которого заканчивается на _forany. Конструктор этого класса имеет аргумент типа bool (имя этого аргумента - nocopy), который определяет, нужно ли выполнять копирование или просто необходимо сохранить в any указатель (разумеется, со взятием управления памятью на себя). Нетрудно заметить, что такое поведение совершенно аналогично поведению класса from_string. В следующем примере выполняется копирование данных:
А вот так можно избежать копирования:
Для чтения массива из any необходимо сначала создать переменную типа MyArray_forany. Для этого типа переопределена операция [], так что можете рассматривать объект этого типа как обычный массив:
any и объектные ссылкиПрименительно к взаимодействию any и объектных ссылок справедливы все замечания, касающиеся использования структур, объединений и последовательностей. VisiBroker предусматривает только одну форму операции <<=, а именно, с использованием типов _var и _ptr. В этом случае выполняется копирование объектной ссылки, т.е. вызывается операция _duplicate(). Спецификация говорит о возможности генерации операции <<=, для которой в качестве второго операнда используется адрес объектной ссылки, т.е. указатель на объект _var- или _ptr-типа. TCKindTCKind просто представляет из себя перечисление, в котором для каждого типа данных IDL (включая тип any) сопоставлен свой элемент):
TCKind, как перечисление IDL, безо всяких изменений отображается в перечисление (enum) C++. Обычно в СORBA-программах те или иные конкретные значения признака типа используются примерно так:
TypeCodeTypeCode представляет собой довольно интересный IDL-тип. C одной стороны, правила его отображения с IDL на конкретный язык программирования могут отличаться от стандартных правил, т.е. TypeCode является псевдо-типом IDL, объявленным с помощью PIDL. С другой стороны, TypeCode можно передавать как аргумент удаленных методов, что нехарактерно для псевдо-типов. Не забывайте также, что TypeCode является IDL-интерфейсом, т.е. это объектная ссылка. Основными методами интерфейса TypeCode являются следующие: kind() - возвращает TCKind для TypeCode. equal() - сравнивает, является ли объект типа TypeCode, для которого вызывается этот метод, <эквивалентным> аргументу этого метода. Метод equal() в CORBA 2.3 возвращает значение TRUE тогда и только тогда, когда сравниваемые объекты типа TypeCode эквивалентны во всех отношениях, т.е. для них применимы одни и те же операции интерфейса TypeCode, и все они дают для обоих объектов одинаковые результаты. equivalent() - выполняет те же действия, что и equal(), но при этом он поступает более <интеллектуальным> образом. В частности, он игнорирует наличие алиасов типов (т.е. использование синонимов типов, создаваемых с помощью typedef). get_compact_typecode() - получает новый объект TypeCode на базе исходного путем удаления всех необязательных описателей структуры типа вида name и member_name. Алиасы остаются нетронутыми. id() - возвращает репозитарный идентификатор (Repository ID). В чем же заключаются особенности отображения TypeCode по сравнению с <нормальными> интерфейсами CORBA? Одной из таких важных особенностей является возврат для методов name(), id(), member_name() и других C++-типа const char*, а не char*: IDL:
C++:
Это, в частности, означает, что управление возвращаемыми строками берет на себя сам TypeCode, и вам не следует пытаться вызывать CORBA::string_free(). Вряд ли это удобно, но что поделаешь. Поскольку TypeCode часто используются при анализе типов используемых данных, возникает вопрос об универсальности их использования - и для стандартных данных, и для типов, созданных программистом. На первый взгляд может показаться, что, например, для сравнения типов объектов вполне достаточно наличия объектов типа TCKind, т.е. вместо сравнения TypeCode лучше сравнивать их TCKind'ы:
Это будет хорошо работать, но только до тех пор, пока вы не захотите сравнить типы двух массивов или структур. Их TCKind будет одинаков - tk_array или tk_struct, соответственно, но два типа могут иметь разные типы своих элементов, число этих элементов или полей. Поэтому необходимо сравнивать все-таки сами объекты типа TypeCode. Это сопряжено с определенными трудностями. Вообще, сравнение на равенство (или неравенство) двух объектных ссылок в CORBA - задача трудно решаемая. В общем случае получить достоверный однозначный ответ крайне трудно, если не сказать невозможно. Но в данном случае, к счастью, никаких проблем не возникает. Для сравнения TypeCode вы можете использовать вполне однозначные функции TypeCode::equal() и TypeCode::equivalent().
или
Теперь можно задаться следующим вопросом: где взять конкретные значения TypeCode - и для стандартных IDL-типов, и для IDL-типов, определенных пользователем? Значения стандартных TypeCode для IDL-типов, не требующих наличия дополнительной информации, помимо TCKind, созданы как константы в классе CORBA. Вот список этих констант (все они имеют тип const CORBA::TypeCode_ptr):
Для остальных типов данных соответствующие _tc_-константы генерируются непосредственно компилятором idl2cpp. IDL:
C++:
Это, конечно, прекрасно, но мы говорим о динамическом способе взаимодействия и, следовательно, IDL-декларации просто отсутствуют. В этом случае нужные объекты типа TypeCode можно создать, используя в качестве фабрики сам ORB:
Описание метаданных каждого типа данных и список функций для создания объектов типа TypeCode вместе со списками их аргументов приведены в документе 99-10-11.pdf. dynAnyИтак, мы теперь знаем, что объекты типа any состоят из описания типа (объект TypeCode) и собственно значения. Если и TypeCode, и тип значения, которое нужно поместить в объект any, известно на этапе компиляции, то не возникает никаких трудностей. Вы определяете с помощью IDL свой собственный тип данных (обычно это структура, объединение, последовательность, массив или тип-значение), а компилятор с IDL на выбранный язык программирования сгенерирует для этого типа операции помещения его в объект any и извлечения из него. Для стандартных типов данных - short, string и др. - такие стандартные операции являются частью мэппинга IDL на конкретный язык программирования. Проблемы возможны в том случае, если код типа (TypeCode) и/или собственно данные, которые нужно поместить в объект типа any, неизвестны на этапе компиляции, т.е. для них нет IDL-декларации и, следовательно, нет сгенерированной операции помещения таких данных в any. Как динамически создавать коды типов с помощью ORB, было уже показано. Возможно и динамическое - без IDL - создание данных произвольных типов и помещение их в объект типа any. Такие операции предоставляют совместно интерфейсы DynAny и DynAnyFactory.
Способ получения интерфейса DynAnyFactory и простые примеры работы с DynAny будут рассмотрены ниже. Более подробное рассмотрение этой темы выходит за рамки статьи. Исчерпывающее описание можно найти в документе 99-07-13.pdf на сайте www.omg.org. Некоторые IDL-типы, используемые при работе с DII/DSIПрактически все рассматриваемые в этой главе IDL-объекты являются псевдо-объектами, и на практике гораздо важнее знать, во что превращается данный набор IDL-объявлений при работе с C++ или Java, чем помнить само IDL-объявление. Вследствие этого, мы рассмотрим и IDL-декларации, и их отображения на C++. NamedValue Структура NamedValue используется для задания аргументов удаленных методов в динамическом режиме. В принципе информация, которая хранится в такой структуре, соответствует информации, известной компилятору С++, Java или других языков при использовании статических стабов. Тип NamedValue предназначен для использования исключительно как элемент NVList.
Отображение на C++ Согласно спецификации OMG, С++-аналог IDL-типа NamedValue выглядит так:
Отображение для VisiBroker for C++ Реализации Visigenic/INPRISE, в общем, следует спецификации OMG, но при этом добавляет несколько полезных методов (далее мы будем приводить отображение для VisiBroker). Текст находится в файле ...vbroker\include\nameval.h. Ниже приведены наиболее важные фрагменты:
Обратите внимание на то, что деструктор объявлен как private-метод. Это классический способ гарантировать в С++ использование только динамических объектов такого типа. Вы можете, как всегда, использовать типы NamedValue_ptr и NamedValue_var. NVList Этот псевдо-объект IDL предназначен для хранения списка аргументов.
Фабрикой для NVList является ORB.
Отображение для VisiBroker for C++
Операции с суффиксом _consume соответствуют новому стилю управления памятью, т.е. создаваемый элемент берет на себя управление памятью для имен и значений. Это означает, что список создает копию создаваемого элемента и хранит эту копию. Обратите внимание на наличие public-конструктора и деструктора. Environment Этот тип предназначен для управления исключениями при работе с DII, точнее, для хранения и управления объектом типа <исключение>. Программист устанавливает исключительную ситуацию (как объект) или получает к нему доступ с помощью атрибута exception, который в обычном стиле CORBA реализован с помощью пары set/get- методов. Этот тип очень важен, так возникновение исключительной ситуации при использовании DII-вызовов отслеживается путем явного анализа присутствия (или отсутствия) исключения как объекта, который <упакован> в объект CORBA::Request. Поскольку каждый конкретный вызов может приводить к появлению только одной исключительной ситуации (из некоторого множества возможных для данного метода), то объект Request содержит единственный подобъект типа Environment, который, в свою очередь, может содержать только одно исключение. Проверка, нее возникло ли исключения при вызове, выполняется путем вызова get-метода exception(). Возврат нулевой ссылки (NULL для C++) говорит о том, что при выполнении кода удаленного метода исключительной ситуации не возникло.
Задание в качестве параметра при вызове set-метода exception() нулевой ссылки эквивалентно вызову метода clear(), т.е. сбросу установленного исключения Отображение для VisiBroker for C++
При вызове set-метода exception() объект типа Environment становится владельцем передаваемого ему объекта типа <исключительная ситуация>, причем он предполагает, что этот объект создан с помощью операции new. Get-метод exception(), возвращая объект, не передает пользователю <владение> им - по-прежнему за удаление этого объекта отвечает Environment, и вы НЕ должны вызывать функцию delete для результата get-метода exception(). Интерфейс ExceptionListОбъект такого типа позволяет хранить список объектов типа TypeCode, рассматриваемый как список признаков типов исключительных ситуаций, которые могут быть сопоставлены с некоторым удаленным методом.
Смысл методов этого интерфейса абсолютно очевиден. На C++ на него основе будет сгенерирован примерно следующий класс:
DIIСпецификация DII содержится в документе 99-07-11.pdf. Автор рекомендует знакомиться с вопросами, имеющими отношение к DII, по спецификациям отображения IDL на конкретный язык программирования – они (по крайней мере, на момент написания статьи) содержат более свежую и полную информацию). Термин «DII» относится к совокупности интерфейсов, методов, типов данных и пр., которые обеспечивают динамическое (т.е. в процессе работы программы) формирование запроса, который стандартным образом передается ORB, а затем и компонентам CORBA на стороне сервера для его выполнения и, возможно, возврата результата. Концептуально и динамический, и статический способ формирования запроса (более конкретно, объекта типа CORBA::Request) ничем не отличаются друг от друга, просто в одном случае программист вызывает те или иные методы, формирующие данный объект, а в другом эту работу выполняет сгенерированный компилятором с IDL некий код в клиентском стабе. Для использования DII (и DSI) программист должен свободно ориентироваться в средствах динамического создания как новых типов (интерфейс TypeCode), так и данных таких типов (интерфейсы any и DynAny и производные от него интерфейсы - DynStruct, DynArray и др.)... Объект CORBA::RequestОбъект Request играет главную роль при вызове удаленных методов в режиме DII. Он содержит все необходимые свойства и методы, чтобы выполнить следующие действия (предположим, что этот объект уже тем или иным образом создан): создание и заполнение списка аргументов – их имена, типы и значение; обращение к одному из методов выполнения удаленного вызова; получение и анализ результата. Ниже приведена IDL-декларация для интерфейса Request. Как и большинство ранее рассмотренных объявлений, это псевдо-IDL, и отображения на конкретные языки программирования имеют многочисленные особенности.
Рассмотрению методов собственно выполнения запроса посвящен отдельный раздел. Смысл атрибутов этого объекта, наверное, в объяснениях не нуждается. Отображение Request на C++Одной из важных (и очень приятных) особенностей отображения IDL для интерфейса Request на C++ является добавление большого количества вспомогательных и очень удобных для работы методов - методов формирования списка аргументов.
Как всегда, для VisiBroker предусмотрены три дополнительные статические функции - _duplicate(), _release() и _nil(). Для объекта Request определены специальные правила управления памятью для всех методов, сгенерированных для IDL атрибутов, а именно, программист никогда не должен пытаться применять операцию delete (или ее аналоги) к результате вызова методов target(), operation(), arguments(), result(), env(), exceptions(), contexts() и ctx(). Если при формировании списка ExceptionList() вы использовали методы с суффиксом _consume, то список создает копии объектов TypeCode и хранит эти копии. Более того, реализация может после создания копии уничтожить оригинал, поэтому не рекомендуется при создании переносимых программ обращаться к объектам, которые являлись аргументами метода add_consumer(). Методы add_XX_arg() позволяют легко добавлять в список новые аргументы. Вместо получения объекта <список аргументов> с помощью вызова arguments(), а затем использования метода NVList::add(), например, так:
можно сразу обратиться к методам добавления аргументов непосредственно в классе Request:
Создание объекта RequestИтак, мы уже немного научились работать с объектом Request, но до сих пор не знаем, как его создать. Стандартным методом, объявленным на уровне IDL, является метод Object::create_request() (об интерфейсе CORBA::Object говорилось в разделе 6.4.1).
При создании и заполнении списка аргументов (параметр arg_list) вы можете использовать любой из способов, о которых говорилось ранее - либо применять методы NVList::add, либо методы Request::add_XX_arg(), но не оба сразу. При использовании методов add_XX_arg() параметр arg_list предварительно должен быть установлен в значение NULL/null. Наверное, единственный из оставшихся параметров, нуждающийся в дополнительных пояснениях - это параметр req_flags. Этот параметр лучше устанавливать в значение CORBA::OUT_LIST_MEMORY при наличии out-аргументов и не устанавливать в противном случае. При установленном значении OUT_LIST_MEMORY динамическая память, выделенная для хранений значения out-аргументов, освобождается вместе с уничтожением самого объекта NVList. Использование C++ При отображении на С++ предусмотрены две версии метода Object::create_request():
Вы можете выбрать любую из них, но на практике программисты на C++ обычно используют дополнительный метод, который отсутствует на уровне IDL, но является частью правил отображения интерфейса Object на C++. Это метод Object::_request(). Единственным его аргументом является строка, содержащая имя вызываемого метода серверного объекта, например:
После этого происходит создание списка аргументов, собственно вызов удаленного метода и анализ результата. Вызовы удаленных методовВ настоящий момент, пока (вместе с CORBA 3.0) не утверждена спецификация Messaging Service - спецификация асинхронного взаимодействия клиентов и серверов - для статического режима CORBA возможен только синхронный вызов удаленных методов (oneway-методы мы по ряду причин в расчет не принимаем). Это означает, что поток клиента, из которого был выполнен удаленный запрос, блокируется до завершения этого запроса. Конечно, такой способ далеко не всегда удобен (а иногда и просто неприемлем). Опытный программист может возразить, что такую проблему решить не составляет труда: нужно просто создать отдельный поток и вызвать синхронный метод из него - пусть ждет завершения, сколько надо. Тем не менее, желательно иметь более простые средства выполнения асинхронных вызовов и обработки их результатов. Сейчас это возможно только при использовании DII. Спецификация предусматривает несколько методов выполнения асинхронных вызовов. Ниже приводятся соответствующие фрагменты IDL-деклараций:
Метод Request::invoke() выполняет синхронный вызов. Последовательность действий (на C++) выглядит примерно так:
Метод Request::send_deferred() (и связанные с ним методы Request::get_response() и poll_response() предполагают другой подход:
Периодически вызываемый метод Request::poll_response() возвращает true, если вызов удаленного метода завершен. В этом случае вы вызываете метод Request::get_response() и анализируете полученное значение. Если вы вызовете метод get_response() перед тем, как вызов удаленного метода будет завершен, то поток, обратившийся к этому методу, будет блокирован до получения ответа от сервера. Спецификация OMG говорит, что результат повторного обращения к методу invoke() или send_deferred() не определен. Та же неопределенность возникает, если вы вызываете эти методы для объекта, который уже был использован в качестве аргумента при вызове метода CORBA::send_multiple_requests_deferred(). Методы
позволяют посылать в асинхронном режиме и анализировать состояние сразу для нескольких объектов типа Request. Метод get_next_response() возвращает объект Request для очередного завершенного вызова. Порядок возврата значений зависит от того, какой из методов на стороне сервере был выполнен быстрее, поэтому программисту необходимо определить, ответом на какой запрос является полученный объект Request. DSIСпецификация DSI содержится в документе 99-07-12.pdf. Этот документ содержит всего 6 страниц. Как ни прост интерфейс DSI, этого все-таки маловато – автор рекомендует дополнительно обращаться к спецификациям отображения IDL на конкретные языки программирования. Интерфейс DSI (конечно, «интерфейс» не в смысле «IDL-интерфейс») выполняет на стороне сервера ту же роль, что и интерфейс DII на стороне клиента. При получении клиентского запроса (неважно, каким образом сформированного) на стороне сервера выполняются вполне стандартные действия до тех пор, пока поученный запрос не «доберется» до нужного серванта. Каким образом – статически или динамически – обрабатывать этот запрос, программист решает на стадии создании класса серванта. При работе в статическом режиме вы используете сгенерированный компилятором с IDL класс скелетона, либо создавая на его основе класс серванта (в случае наследования), либо рассматривая этот класс скелетона как класс серванта (в случае делегирования). В обоих случаях вы пишете код для нескольких методов, определенных на уровне IDL. DSI использует другой подход. Вы создаете класс серванта именно для динамической обработки запросов. Главная идея DSI – использование одного-единственного метода для обработки всех клиентских запросов, адресованных данному объекту. Такой метод (спецификация OMG называет его DIR – Dynamic Implementation Routine) вызывается самим ORB’ом. В качестве единственного INOUT-аргумента ему передается объект типа ServerRequest. Задача программиста – написать код для DIR, который и будет обслуживать все поступающие запросы клиентов. Объект ServerRequestЕсли при использовании DII вы работаете, в основном, с объектом CORBA::Request, то «сердцем» DSI является объект CORBA::ServerRequest. Программисту нет никакой необходимости создавать его - это задача ORB – поэтому использовать интерфейс ServerRequest даже проще, чем интерфейс Request. Объект типа ServerRequest содержит всю нужную информацию о полученном вызове – имя метода, а также имена, типы и значения его аргументов. Задача программиста – считать значения аргументов типа IN и INOUT, выполнить необходимые действия, а затем установить значения выходных аргументов (типа INOUT и OUT), а также (если надо) значение результата. Кроме того, вы можете создать и поместить в объект ServerRequest объект типа «исключение».
Единственное, что может вызвать некоторые вопросы - это метод arguments(). Вы вызываете это метод, чтобы считать в заранее подготовленный вами список аргументов их текущие значения. Аргумент nv типа NVList имеет атрибут INOUT - сначала вы считываете данные (вызывая метод arguments()), а затем тем или иным образом в этом же объекте устанавливаете значения выходных параметров. Метод set_exception(), хотя формально получает аргумент любого типа, должен получать только значения типа <исключения> (другими словами, TCKind для TypeCode аргумента должен быть равен) tk_except. В противном случае при попытке установить значение исключения будет возбуждена исключительная ситуация CORBA::BAD_PARAM. Если вы установили исключение, которое не предусмотрено в списке исключений для данного метода, то спецификация разрешает как возбуждение исключения BAD_PARAM при попытке его установки в объекте ServerRequest, так и возбуждение ORB'ом исключения UNKNOWN_EXCEPTION на стороне клиента. Отображение ServerRequest на C++Спецификация OMG в качестве стандартного объявляет следующий код:
Метод operation() возвращает имя вызванной клиентом операции. Если клиент вызвал операцию, используя не IDL-метод, а IDL-атрибут, то имя операции получается конкатенацией префиксов _get_ /_set_ и имени атрибута. Здесь надо сказать несколько слов об особенностях отображения для этого интерфейса. Спецификация его сильно изменилась по сравнению с версией CORBA 2.0, в которой впервые появилась концепция DSI. Наличие этих изменений, хотя они и не носят принципиального характера (кроме схемы управления памятью), может вызвать затруднение у начинающего CORBA-программиста - реализации поддерживают как старые, так и новые методы, которые, хотя и делают, в общем, одно и то же, но почему-то имеют разные имена или другой способ передачи своих аргументов. Например, <старый> метод result() в качестве аргумента получает указатель на CORBA::Any, а его <новый> аналог - set_result() - сам объект CORBA::Any. Реализация отображения при работе с VisiBroker выглядит так (полный текст вы можете найти в файле ...\vbroker\include\srequest.h):
Простейший анализ приведенного фрагмента показывает, что в использовании <стиля CORBA 2.0> нет никакой необходимости. И еще одно изменение по сравнению с CORBA 2.0: объект ServerRequest является владельцем своих объектов, поэтому не надо вызывать delete или free() для результатов методов op_name() и operation(). Тип сервантаКласс серванта, используемого при работе в режиме DSI, должен быть создан как класс, производный либо от CORBA::DynamicImplementation (этот подход объявлен устаревшим), либо от PortableServer::DynamicImplementation. Для пользователя особо большой разницы нет - и в том, и в другом случае необходимо знать (и явно указать) RepositoryID для самого производного в иерархии наследования интерфейса (при работе с C++)., только сделано это немного по-разному. При использовании CORBA::DynamicImplementation вы указываете RepositoryID (и некоторые другие параметры) как аргументы конструктора, а при использовании PortableServer::DynamicImplementation вы должны вернуть нужные значения как результат метода, объявленного в C++ как чисто виртуальный. Разумеется, при создании новых программ следует использовать класс PortableServer::DynamicImplementation, а не CORBA::DynamicImplementation Поскольку этот класс является классом серванта, т.е. заведомо зависит от языка программирования, то для него не существует IDL-объявления и спецификация определена на уровне конкретного языка программирования. CORBA не предусматривает никаких специальных процедур регистрации DSI-серванта - создание сервантов и активация CORBA-объектов никак не зависят от того, на базе какого класса создан ваш класс серванта. Все берет на себя объектный адаптер, т.е. POA.
Использование C++
Метод _this() возвращает объектную ссылку для объекта, которому адресован данный вызов. Вызывать этот метод можно в контексте обработки конкретного запроса, обслуживаемого в режиме DSI. Для VisiBroker этот метод отсутствует в класса PortableServer::DynamicImplementation просто потому, что он уже реализован в его базовом классе - PortableServer::ServantBase. Два остальных метода вы должны переопределить сами. Метод invoke() и есть та самая DIR, код которой реализует всю необходимую функциональность серверного объекта. Метод _primary_interface() должен возвращать <самый производный> в иерархии наследования интерфейс, методы которых реализованы в методе invoke(). Как invoke(), так и _primary_interface() НЕ должны вызываться явно программистом - результат такого вызова не определен.
| |
|
| |