Крестики-нолики с CORBA
Особенности установки 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 был запущен до того как вы фактически создаете этот компонент.
DelphiWorld 6.0