Sources
Delphi Russian Knowledge Base
DRKB - это самая большая и удобная в использовании база знаний по Дельфи в рунете, составленная Виталием Невзоровым

Крестики-нолики с CORBA

01.01.2007
Известно расшифровывается как 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..9
    enum 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;
 
interface
 
uses 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.

unit TTT_c;
 
interface
 
uses CORBA, TTT_i;
 
type
  TTicTacToeHelper = class;
  TTicTacToeStub = class;
  TTicTacToe_TGameHelper = class;
  TTicTacToe_TPlaceHelper = class;
  TTicTacToe_TPlayerHelper = class;
  ETicTacToe_PlaceTaken = class;
 
  TTicTacToeHelper = class
    class procedure Insert (var _A: CORBA.Any; const _Value: TTT_i.TicTacToe);
    class function Extract(var _A: CORBA.Any): TTT_i.TicTacToe;
    class function TypeCode: CORBA.TypeCode;
    class function RepositoryId: string;
    class function read (const _Input: CORBA.InputStream): TTT_i.TicTacToe;
    class procedure write(const _Output: CORBA.OutputStream; const _Value:
    TTT_i.TicTacToe);
    class function Narrow(const _Obj: CORBA.CORBAObject; _IsA: Boolean = False):
    TTT_i.TicTacToe;
    class function Bind(const _InstanceName: string = ''; _HostName: string = ''):
    TTT_i.TicTacToe; overload;
    class function Bind(_Options: BindOptions; const _InstanceName: string = '';
    _HostName: string = ''): TTT_i.TicTacToe; overload;
  end;
 
  TTicTacToeStub = class(CORBA.TCORBAObject, TTT_i.TicTacToe)
  public
    function NewGame: TTT_i.TicTacToe_TGame; virtual;
    procedure MakeMove(const Game: TTT_i.TicTacToe_TGame;
    const player: TTT_i.TicTacToe_TPlayer;
    const Place: TTT_i.TicTacToe_TPlace); virtual;
    function NextMove(const Game: TTT_i.TicTacToe_TGame;
    const player: TTT_i.TicTacToe_TPlayer):
    TTT_i.TicTacToe_TPlace; virtual;
    function IsWinner(const Game: TTT_i.TicTacToe_TGame):
    TTT_i.TicTacToe_TPlayer; virtual;
    function GetValue(const Game: TTT_i.TicTacToe_TGame;
    const Place: TTT_i.TicTacToe_TPlace):
    TTT_i.TicTacToe_TPlayer; virtual;
  end;
 
  TTicTacToe_TGameHelper = class
    class procedure Insert (var _A: CORBA.Any; const _Value: TTT_i.TicTacToe_TGame);
    class function Extract(const _A: CORBA.Any): TTT_i.TicTacToe_TGame;
    class function TypeCode: CORBA.TypeCode;
    class function RepositoryId: string;
    class function read (const _Input: CORBA.InputStream): TTT_i.TicTacToe_TGame;
    class procedure write(const _Output: CORBA.OutputStream; const _Value:
    TTT_i.TicTacToe_TGame);
  end;
 
  TTicTacToe_TPlaceHelper = class
    class procedure Insert (var _A: CORBA.Any; const _Value: TTT_i.TicTacToe_TPlace);
    class function Extract(const _A: CORBA.Any): TTT_i.TicTacToe_TPlace;
    class function TypeCode: CORBA.TypeCode;
    class function RepositoryId: string;
    class function read (const _Input: CORBA.InputStream): TTT_i.TicTacToe_TPlace;
    class procedure write(const _Output: CORBA.OutputStream; const _Value:
    TTT_i.TicTacToe_TPlace);
  end;
 
  TTicTacToe_TPlayerHelper = class
    class procedure Insert (var _A: CORBA.Any; const _Value: TTT_i.TicTacToe_TPlayer);
    class function Extract(const _A: CORBA.Any): TTT_i.TicTacToe_TPlayer;
    class function TypeCode: CORBA.TypeCode;
    class function RepositoryId: string;
    class function read (const _Input: CORBA.InputStream): TTT_i.TicTacToe_TPlayer;
    class procedure write(const _Output: CORBA.OutputStream; const _Value:
    TTT_i.TicTacToe_TPlayer);
  end;
 
  ETicTacToe_PlaceTaken = class(UserException)
  private
    FTakenBy: TTT_i.TicTacToe_TPlayer;
  protected
    function _get_TakenBy: TTT_i.TicTacToe_TPlayer; virtual;
  public
    property TakenBy: TTT_i.TicTacToe_TPlayer read _get_TakenBy;
    constructor Create; overload;
    constructor Create(const TakenBy: TTT_i.TicTacToe_TPlayer); overload;
    procedure Copy(const _Input: InputStream); override;
    procedure WriteExceptionInfo(var _Output: OutputStream); override;
  end;

На что следует обратить внимание, так это на декларацию исключения ETicTacToe_PlaceTaken, которое имеет два конструктора: по умолчанию без аргументов и с одним аргументом TakenBy, который автоматически инициализируя исключение.

