Распределенные вычисления и технологии Inprise
Часть 5. Cоздание CORBA-серверов, переносимых на другие платформы, с помощью С++Builder 4
Данная статья посвящена возможностям C++Builder 4, связанным с созданием распределенных многоплатформенных систем. Это вторая часть опубликованного обзора, посвященного возможностям С++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):
В нашем примере компонент 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):
Это обычное Windows-приложение, содержащее в себе и пользовательский интерфейс, и функциональность, связанную с проведением расчетов (вычисление значений функции).
Теперь попробуем разбить это приложение на две части, отделив пользовательский интерфейс (кнопки и компонент TChart) от функциональности. Иными словами, мы создадим сервер функциональности, вычисляющий значение функции, и клиентское приложение, использующее этот сервер как поставщика результатов расчетов.
Создание переносимого сервера функциональности
Итак, создадим CORBA-сервер, осуществляющий проведение расчетов. Сразу же заметим, что подобного рода серверы, как правило, не нуждаются в пользовательском интерфейсе, поэтому его вполне можно создать как консольное приложение. Если при этом мы откажемся от использования VCL, код полученного сервера может быть скомпилирован любым другим компилятором C++ (в том числе, естественно, и компилятором для другой платформы). Консольные приложения — единственный тип приложений, существующий для любых платформ, в отличие от приложений с графическим интерфейсом пользователя. (Последние требуют графических библиотек или операционных систем с GUI, и на все эти графические библиотеки и функции API подобных операционных систем нет никаких стандартов, в отличие от самого языка С++).
Иными словами, сервер может быть создан переносимым на другие платформы, что и будет сделано.
Для создания сервера выберем из репозитария объектов со страницы Multitier пиктограмму CORBA server (рис. 3):
После выбора пиктограммы в диалоговой панели CORBA Server Wizard выберем в качестве типа приложения Console Application и отключим возможность использования VCL. Это позволит нам создать переносимый на другие платформы код:
Первое, с чего следует начинать создание распределенной системы, — это описание интерфейсов сервера. С этой целью используется язык 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):
Теперь можно вспомнить о реализации нашей функции. Для этого выберем со страницы Multitier репозитария объектов пиктограмму CORBA Object Implementation. В диалоговой панели CORBA Object Implementation Wizard выберем имя интерфейса (b1), а также определим имя модуля, содержащего реализацию, и имя класса CORBA-объектов. В данном примере мы опишем объект, экземпляр которого создается в момент старта сервера, так что сервер сразу же сможет принимать обращения клиентов (рис. 6):
Мы можем просмотреть изменения, внесенные в наше приложение этим экспертом. В частности, имеет смысл внести в код реализации нашего метода строки, ответственные за проведение расчетов (рис. 7):
Затем можно скомпилировать проект и закрыть его.
Перед созданием клиента мы должны запустить VisiBroker Smart Agent — службу каталогов CORBA, позволяющую осуществить доступ к CORBA-серверам. Теперь можно запустить наш сервер (желательно отдельно от среды разработки).
Можно создавать клиенские приложения.
Создание клиента с графическим пользовательским интерфейсом
Для начала создадим клиентское приложение, представляющее собой обычное Windows-приложение с интерфейсом, похожим на представленный на рис.2. Для этого выберем пиктограмму CORBA Client со страницы Multitier репозитария объектов C++Builder.
В этом примере мы будем использовать раннее связывание клиента с сервером (в этом случае можно достичь максимального быстродействия). Для этого выберем из меню среды разработки опцию «Edit/Use CORBA object». В соответствующем эксперте мы должны добавить к проекту тот же самый IDL-файл, что был создан при создании сервера, а также ответить на вопросы об именах объектов и переменных, в частности, об имени нового свойства формы, к которому следует обращаться, если нужно вызвать метод сервера (рис. 8):
Далее можно создать интерфейс, сходный с приведенным на рис. 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
КомпьютерПресс 5'1999