Наталия Елманова
Компьютер Пресс - CD, 1999, N 5
Copyright N.Elmanova & ComputerPress Magazine.
Данная статья посвящена возможностям C++Builder 4, связанным с созданием распределенных многоплатформенных систем. Она же является второй частью опубликованного в предыдущем выпуске журнала обзора, посвященного возможностям С++Builder 4.
Организация распределенных вычислений за последние два года стала весьма "модным" направлением в области проектирования информационных систем. Весьма популярны распределенные системы на основе COM и MIDAS (возможность создания которых появилась в предыдущей версии С++Builder). Однако нередко (особенно в случае повышенных требований к надежности подобного рода систем) возникает потребность использовать в качестве серверной части такой распределенной системы приложения, функционирующие на платформе, отличной от Windows (что исключает использование COM как специфичной для Windows технологии).
В этой связи следует заметить, что C++Builder позволяет генерировать код для таких переносимых приложений (естественно, за счет отказа от использования COM, VCL и Windows API). Соответственно в качестве технологии распределенных вычислений в этом случае используется многоплатформенная технология, не использующая специфику Windows. В последнее время наиболее часто для этой цели используется CORBA (Common Object Request Broker Architecture), спецификация которой создана консорциумом Object Management Group. В комплект поставки C++Builder Enterprise входит набор утилит и сервисов, предстаавляющих собой одну из реализаций этой спецификации - Inprise VisiBroker.
Данная часть обзора возможностей C++Builder 4 описывает создание сервера функциональности с использованием VisiBroker, переносимого на другие платформы, а также создание клиентских приложений для него (в том числе переносимых на другип платформы.
Создадим приложение, производящее, к примеру, сложные научные вычисления (в нашем примере - вычисление значения некоторой функции, представляющей собой полином третьей степени). В реальной жизни вместо этого расчета данная функция может реализовывать любую другую функциональность - доступ к данным, обработку сигналов, поступающих с аппаратуры, генерацию отчетов, и др.
Пусть наше приложение имеет главную форму следующего вида (рис. 1):
Рис. 1. Интерфейс приложения, подлежащего разбиению на сервер и клиента
В нашем примере компонент TChart содержит одну серию (ее следует добавить вручную при создании формы).
Создадим обработчик события, связанный с нажатием на кнопку с надписью "График", и в этом же модуле создадим реализацию нашей функции:
//--------------------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "Unit1.h" //--------------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //--------------------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //--------------------------------------------------------------------------- void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i; double x1,y; for (i=1;i<60;i++) { x1=0.1*float(i-13); y=fun1(x1); Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite); } } double fun1(double x) { double r=x*x*x-5*x*x+3*x+5; return(r); } //---------------------------------------------------------------------------
Результатом работы такого приложения будет график нашего полинома, появляющийся при нажатии на кнопку "График" (рис. 2):
Рис. 2. То же приложение на этапе выполнения
Это обычное Windows-приложение, содержащее в себе и пользовательский интерфейс, и функциональность, связанную с проведением расчетов (вычисление значений функции).
Теперь попробуем разбить это приложение на две части, отделив пользовательский интерфейс (кнопки и компонент TChart) от функциональности. Иными словами, создадим сервер функциональности, вычисляющий значение функции, и клиентское приложение, использующее этот сервер как поставщика результатов расчетов.
Итак, создадим CORBA-сервер, осуществляющий проведение расчетов. Сразу же заметим, что подобного рода серверы, как правило, не нуждаются в пользовательском интерфейсе. Поэтому мы вполне можем создать его как консольное приложение. Если при этом мы откажемся от использования VCL, код полученного сервера может быть скомпилирован любым другим компилятором C++ (в том числе, естественно, и компилятором для другой платформы). Консольные приложения есть единственный тип приложений, существующий для любых платформ, в отличие от приложений с графическим интерфейсом пользователя - последние требуют графических библиотек или операционных систем с GUI, и на все эти графические библиотеки и функции API подобных операционных систем нет никаких стандартов, в отличие от самого языка С++.
Иными словами, сервер может быть создан переносимым на другие платформы, что и будет сделано.
Для создания сервера выберем из репозитария объектов со страницы Multitier пиктограмму CORBA server (рис. 3):
Рис. 3. Выбор пиктограммы CORBA Server из репозитария объектов
После выбора пиктограммы в диалоговой панели CORBA Server Wizard выберем в качетсве типа приложения Console Application и отключим возможность использования VCL. Это позволит нам создать переносимый на другие платформы код (fig. 4):
Рис. 4. CORBA Server Wizard
Первое, с чего следует начинать создание распределенной системы, - это описание интерфейсов сервера. С этой целью используется язык IDL (Interface Definition Language), являющийся, по существу, стандартом для подобного рода описаний, не зависящим от языков программирования и платформ. Отметим, что существует несколько диалектов IDL (COM IDL, CORBA IDL, DCE IDL), имеющих некоторые различия. Естественно, мы будем использовать CORBA IDL. Так как мы еще не создали никакого IDL-описания, выберем опцию Add New IDL File. После нажатия на кнопку OK в окне редактора кода появится пустая страница для ILD-описания, куда можно ввести описание интерфейса нашего сервера.
interface b1 { double fun1(in double x); } ;
Далее следует скомпилировать IDL-файл. В действительности это не компиляция, а кодогенерация, в результате которой мы получим два модуля, fun1_s.cpp and fun1_c.cpp, содержащие stub-код и skeleton-код.
Что представляют собой stub-объект и skeleton-объект?
Все способы взаимодействия между серверами функциональности и их клиентами основаны на механизме вызовов удаленных процедур (RPC - Remote Procedure Calls) и маршалинга, представляющего солой обмен пакетами данных между объектом внутри клиента (stub-объектом) и объектом внутри сервера (skeleton-объектом). Skeleton-объект является представителем клиента внутри адресного пространства сервера. Обращаясь к нему, сервер "думает", что он имеет дело с локальным объектом. Stub-объект является представителем сервера внутри адресного пространства клиентского приложения, поэтому последнее также "думает", что вызывает методы локального объекта. В действительности так оно и есть, однако вместо истинной реализации (в нашем случае - вычисления значений функции) методы этого объекта осуществляют вызовы удаленных процедур и обмен пакетами данных с сервером (рис. 5):
Рис. 5. Взаимодействие сервера и клиента
Теперь можно вспомнить о реализации нашей функции. Для этого выберем со страницы Multitier репозитария объектов пиктограмму CORBA Object Implementation. В диалоговой панели CORBA Object Implementation Wizard выберем имя интерфейса (b1), а также определим имя модуля, содержащего реализацию, и имя класса CORBA-объектов. В данном примере мы описшем объект, экземпляр которого создается в момент старта сервера, так что сервер сразу же сможет принимать обращения клиентов (рис. 6):
Рис. 6. CORBA Object Implementation Wizard
Мы можем просмотреть изменения, внесенные в наше приложение этим экспертом. В частности, имеет смысл внести в код реализации нашего метода строки, ответственные за проведение расчетов (рис. 7):
Рис. 7. Добавление кода рализации функции в сгенерированный модуль
Теперь можно скомпилировать проект и закрыть его.
Перед созданием клиента мы должны запустить VisiBroker Smart Agent - службу каталогов CORBA, позволяющую осуществить доступ к CORBA-серверам. Теперь можно запустить наш сервер (желательно отдельно от среды разработки).
Теперь можно создавать клиенские приложения.
Для начала создадим клиентское приложение, представляющее собой обычное Windows-приложение с интерфейсом, похоржим на представленный на рис.2. Для этого выберем пиктограмму CORBA Client со страницы Multitier репозитария объектов C++Builder.
В этом примере мы будем использовать раннее связывание клиента с сервером (в этом случае можно достичь максимального быстродействия). Для этого выберем из меню среды разработки опцию "Edit/Use CORBA object". В соответствующем эксперте мы должны добавить к проекту тот же самый IDL-файл, что был создан при создании сервера, а также ответить на вопросы об именах объектов и переменных, в частности, об имени нового свойства формы, к которому следует обращаться, если нужно вызвать метод сервера (рис. 8):
Рис. 8. Определение свойств для доступа к CORBA-объекту в клиентском приложении
Далее можно создать интерфейс, сходный с приведенным на рис. 1, и создать обработчик события, связанного с нажатием на кнопку "График":
void __fastcall TForm1::BitBtn1Click(TObject *Sender) { int i; double x1,y; for (i=1;i<60;i++) { x1=0.1*float(i-13); y=b1_1->fun1(x1); Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite); } }
В результате получим тот же график, что и на рис.2.
Теперь создадим клиентское приложение, переносимое на другие платформы. Это должно быть консольное приложение, не использующее VCL. В этом случае при создании клиента мы должны выбрать опцию "Console Application" и отменить опцию "Enable VCL" .
Все остальные действия похожи на предыдущие, за исключением создания пользовательского интерфейса. Простейший способ создания пользовательского интерфейса в данном случае - вывести результаты расчетов на экран и поместить этот код непостредственно в файл проекта:
//--------------------------------------------------------------------------- #include <corbapch.h> #pragma hdrstop //--------------------------------------------------------------------------- #include "fun1_c.hh" #include <corba.h> #include <condefs.h> USEIDL("corba\corba_rus\fun1.idl"); USEUNIT("corba\corba_rus\fun1_c.cpp"); USEUNIT("corba\corba_rus\fun1_s.cpp"); //--------------------------------------------------------------------------- #pragma argsused int main(int argc, char* argv[]) { try { // Initialize the ORB and BOA CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); CORBA::BOA_var boa = orb->BOA_init(argc, argv); a1_var a1_1 = a1::_bind("a1Obj"); cout<<"Our function table \n"; int i; double x1,y; for (i=1;i<271;i++) { x1=0.1*float(i); y=a1_1->fun1(x1); cout<<x1<<" "<<y<<"\n"; } } catch(const CORBA::Exception& e) { cerr << e << endl; return(1); } return 0; } //---------------------------------------------------------------------------
В этом случае мы получим таблицу с результатами расчетов непосредственно на экране консольного приложения.
Отметим, что данное приложение можно скомпилировать любым компилятором С++, в том числе и компилятором для платформы, отличной от Windows.
Серверы доступа к данным являются одним из наиболее популярных типов серверов middleware. Как создать переносимый сервер доступа к данным?
Наиболее популярный способ создания таких серверов - использование MIDAS и создание удаленных модулей данных как COM- или CORBA-объектов. В этом случае мы получим COM или CORBA-сервер, но это будет Windows-приложение, так как оно использует VCL, основанную на Windows API, и BDE, которая также представляет собой набор Windows-библиотек. Создание таких серверов более подробно будет рассмотрено в следующей статье данного цикла.
Если нам нужен переносимый сервер доступа к данным, он не должен использовать ни VCL, ни BDE. Как быть в этом случае?
Вспомним, что вся функциональность сервера доступа к данным - это набор SQL-запросов, и ничего более. Для MIDAS-серверов генератором запросов является BDE. Но выполнить запрос можно и другим способом! Например, всегда есть возможность использования низкоуровневого API клиентских частей серверных СУБД (например, Oracle Call Interface). Для большинства таких СУБД такие API существуют для многих платформ и содержат одни и те же функции. Использование таких API может показаться несколько утомительным, но в целом оно ненамного сложнее, чем написание других функций.
Российское представительство Inprise:
Тел. 7(095)238-36-11
e-mail: info@inprise.ru
http://www.inprise.ru