Server Skeletons (TTT_s.pas)

Класс TticTacToeSkeleton единственный класс, который мы используем для создания экземпляра CORBA Server TicTacToe, принимающего в качестве аргументов имя InstanceName и экземпляр интерфейса TicTacToe .

unit TTT_s;
 
interface
 
uses CORBA, TTT_i, TTT_c;
 
type
  TTicTacToeSkeleton = class;
 
  TTicTacToeSkeleton = class(CORBA.TCorbaObject, TTT_i.TicTacToe)
  private
    FImplementation: TicTacToe;
  public
    constructor Create(const InstanceName: string; const Impl: TicTacToe);
    destructor Destroy; override;
    function GetImplementation: TicTacToe;
 
    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;
  published
    procedure _NewGame(const _Input: CORBA.InputStream; _Cookie: Pointer);
    procedure _MakeMove(const _Input: CORBA.InputStream; _Cookie: Pointer);
    procedure _NextMove(const _Input: CORBA.InputStream; _Cookie: Pointer);
    procedure _IsWinner(const _Input: CORBA.InputStream; _Cookie: Pointer);
    procedure _GetValue(const _Input: CORBA.InputStream; _Cookie: Pointer);
end;

Implementation (TTT_impl.pas)

Файл TTT_impl.pas, единственный файл который редактируется и в который вставляется код реализации CORBA сервера. Тут использован модуль Magic, который использовался для ITicTacToe web service в Delphi 6.

unit TTT_impl;
 
interface
 
uses
  SysUtils, CORBA, TTT_i, TTT_c,
  Magic; // implementation of Magic.TTicTacToe
 
type
  TTicTacToe = class(TInterfacedObject, TTT_i.TicTacToe)
  protected
    TTT: Magic.TTicTacToe;
  public
    constructor 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;
 
implementation
 
constructor TTicTacToe.Create;
begin
  inherited;
  { *************************** }
  { *** 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;
 
initialization
 
end.

Теперь мы имеем на руках практически все части для создания приложения с использованием технологии CORBA . Пусть даже это и игрушка.

CORBA Server Application

Помимо сгенерированных файлов должен же быть и сам проект с главным модулем формы. Сохранив проект как TTTServer.dpr а модуль главной формы как GameUnit. Если заменить фактический ТТТ на объект skeleton типа TicTacToe, код модуля будет выглядеть следующим образом. Тут следует обратить внимание на использование четырех модулей в предложении uses секции interface:

unit GameUnit;
 
interface
 
uses
  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 object
    procedure 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 object
 
begin
  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;
 
interface
 
uses
  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;
 
interface
 
uses
  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: string
    read 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};
 
procedure register;
 
implementation
 
uses Forms;
 
constructor TTTTControl.Create(AOwner: TComponent);
var
  ButtonIndex: TPlace;
begin
  inherited 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) do
  begin
    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;
begin
  inherited 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;
begin
  try
    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 then
    begin
      MessageDlg('You may start...', mtInformation, [mbOk], 0);
      Button[5].SetFocus; { hint... }
    end
    else
      ComputerMove
  except
    on E: Exception do
      MessageDlg('Sorry: '+E.message, mtError, [mbOk], 0)
  end
end {StartButtonClick};
 
procedure TTTTControl.ButtonClick(Sender: TObject);
var
  ButtonIndex: TPlace;
begin
  Enabled := False;
  for ButtonIndex := Low(ButtonIndex) to High(ButtonIndex) do
    if 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)
  else
  begin
    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)
    else
    begin
      Move := TicTacToe.NextMove(Game,TicTacToe_TPlayer(UserID));
      if Move = 0 then
        MessageDlg('Neither has won, the game is a draw!', mtInformation, [mbOk], 0)
      else
      if Move in [FirstPlace..LastPlace] then
      begin
        Enabled := True;
        Button[Move].SetFocus { hint... }
      end
      else
      if Parent is TForm then
        (Parent as TForm).Caption := IntToStr(Move)
    end
  end
end {ComputerMove};
 
procedure TTTTControl.UserMove(Move: TPlace);
begin
  if Button[Move].Caption <> '' then
    MessageDlg('This place is occupied!', mtWarning, [mbOk], 0)
  else
  begin
    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
  end
end {UserMove};
 
procedure TTTTControl.SetUserChar(Value: Char);
begin
  if Value = FCompChar then
    MessageDlg('Character '+Value+' already in use by CompChar!', mtError, [mbOk], 0)
  else
    FUserChar := Value
end {SetUserChar};
 
procedure TTTTControl.SetCompChar(Value: Char);
begin
  if 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};
 
procedure register;
begin
  RegisterComponents('DrBob42', [TTTTControl])
end {Register};
 
end.

Обратите внимание, что конструктор TTTControl также вызывает CorbaInitialize для того чтобы Smart Agent был запущен до того как вы фактически создаете этот компонент.

https://delphiworld.narod.ru/

DelphiWorld 6.0