- Какая разница межу 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 был запущен до того как вы фактически создаете этот компонент.
Статья Крестики - нолики с коброй раздела Технологии CORBA может быть полезна для разработчиков на Delphi и FreePascal.
Комментарии и вопросы
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.