- Какая разница межу Win95 и рос. рублем?
- Никакой. Каждый день падает, и раз в три года выходит новая версия.
Известно расшифровывается как Common Object Request Broker Architecture, и представляет собой объектно-ориентированную архитектуру связи между клиентом и сервером. Приложения на основе CORBA состоят из двух частей: CORBA-сервер и CORBA-клиент. И сервер и клиент могут быть реализованы на любом языке и запущены на любой платформе. CORBA представляет собой независимую от языка программирования и операционной системы технологию. Это возможно, так как все параметры и типы, возвращаемые методами транспортируются через сеть в специально универсальном формате. А вот для того чтобы сервер и клиент понимали друг друга необходимо определить интерфейс CORBA-сервера, при этом необходимо учитывать независимость от операционной системы и языка на котором происходит разработка приложения. Для этой цели и был разработан интерфейс общения клиента и сервера Interface Definition Language (IDL). Используя IDL, можно определять специфические объекты с присущими им методами и свойствами. Данные методы подобны функциям, которые могут быть вызваны клиентом, и которые могут быть реализованы сервером. В Delphi например для реализации подобного интерфейса прийдеться компилировать специализированный IDL-файл. Вообще же преобразование из стандартного внутреннего стандарта языка программирования в подобный переносимый формат обозначают как marshalling. Обратный процесс преобразования из универсального формата в стандарт понятный программе называется unmarshalling.
Особенности установки VisiBroker
В стандартный набор Delphi 6 Enterprise входит поддержка CORBA в двух вариантах. Во время инсталляции Delphi необходимо выбрать поддержку VisiBroker 3.3 или VisiBroker 4. Это связано с тем, что VisiBroker 3.3 и VisiBroker 4 не могут быть установлены одновременно. В противном случаи, возможны проблемы при работе с Delphi 6. В более ранней версии VisiBroker 3.3 существует полезная возможность динамического вызова интерфейса. В VisiBroker 4 это функциональная особенность не поддерживается. Несмотря на это VisiBroker 4 представляет собой более совершенную реализацию стандарта CORBA, поэтому вопросы, связанные с предыдущей версией VisiBroker 3.3 рассматриваться не будут.
TicTacToe
А теперь рассмотрим возможности технологии CORBA в Delphi, с использованием VisiBroker 4, на примере практического создания небольшой программы. Ниже представлена конструкция IDL известной всем игры в "крестики-нолики", которая имеет гордое английское название TicTacToe. Модуль TTT с интерфейсом TicTacToe реализуется CORBA сервером, и CORBA клиент может соединяться с сервером во время игры.
module TTT
{
interface TicTacToe
{
typedef long TGame;
typedef long TPlace; // 0,1..9enum TPlayer
{
user,
computer,
none
};
exception PlaceTaken
{
TPlayer TakenBy;
};
TGame NewGame();
void MakeMove(in TGame Game, in TPlayer player, in TPlace Place)
raises(PlaceTaken);
TPlace NextMove(in TGame Game, in TPlayer player);
TPlayer IsWinner(in TGame Game);
TPlayer GetValue(in TGame Game, in TPlace Place);
};
};
Модуль TTT имеет интерфейс TicTacToe. Это интерфейс содержит определения ряда типов (видимы только внутри области интерфейса), определение исключения и определения ряда методов. Обратите внимание, что метод MakeMove может вызывать исключение PlaceTaken. Исключение PlaceTaken - фактически структура, которая также будет обработана.
IDL2Pas Wizard
Для использования IDL файла, его необходимо скомпилировать для Server Skeletons и Client Stubs. Для этого используется файл IDL2Pas, который является частью VisiBroker for Delphi. Но более простой путь, использовать мастера CORBA Server Application и CORBA Client Найти их можно в File | New | Other, закладка Corba.
При выборе мастера CORBA Server Application появится окно и вы можете добавить туда IDL.
Закладка Options содержит ряд специфических установок, который будут выполнены в командной строке IDL2Pas. Обратите внимание на опцию "Overwrite Implementation Units", она не установлена по умолчанию. Кстати, при повторной компиляции данную опцию необходима снять - иначе созданная до этого IDL-файл будет перекомпилировать.
Установки закладки Options мастера IDL2Pas хранятся в секции [idl2pas] файла defproj.dof, находящегося в директории Delphi6\bin, и все выбранные установки будут использованы при следующей загрузки мастера IDL2Pas.
CORBA Server Skeleton
После того как вы нажмете на кнопку ОК в CORBA Server Application Wizard, будут сгенерировано несколько файлов: TTT.IDL будет использован для генерации файла TTT_c.pas (client stubs и helpers), TTT_i.pas будет содержать определения интерфейса, TTT_impl.pas будет использован для реализации интерфейса и TTT_s.pas содержащий server skeletons. Далее можно будет только модифицировать файл TTT_impl.pas, тогда как другие могут быть сгенерированы заново с помощью IDL2Pas.
Interface Definitions (TTT_i.pas)
Файл интерфейса ТТТ TTT_i.pas содержит определение интерфейса TicTacToe. Причиной использования в определениях типов префикса TicTacToe_ является использование этих типов внутри интерфейса. Если мы определяем их вне интерфейса TicTacToe, то транслироваться они буду без префикса TicTacToe_.
unit TTT_i;
interfaceuses CORBA;
type
TicTacToe_TPlayer = (user, computer, none);
type
TicTacToe = interface;
TicTacToe_TGame = Integer;
TicTacToe_TPlace = Integer;
TicTacToe = interface ['{50B30FC5-4B18-94AB-1D5F-4148BB7467B4}']
function NewGame: TTT_i.TicTacToe_TGame;
procedure MakeMove (const Game: TTT_i.TicTacToe_TGame;
const player: TTT_i.TicTacToe_TPlayer;
const Place: TTT_i.TicTacToe_TPlace);
function NextMove (const Game: TTT_i.TicTacToe_TGame;
const player: TTT_i.TicTacToe_TPlayer):
TTT_i.TicTacToe_TPlace;
function IsWinner (const Game: TTT_i.TicTacToe_TGame):
TTT_i.TicTacToe_TPlayer;
function GetValue (const Game: TTT_i.TicTacToe_TGame;
const Place: TTT_i.TicTacToe_TPlace):
TTT_i.TicTacToe_TPlayer;
end;
Можно заметить, что здесь не видны определения исключения. Оно появится в файле Client Stub TTT_c.pas.
Client Stubs and Helpers (TTT_c.pas)
Файл TTT_s.pas содержит не только Client Stubs, но и классы helper. Конечно, лучше было бы если Client Stubs был включен в TTT_c.pas, а классы helper в TTT_h.pas. Но раз все обстоит не так, придется включить файл TTT_c.pas в предложение uses нашего файла Server Skeleton TTT_s.pas.
На что следует обратить внимание, так это на декларацию исключения ETicTacToe_PlaceTaken, которое имеет два конструктора: по умолчанию без аргументов и с одним аргументом TakenBy, который автоматически инициализируя исключение.
Server Skeletons (TTT_s.pas)
Класс TticTacToeSkeleton единственный класс, который мы используем для создания экземпляра CORBA Server TicTacToe, принимающего в качестве аргументов имя InstanceName и экземпляр интерфейса TicTacToe .
Файл TTT_impl.pas, единственный файл который редактируется и в который вставляется код реализации CORBA сервера. Тут использован модуль Magic, который использовался для ITicTacToe web service в Delphi 6.
unit TTT_impl;
interfaceuses
SysUtils, CORBA, TTT_i, TTT_c,
Magic; // implementation of Magic.TTicTacToetype
TTicTacToe = class(TInterfacedObject, TTT_i.TicTacToe)
protected
TTT: Magic.TTicTacToe;
publicconstructor Create;
function NewGame:TTT_i.TicTacToe_TGame;
procedure MakeMove(const Game: TTT_i.TicTacToe_TGame;
const player: TTT_i.TicTacToe_TPlayer;
const Place: TTT_i.TicTacToe_TPlace);
function NextMove(const Game: TTT_i.TicTacToe_TGame;
const player: TTT_i.TicTacToe_TPlayer):
TTT_i.TicTacToe_TPlace;
function IsWinner(const Game: TTT_i.TicTacToe_TGame):
TTT_i.TicTacToe_TPlayer;
function GetValue(const Game: TTT_i.TicTacToe_TGame;
const Place: TTT_i.TicTacToe_TPlace):
TTT_i.TicTacToe_TPlayer;
end;
implementationconstructor TTicTacToe.Create;
begininherited;
{ *************************** }{ *** User code goes here *** }{ *************************** }
TTT := Magic.TTicTacToe.Create;
end;
function TTicTacToe.NewGame: TTT_i.TicTacToe_TGame;
begin{ *************************** }{ *** User code goes here *** }{ *************************** }
Result := TTT.NewGame
end;
procedure TTicTacToe.MakeMove(const Game: TTT_i.TicTacToe_TGame;
const player: TTT_i.TicTacToe_TPlayer;
const Place: TTT_i.TicTacToe_TPlace);
begin{ *************************** }{ *** User code goes here *** }{ *************************** }
TTT.MakeMove(Game, Ord(Player), Place);
end;
function TTicTacToe.NextMove(const Game: TTT_i.TicTacToe_TGame;
const player: TTT_i.TicTacToe_TPlayer):
TTT_i.TicTacToe_TPlace;
begin{ *************************** }{ *** User code goes here *** }{ *************************** }
Result := TTT.NextMove(Game, Ord(Player))
end;
function TTicTacToe.IsWinner(const Game: TTT_i.TicTacToe_TGame):
TTT_i.TicTacToe_TPlayer;
begin{ *************************** }{ *** User code goes here *** }{ *************************** }
Result := TTT_i.TicTacToe_TPlayer(TTT.IsWinner(Game))
end;
function TTicTacToe.GetValue(const Game: TTT_i.TicTacToe_TGame;
const Place: TTT_i.TicTacToe_TPlace):
TTT_i.TicTacToe_TPlayer;
begin{ *************************** }{ *** User code goes here *** }{ *************************** }
Result := TTT_i.TicTacToe_TPlayer(TTT.GetValue(Game, Place))
end;
initializationend.
Теперь мы имеем на руках практически все части для создания приложения с использованием технологии CORBA . Пусть даже это и игрушка.
CORBA Server Application
Помимо сгенерированных файлов должен же быть и сам проект с главным модулем формы. Сохранив проект как TTTServer.dpr а модуль главной формы как GameUnit. Если заменить фактический ТТТ на объект skeleton типа TicTacToe, код модуля будет выглядеть следующим образом. Тут следует обратить внимание на использование четырех модулей в предложении uses секции interface:
unit GameUnit;
interfaceuses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Corba, TTT_i, TTT_c, TTT_s, TTT_impl;
type
TForm1 = class(TForm)
private{ private declarations }protected{ protected declarations }
TTT: TicTacToe; // skeleton objectprocedure InitCorba;
public{ public declarations }end;
var
Form1: TForm1;
implementation{$R *.DFM}procedure TForm1.InitCorba;
begin
CorbaInitialize;
TTT := TTicTacToeSkeleton.Create('TTT', TTicTacToe.Create);
BOA.ObjIsReady(TTT as _Object)
end;
end.
Вызов InitCorba будем производить из обработчика события OnCreate формы:
procedure TForm1.FormCreate(Sender: TObject);
begin
InitCorba;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
TTT := nil;
end;
Можно сделать вывод, что сервер лучше иметь в виде консольного приложения. Ниже оно представлено. Там используется старомодный оператор writeln, с помощью которого и сообщается пользователю о запуске новой игры. Консольное приложение использует те же самые элементы, что и визуальная версия, но в конце добавлен вызов BOA.ImplIsReady.
program TTTCServer;
{$APPTYPE CONSOLE}uses
SysUtils, CORBA, TTT_c, TTT_i, TTT_s, TTT_impl;
var
TTT: TicTacToe; // skeleton objectbegin
writeln('CorbaInitialize');
CorbaInitialize;
writeln('TTicTacToe.Create');
TTT := TTicTacToeSkeleton.Create('TTT', TTicTacToe.Create);
writeln('BOA.ObjIsReady');
BOA.ObjIsReady(TTT as _Object);
writeln('BOA.ImplIsReady');
BOA.ImplIsReady
end.
Теперь можно приступать к созданию CORBA-клиента.
CORBA Client Application
Для создания CORBA-клента так же можно использовать CORBA Wizard. Проделываем тоже самое что мы делали для формирования сервера CORBA. Только не следует создавать снова TTT_impl.pas. Кроме уже описанных выше файлов, в наличие есть и файл главной формы и файл проекта. Сохраним их как MainForm.pas и TTTClient.dpr. Модуль MainForm.pas содержит подсказки, чтобы показать вам как создать экземпляр CORBA сервера:
unit MainForm;
interfaceuses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, Corba;
type
TForm1 = class(TForm)
private{ private declarations }protected{ protected declarations }// declare your Corba interface variables like this// Acct : Account;procedure InitCorba;
public{ public declarations }end;
var
Form1: TForm1;
implementation{$R *.DFM}procedure TForm1.InitCorba;
begin
CorbaInitialize;
// Bind to the Corba server like this// Acct := TAccountHelper.bind;end;
end.
Здесь нужно вызвать метод InitCorba из обработчика OnCreate формы. Надо включить в предложение uses модуля MainForm модули TTT_c, TTT_i и TTT_impl, без которых не будут доступны классы helpers. Непосредственно же объявление переменной типа интерфейса CORBA, может выглядеть следующим образом:
private
TicTacToe: TicTacToe;
Фактическое связывание интерфейса TicTacToe с CORBA сервером реализуется следующим образом:
TicTacToe := TTicTacToeHelper.bind;
Теперь можно использовать TicTacToe как обыкновенный класс, включающий поддержку Code Insight.
Action!
Внизу представлен небольшой компонент, основанный на оригинальном компоненте игры TicTacToe. Результирующий код, реализован в MagicTTT.pas - содержит в предложении uses модули TTT_i, TTT_c and TTT_impl и создает экземпляр интерфейса TicTacToe:
unit MagicTTT;
interfaceuses
SysUtils, Classes, Controls, StdCtrls, Dialogs, TTT_c, TTT_i, TTT_impl;
const
NoneID = 0;
UserID = 1;
CompID = 2;
const
chrUser = 'X';
chrComp = '@';
const
FirstPlace = 1;
LastPlace = 9;
type
TPlace = FirstPlace..LastPlace;
type
TTTTControl = class(TWinControl)
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
private
TicTacToe: TicTacToe;
private{ 9 game buttons }
Game: Integer;
Button: array[TPlace] of TButton;
procedure ButtonClick(Sender: TObject);
procedure ComputerMove;
procedure UserMove(Move: TPlace);
private{ start button }
TheStartButton: TButton;
procedure StartButtonClick(Sender: TObject);
private{ game properties }
FStartButton: Boolean;
FUserStarts: Boolean;
FUserChar: Char;
FCompChar: Char;
protected{ design interface }procedure SetStartButton(Value: Boolean);
procedure SetUserStarts(Value: Boolean);
procedure SetUserChar(Value: Char);
procedure SetCompChar(Value: Char);
function GetCaption: string;
procedure SetCaption(Value: string);
published{ user interface }property StartButton: Boolean
read FStartButton write FStartButton default False;
property Caption: stringread GetCaption write SetCaption;
property UserStarts: Boolean
read FUserStarts write SetUserStarts default False;
property UserChar: Char
read FUserChar write SetUserChar default chrUser;
property CompChar: Char
read FCompChar write SetCompChar default chrComp;
end{TTTTControl};
procedureregister;
implementationuses Forms;
constructor TTTTControl.Create(AOwner: TComponent);
var
ButtonIndex: TPlace;
begininherited Create(AOwner);
Game := 0;
UserStarts := False;
FUserChar := chrUser;
FCompChar := chrComp;
TheStartButton := TButton.Create(Self);
TheStartButton.Parent := Self;
TheStartButton.Visible := True;
TheStartButton.Caption := 'Humor me...';
TheStartButton.OnClick := StartButtonClick;
CorbaInitialize;
TicTacToe := TTicTacToeHelper.bind;
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) dobegin
Button[ButtonIndex] := TButton.Create(Self);
Button[ButtonIndex].Parent := Self;
Button[ButtonIndex].Caption := '';
Button[ButtonIndex].Visible := False;
Button[ButtonIndex].OnClick := ButtonClick;
end;
SetBounds(Left,Top,132,132)
end{Create};
destructor TTTTControl.Destroy;
var
ButtonIndex: TPlace;
begin
TheStartButton.Destroy;
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) do
Button[ButtonIndex].Destroy;
TicTacToe := nil; { explicit! }inherited Destroy;
end; {Destroy};
procedure TTTTControl.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
const
Grid = 3;
GridX = 2;
GridY = 2;
var
X,DX,W,Y,DY,H: Word;
begininherited SetBounds(ALeft,ATop,AWidth,AHeight);
TheStartButton.SetBounds(0,0,Width,Height);
X := GridX;
DX := (Width div (Grid * (GridX+GridX))) * (GridX+GridX);
W := DX - GridX;
Y := GridY;
DY := (Height div (Grid * (GridY+GridY))) * (GridY+GridY);
H := DY - GridY;
Button[8].SetBounds(X, Y, W,H);
Button[1].SetBounds(X, Y+DY, W,H);
Button[6].SetBounds(X, Y+DY+DY, W,H);
Inc(X,DX);
Button[3].SetBounds(X, Y, W,H);
Button[5].SetBounds(X, Y+DY, W,H);
Button[7].SetBounds(X, Y+DY+DY, W,H);
Inc(X,DX);
Button[4].SetBounds(X, Y, W,H);
Button[9].SetBounds(X, Y+DY, W,H);
Button[2].SetBounds(X, Y+DY+DY, W,H)
end{SetBounds};
procedure TTTTControl.StartButtonClick(Sender: TObject);
var
ButtonIndex: TPlace;
begintry
Game := TicTacToe.NewGame;
if Parent is TForm then
(Parent as TForm).Caption := IntToStr(Game);
TheStartButton.Visible := False;
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) do
Button[ButtonIndex].Visible := True;
if UserStarts thenbegin
MessageDlg('You may start...', mtInformation, [mbOk], 0);
Button[5].SetFocus; { hint... }endelse
ComputerMove
excepton E: Exception do
MessageDlg('Sorry: '+E.message, mtError, [mbOk], 0)
endend{StartButtonClick};
procedure TTTTControl.ButtonClick(Sender: TObject);
var
ButtonIndex: TPlace;
begin
Enabled := False;
for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) doif Button[ButtonIndex] = Sender as TButton then
UserMove(ButtonIndex)
end{ButtonClick};
procedure TTTTControl.ComputerMove;
var
Move: Integer;
begin
Move := TicTacToe.NextMove(Game,TicTacToe_TPlayer(CompID));
if Move = 0 then
MessageDlg('Neither has won, the game is a draw!', mtInformation, [mbOk], 0)
elsebegin
TicTacToe.MakeMove(Game,TicTacToe_TPlayer(CompID),Move);
Button[Move].Caption := CompChar;
Button[Move].Update;
if TicTacToe.IsWinner(Game) = TicTacToe_TPlayer(CompID) then
MessageDlg('I have won!', mtInformation, [mbOk], 0)
elsebegin
Move := TicTacToe.NextMove(Game,TicTacToe_TPlayer(UserID));
if Move = 0 then
MessageDlg('Neither has won, the game is a draw!', mtInformation, [mbOk], 0)
elseif Move in [FirstPlace..LastPlace] thenbegin
Enabled := True;
Button[Move].SetFocus { hint... }endelseif Parent is TForm then
(Parent as TForm).Caption := IntToStr(Move)
endendend{ComputerMove};
procedure TTTTControl.UserMove(Move: TPlace);
beginif Button[Move].Caption <> '' then
MessageDlg('This place is occupied!', mtWarning, [mbOk], 0)
elsebegin
Button[Move].Caption := UserChar;
Button[Move].Update;
TicTacToe.MakeMove(Game,TicTacToe_TPlayer(UserID),Move);
if TicTacToe.IsWinner(Game) = TicTacToe_TPlayer(UserID) then
MessageDlg('Congratulations, you have won!', mtInformation, [mbOk], 0)
else
ComputerMove
endend{UserMove};
procedure TTTTControl.SetUserChar(Value: Char);
beginif Value = FCompChar then
MessageDlg('Character '+Value+' already in use by CompChar!', mtError, [mbOk], 0)
else
FUserChar := Value
end{SetUserChar};
procedure TTTTControl.SetCompChar(Value: Char);
beginif Value = FUserChar then
MessageDlg('Character '+Value+' already in use by UserChar!', mtError, [mbOk], 0)
else
FCompChar := Value
end{SetCompChar};
procedure TTTTControl.SetUserStarts(Value: Boolean);
begin
FUserStarts := Value;
end{SetUserStarts};
procedure TTTTControl.SetStartButton(Value: Boolean);
begin
FStartButton := Value
end{SetStartButton};
function TTTTControl.GetCaption: string;
begin
GetCaption := TheStartButton.Caption
end{GetCaption};
procedure TTTTControl.SetCaption(Value: string);
begin
TheStartButton.Caption := Value
end{SetCaption};
procedureregister;
begin
RegisterComponents('DrBob42', [TTTTControl])
end{Register};
end.
Обратите внимание, что конструктор TTTControl также вызывает CorbaInitialize для того чтобы Smart Agent был запущен до того как вы фактически создаете этот компонент.
Here is the translation of the article into a single sentence:
This article describes how to create a CORBA client-server application in Delphi, using the VisiBroker library, and provides an example implementation of a Tic-Tac-Toe game.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.