How I can parse a tnsnames.ora file from delphi? - delphi

How can I get the list of Oracle data source names and add them to a combobox so that I can choose whcich datasource to connect to? I need the program to read the contents of the TNS_NAMES.ora file and get the data source names. I can do a FileSearch but want the program to find the TNS_NAMES file itself like TOAD,PL/SQL developer and other Oracle managers do, as the program will be run on different computers and Oracle client might be installed into different folders.

To get the datasource or any other information contained inside of the TNS_NAMES.ora file you must parse this file. So first read the Syntax Rules for this file from here and here, and then you can use the most common approach to parse these files, which is use regular expressions. Unfortunally the Delphi 2010 RTL doesn't include support for regular expressions. But you can use the PCRE library). from here you can use as guide these articles to write your own delphi implementation.
TNSNames Reader (C#)
Parsing tnsnames.ora using regex (C#)

You can use this code for Oracle 10. Drop combobox and button on your form and link FormCreate and button1click events.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Registry, Vcl.StdCtrls;
type
TForm1 = class(TForm)
ComboBox1: TComboBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure ParseTNS;
public
{ Public declarations }
end;
var
Form1: TForm1;
slTNSConfig : TStringList;
implementation
{$R *.dfm}
function GetTNSNamesPath : string;
var
Reg: TRegistry;
SubKeyNames: TStringList;
Name: string;
begin
Reg := TRegistry.Create;
Try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKeyReadOnly('SOFTWARE\ORACLE');
SubKeyNames := TStringList.Create;
Try
Reg.GetKeyNames(SubKeyNames);
for Name in SubKeyNames do
// oracle 10 save path to ORACLE_HOME in registry key like this
// HKEY_LOCAL_MACHINE\SOFTWARE\ORACLE\KEY_OraClient10g_home1\ORACLE_HOME
// for oracle 8 and 9 another key
if pos('KEY_',Name)=1 then
begin
Reg.OpenKeyReadOnly(Name);
// for oracle 10 path to tnsnames.ora like this
// %ORACLE_HOME%\NETWORK\ADMIN\tnsnames.ora
// for oracle 8 and 9 another path
Result :=Reg.ReadString('ORACLE_HOME')+'\NETWORK\ADMIN\tnsnames.ora';
end;
Finally
SubKeyNames.Free;
End;
Finally
Reg.Free;
End;
end;
procedure TForm1.ParseTNS;
var
slTemp : TStringList;
sPath, sTemp : string;
i : integer;
begin
slTemp:= TStringList.Create;
slTNSConfig:= TStringList.Create;
try
sPath:=GetTNSNamesPath;
if (length(sPath)<33) or (not FileExists(sPath)) then
messageDlg('tnsnames.ora not found.', mtError, [mbOk],0)
else
begin
slTemp.LoadFromFile(sPath); // Load tnsnames.ora
sTemp := StringReplace(StringReplace(UpperCase(slTemp.Text),' ','',[rfReplaceAll]),')','',[rfReplaceAll]); // delete ')' and spaces
slTemp.Clear;
slTemp.Delimiter:='(';
slTemp.DelimitedText:=sTemp; // parse like Name=Value
sTemp:='';
for i := 0 to slTemp.Count-1 do
begin
if pos('DESCRIPTION',slTemp[i])=1 then // Get Name before description
begin
sTemp:=StringReplace(slTemp[i-1],'=','',[rfReplaceAll]);
ComboBox1.Items.Add(sTemp); // Fill combobox
end;
if length(slTemp.ValueFromIndex[i])>0 then //Get filled Name=Value
slTNSConfig.Add(sTemp+'_'+slTemp[i]); // Fill TNS config like TNS_HOST=Value
end;
ComboBox1.Sorted:=true;
end;
finally
slTemp.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ComboBox1.Text:='';
ParseTNS;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
sHost, sPort, sSID, sServiceName : string;
begin
sHost:=slTNSConfig.Values[ComboBox1.Text+'_HOST'];
sPort:=slTNSConfig.Values[ComboBox1.Text+'_PORT'];
sSID:=slTNSConfig.Values[ComboBox1.Text+'_SID'];
sServiceName:=slTNSConfig.Values[ComboBox1.Text+'_SERVICE_NAME'];
messageDLG('sHost:'+sHost+' sPort:'+sPort+' sSID:'+sSID+' sServiceName:'+sServiceName,mtInformation,[mbOk],0);
end;
end.

Related

Why the shortcut doesn't work in my Delphi program?

I've written a program in Delphi 10.4. The main part of the UI is just a TMemo. When the user types something in it, the app will automatically copy the text in the TMemo to clipboard. It looks like this:
This auto copy part works well. However, I also want to let the user change dark theme or light theme by a shortcut. I enabled a dark theme and a light theme.
The code looks like this:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Clipbrd, System.Actions,
Vcl.ActnList, Vcl.Themes;
type
TForm1 = class(TForm)
txt: TMemo;
ActionList1: TActionList;
act_change_theme: TAction;
procedure txtChange(Sender: TObject);
procedure act_change_themeExecute(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
var
is_dark: Boolean;
implementation
{$R *.dfm}
function ShortCut(Key: Word; Shift: TShiftState): TShortCut;
begin
Result := 0;
if HiByte(Key) <> 0 then
Exit; // if Key is national character then it can't be used as shortcut
Result := Key;
if ssShift in Shift then
Inc(Result, scShift); // this is identical to "+" scShift
if ssCtrl in Shift then
Inc(Result, scCtrl);
if ssAlt in Shift then
Inc(Result, scAlt);
end;
procedure TForm1.act_change_themeExecute(Sender: TObject);
begin
if is_dark then
begin
TStyleManager.TrySetStyle('Windows', false);
is_dark := false;
end
else
begin
TStyleManager.TrySetStyle('Carbon', false);
is_dark := true;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
is_dark := false;
act_change_theme.ShortCut := ShortCut(Word('d'), [ssCtrl]);
end;
procedure TForm1.txtChange(Sender: TObject);
begin
try
Clipboard.AsText := txt.Lines.GetText;
except
on E: Exception do
end;
end;
end.
However, when I press ctrl+d, nothing happened. I tried to debug it and I found that ctrl+d never triggers the action's shortcut. Why this happened? How to fix it? I've used the shortcut function in the past and it worked.
Try Word('D'), or the constant vkD, instead of Word('d'). Shortcuts use virtual key codes, and letters are represented as virtual keys using their capital values. Typing an Uppercase or Lowercase letter into an edit control uses the same virtual key, it is the current shift state that determines the case of the letter when the key is translated into a text character.
Also note that the VCL has its own ShortCut() function (and also TextToShortCut()) in the Vcl.Menus unit for creating TShortCut values, so you don't need to write your own function.
See Representing Keys and Shortcuts, especially Representing Shortcuts as Instances of TShortCut.
Also, your TAction is clearly placed on the Form at design-time, so you should simply assign its ShortCut using the Object Inspector, rather than in code. Then these details would be handled for you automatically by the framework.

DataSnap ServerMethod functions returned as ftStream parameters being wrongly truncated

As DataSnap users will know, its ServerMethods return values to their callers
as DataSnap parameters.
There have been a number of reports on SO and elsewhere relating to a problem with
DataSnap servers returning ServerMethod results as ftStream parameters, that the stream is truncated
prematurely or returned empty. An example is here:
Can't retrieve TStreams bigger than around 260.000 bytes from a Datasnap Server
I have put together a reproducible test case of this that I intend submitting to
Emba's Quality Portal as an MCVE, but before I do I'd like some help pinning down
where the problem occurs. I'm using Delphi Seattle on Win64, compiling to 32-bits, btw.
My MCVE is completely self-contained (i.e. includes both server and client) and does
not depend on any database data. Its ServerMethods module contains a function
(BuildString in the code below) which returns a string of a caller-specified length
and two ServerMethods GetAsString and GetAsStream which return the result
as parameters of types ftString and ftStream, respectively.
Its GetString method successfully returns a string of any requested length up to
the maximum I've tested, which is 32000000 (32 million) bytes.
Otoh, the GetStream method works up to a requested size of 30716; above that,
the returned stream has a size of -1 and is empty. The expected behaviour of course
that it should be capable of working with much larger sizes, just as GetString does.
On the outbound (server) side, at some point the returned stream is passed into
DataSnap's JSon layer en route to the tcp/ip transport layer and on the inbound side, similarly, the stream is retrieved
from the JSon layer. What I'd like to be able to do, and what this q is about,
is to capture the outbound and inbound JSon representations of the AsStream
parameter value in human-legible form so that I identify whether the unwanted
truncation of its data occurs on the server or client side. How do I do that?
the reason I'm asking this is that despite hours of looking I've been unable to identify exactly
where the JSon conversions occur. It's like looking for a needle in a haystack.
If you take a look at the method TDBXJSonStreamWriter.WriteParameter in Data.DBXStream,
the one thing it doesn't write is the stream's contents!
One thing I have been able to establish is regarding line 4809 in Data.DBXStream
Size := ((FBuf[IncrAfter(FOff)] and 255) shl 8) or (FBuf[IncrAfter(FOff)] and 255)
in the function TDBXRowBuffer.ReadReaderBlobSize. On entry to
this method, Size is initialised to zero, and it is this line which sets Size to 30716
for all requested stream sizes >= that value. But I don't know whether this is cause or effect,
i.e. whether the stream trucation has already taken place or whether it's this line
which causes it.
My code is below; apologies for the length of it, but DataSnap projects require
quite a lot of baggage at the best of times and I've included some code which
initialises some of the components to avoid having to post .DFMs too.
ServerMethods code:
unit ServerMethods2u;
interface
uses System.SysUtils, System.Classes, System.Json, variants, Windows,
Datasnap.DSServer, Datasnap.DSAuth, DataSnap.DSProviderDataModuleAdapter;
{$MethodInfo on}
type
TServerMethods1 = class(TDSServerModule)
public
function GetStream(Len: Integer): TStream;
function GetString(Len: Integer): String;
end;
{$MethodInfo off}
implementation
{$R *.dfm}
uses System.StrUtils;
function BuildString(Len : Integer) : String;
var
S : String;
Count,
LeftToWrite : Integer;
const
scBlock = '%8d bytes'#13#10;
begin
LeftToWrite := Len;
Count := 1;
while Count <= Len do begin
S := Format(scBlock, [Count]);
if LeftToWrite >= Length(S) then
else
S := Copy(S, 1, LeftToWrite);
Result := Result + S;
Inc(Count, Length(S));
Dec(LeftToWrite, Length(S));
end;
if Length(Result) > 0 then
Result[Length(Result)] := '.'
end;
function TServerMethods1.GetStream(Len : Integer): TStream;
var
SS : TStringStream;
begin
SS := TStringStream.Create;
SS.WriteString(BuildString(Len));
SS.Position := 0;
Result := SS;
end;
function TServerMethods1.GetString(Len : Integer): String;
begin
Result := BuildString(Len);
end;
ServerContainer code:
unit ServerContainer2u;
interface
uses System.SysUtils, System.Classes, Datasnap.DSTCPServerTransport,
Datasnap.DSServer, Datasnap.DSCommonServer, Datasnap.DSAuth, IPPeerServer,
DataSnap.DSProviderDataModuleAdapter;
type
TServerContainer1 = class(TDataModule)
DSServer1: TDSServer;
DSTCPServerTransport1: TDSTCPServerTransport;
DSServerClass1: TDSServerClass;
procedure DataModuleCreate(Sender: TObject);
procedure DSServerClass1GetClass(DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
end;
var
ServerContainer1: TServerContainer1;
implementation
{$R *.dfm}
uses ServerMethods2u;
procedure TServerContainer1.DataModuleCreate(Sender: TObject);
begin
DSServerClass1.Server := DSServer1;
DSTCPServerTransport1.Server := DSServer1;
end;
procedure TServerContainer1.DSServerClass1GetClass(
DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
PersistentClass := TServerMethods1;
end;
end.
ServerForm code:
unit ServerForm2u;
interface
uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, DBXJSON, Data.DBXDataSnap, IPPeerClient,
Data.DBXCommon, Data.FMTBcd, Data.DB, Data.SqlExpr, Data.DbxHTTPLayer,
DataSnap.DSServer;
type
TForm1 = class(TForm)
btnGetStream: TButton;
edStreamSize: TEdit;
SQLConnection1: TSQLConnection;
SMGetStream: TSqlServerMethod;
Memo1: TMemo;
Label1: TLabel;
btnGetString: TButton;
Label2: TLabel;
edStringSize: TEdit;
SMGetString: TSqlServerMethod;
procedure FormCreate(Sender: TObject);
procedure btnGetStreamClick(Sender: TObject);
procedure btnGetStringClick(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
SqlConnection1.ConnectionData.Properties.Values['CommunicationProtocol'] := 'tcp/ip';
SqlConnection1.ConnectionData.Properties.Values['BufferKBSize'] := '64';
SMGetStream.Params.Clear;
SMGetStream.Params.CreateParam(ftInteger, 'Len', ptInput);
SMGetStream.Params.CreateParam(ftStream, 'Result', ptOutput);
SMGetString.Params.Clear;
SMGetString.Params.CreateParam(ftInteger, 'Len', ptInput);
SMGetString.Params.CreateParam(ftString, 'Result', ptOutput);
end;
procedure TForm1.btnGetStreamClick(Sender: TObject);
var
SS : TStringStream;
S : TStream;
begin
Memo1.Lines.Clear;
SS := TStringStream.Create;
try
SMGetStream.Params[0].AsInteger := StrtoInt(edStreamSize.Text);
SMGetStream.ExecuteMethod;
S := SMGetStream.Params[1].AsStream;
S.Position := 0;
if S.Size > 0 then begin
try
SS.CopyFrom(S, S.Size);
Memo1.Lines.BeginUpdate;
Memo1.Lines.Text := SS.DataString;
Memo1.Lines.Insert(0, IntToStr(S.Size));
finally
Memo1.Lines.EndUpdate;
end;
end
else
ShowMessage(IntToStr(S.Size));
finally
SS.Free;
end;
end;
procedure TForm1.btnGetStringClick(Sender: TObject);
var
S : String;
Size : Integer;
begin
Memo1.Lines.Clear;
Size := StrtoInt(edStringSize.Text);
SMGetString.Params[0].AsInteger := Size;
SMGetString.ExecuteMethod;
S := SMGetString.Params[1].AsString;
if Length(S) > 0 then begin
try
Memo1.Lines.BeginUpdate;
Memo1.Lines.Text := S;
Memo1.Lines.Insert(0, IntToStr(Length(S)));
finally
Memo1.Lines.EndUpdate;
end;
end;
end;
end.

Delphi - modify variable from DLL

I want to make simple program that sets Edit1.Text to "6" (for example, but with usage of DLL - thats important). Here's the code:
Unit:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
a:integer;
implementation
procedure test; external 'lib.dll' name 'test';
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
test;
Edit1.Text:=Inttostr(a);
end;
end.
And the DLL file:
library lib;
uses
Winapi.Windows, System.SysUtils;
var
a:integer;
procedure test;
begin
a:=6;
end;
exports
test;
{$R *.res}
begin
end.
The problem is, that Edit1.Text is still 0. Can you help me, please?
You've got two different variables, one in the DLL and one in the executable. That they are both named a is incidental. Setting one has no impact on the other.
Make the DLL export a function that returns the value:
function GetValue: Integer; stdcall;
begin
Result := 6;
end;
Import it like this:
function GetValue: Integer; stdcall; external dllname;
And call it like this:
Edit1.Text := IntToStr(GetValue);
No doubt the real code will do more than return the value 6 but that's no problem. You can return anything you like. They key point is that you pass the value from the DLL to the host using a function return value.
it works good:
in MainUnit
implementation
procedure Test(Edit1: TEdit); stdcall; external 'dll_proj.dll';
in DLL
exports Test;
Procedure Test(Object1: TEdit); stdcall;
var i:integer;
begin
for i:= 0 to 100 do
begin
Object1.Text:= IntToStr(i);
Application.Processmessages();
Sleep(100);
end;
end;
Building on your original code and adding a few more buttons, here's a demonstration of how you can use procedures (or functions if you prefer) and have them play nicely with a DLL.
Note that the name option is not required unless you wish to change the function's name or use overloading - so I've commented it out.
implementation
procedure test(var a : integer); external 'lib.dll' {name 'test'};
procedure test2(ptr_a : pinteger); external 'lib.dll';
procedure test3(ptr_a : pinteger); external 'lib.dll';
procedure test4(ptr_a : pinteger = nil); external 'lib.dll';
{$R *.dfm}
procedure TForm14.Button1Click(Sender: TObject);
begin
test(a);
Edit1.Text:=Inttostr(a);
end;
procedure TForm14.Button2Click(Sender: TObject);
begin
test2(#a);
Edit1.Text:=Inttostr(a);
end;
procedure TForm14.Button3Click(Sender: TObject);
begin
test3(#a);
Edit1.Text:=Inttostr(a);
end;
procedure TForm14.Button4Click(Sender: TObject);
begin
test4(#a);
test4;
Edit1.Text:=Inttostr(a);
end;
end.
... and the library body ...
var
local_a:integer;
local_Ptr_a:pinteger;
procedure test(var a : integer);
begin
a:=6;
end;
procedure test2(ptr_a : pinteger);
begin
inc(ptr_a^);
end;
procedure test3(ptr_a : pinteger);
begin
inc(local_a);
ptr_a^:=local_a;
end;
procedure test4(ptr_a : pinteger = nil);
begin
if ptr_a = nil then
inc(local_ptr_a^)
else
local_ptr_a := ptr_a;
end;
exports test;
exports test2;
exports test3;
exports test4;
{$R *.res}
begin
local_a := 4;
end.
So - to a little explanation.
First test : using a var-parameter to return a value from a procedure. No problem there.
Second test : pass the address of the receiving variable as a pointer. I've added a little curl here - incrementing the value for entertainment er,...value.
Third test : This is showing how local values owned by the DLL can be used. The local value is initialised by the assignment of 4 at the end. The procedure itself uses the same mechanism as the second test to return the value from the DLL's local variables to the main routine's variables.
Note that test3 assigns to the program's variable (1 + the value stored in the DLL's memory, ) hence pressing button3 will actually changes a; so pressing buttons 1-2-2-3-2 will use the value from 3 for the last change, not the prior-value-from-2.
Final test: this where we can get a little more clever. It uses the optional-parameters mechanism to vary the detailed operation.
First you eecute the procedure with a parameter, being the address of (or pointer to) a variable of the appropriate type. The procedure stores that address in the DLL's memory area.
Next you can execute the procedure with no parameters and it will increment the integer to which the stored pointer is pointing. Purely for convenience, I've established the variable's address on each button-push, but once the address has been stored, it doesn't matter whether that address was set a few microseconds ago or a week, test4; will increment the integer value at that address - whatever it is. Set the address using test4(#b); then test4; will increment b - whichever integer was last pointed to when the procedure was executed with a parameter.
executing test4; without having at sometime prior executed a test4(#something) or where something is now out-of-scope (like perhaps a local variable in a procedure) is very likely to cause an access violation.

Why does the compiler say my form's variable is undeclared in my procedure?

I would like to read a CSV file to Delphi (DBGrid) as suggested in the sample project here. I have a simple form where I defined TOpenDialog and elements from the TCsvTransform. The project does not compile when I am trying to create a procedure that would pass the file path from the TOpenDialog to the procedure responsible for reading the CSV file.
procedure ReadCSVFile;
var
SS: TStringStream;
OS: TFileStream;
begin
OS := TFileStream.Create(MainOpenDialog.FileName, fmCreate);
SS := TStringStream.Create;
try
ClientDataSet1.SaveToStream(SS, dfXML);
with TCsvTransform.Create do
try
Transform(DPToCsv, SS, TStream(OS));
finally
Free;
end;
finally
SS.Free;
OS.Free;
end;
end;
The compiler says that MainOpenDialog is undeclared. The full code, where I think that I declared the Open Dialog elements is below.
unit geoimp;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.Buttons, Vcl.StdCtrls,
Vcl.Grids, Vcl.DBGrids, Data.DB, Datasnap.DBClient, ShlObj;
const
shfolder = 'ShFolder.dll';
type
TMainForm = class(TForm)
MainPageControl: TPageControl;
ImportTab: TTabSheet;
MapPreviewTab: TTabSheet;
GeoMatchingTab: TTabSheet;
ImportDBGrid: TDBGrid;
ImportLbl: TLabel;
SlctImportDta: TSpeedButton;
MainClientData: TClientDataSet;
MainDataSource: TDataSource;
MainOpenDialog: TOpenDialog;
procedure SlctImportDtaClick(Sender: TObject);
procedure ReadCSVFile;
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure ReadCSVFile;
var
SS: TStringStream;
OS: TFileStream;
begin
OS := TFileStream.Create(MainOpenDialog.FileName, fmCreate);
SS := TStringStream.Create;
try
ClientDataSet1.SaveToStream(SS, dfXML);
with TCsvTransform.Create do
try
Transform(DPToCsv, SS, TStream(OS));
finally
Free;
end;
finally
SS.Free;
OS.Free;
end;
end;
procedure TMainForm.SlctImportDtaClick(Sender: TObject);
begin
// Create the open dialog object - assign to our open dialog variable
MainOpenDialog := TOpenDialog.Create(self);
// Set up the starting directory to be the current one
MainOpenDialog.InitialDir := GetCurrentDir;
// Only allow existing files to be selected
MainOpenDialog.Options := [ofFileMustExist];
// Allow only .dpr and .pas files to be selected
MainOpenDialog.Filter :=
'CSV Files|*.csv';
// Select pascal files as the starting filter type
MainOpenDialog.FilterIndex := 2;
// Display the open file dialog
if MainOpenDialog.Execute
then ShowMessage('File : '+MainOpenDialog.FileName)
else ShowMessage('Open file was cancelled');
// Free up the dialog
MainOpenDialog.Free;
end;
end.
That's because in your implementation section, your procedure ReadCSVFile is stand-alone, not a method of TForm1 (like your SlctImportDtaClick already is). Change it to read
procedure TForm1.ReadCSVFile;
var
SS: TStringStream;
OS: TFileStream;
begin
OS := TFileStream.Create(MainOpenDialog.FileName, fmCreate);
[etc]
The reason the compiler is complaining is that while ReadCSVFile is declared as a stand-alone procedure, it can't "make the connection" between MainOpenDialog in it and the one that's declared as part of TForm1.

Add a Data Pointer to TIdTCPServer and TIdCustomTCPServer

I would like to add a Data Pointer to the TIdTCPServer and I would like to have it also in the TIdCustomTCPServer. I'm not good in overriding, etc. so that's what I have so far:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdContext, IdBaseComponent, IdComponent,
IdCustomTCPServer, IdTCPServer, Vcl.StdCtrls;
type
TIdTcpServer = class(IdTcpServer.TIdTcpServer)
public
Data : Pointer;
end;
type
TForm1 = class(TForm)
IdTCPServer1: TIdTCPServer;
Button1: TButton;
procedure IdTCPServer1Connect(AContext: TIdContext);
procedure IdTCPServer1Disconnect(AContext: TIdContext);
procedure IdTCPServer1Execute(AContext: TIdContext);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPServer1.Data := TObject (12345); // Just a test to fill the Data Pointer;
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
ParentServer : TIdCustomTCPServer;
MyData : Integer;
begin
//
ParentServer := TIdServerContext(AContext).Server;
// MyData := Integer(ParentServer.Data);
end;
procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);
begin
//
end;
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
begin
//
end;
end.
How could I do this to get the Data Pointer back to any of the OnConnect/OnDisconnect/etc ?
There is no Data property in http://www.indyproject.org/docsite/html/!!MEMBEROVERVIEW_TIdTCPServer.html - are you sure your code compiles and works ?
Well, if there is such a property then just cast the variable back.
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
ParentServer : TIdCustomTCPServer;
MyData : Integer;
begin
//
ParentServer := TIdServerContext(AContext).Server;
MyData := Integer( (ParentServer as TIdTcpServer).Data);
end;
If there is not - then you have two options. The one is subclassing - adding the property in your class, and second is adding some outside data storage.
Unit IDWithData;
interface uses IdTCPServer;
type TIdTcpServer = class( IdTCPServer.TIdTcpServer )
public
var Data: Integer;
end;
implementation
end.
Add this unit at LAST position in the TForm1's unit INTERFACE/USES list and voila! the server now was - invisible to the IDE - replaced with your subclassed one, which have the new Data field, thus the typecast above would work using this new but intentionally same-named type.
Of course, if you wish, you may just go full throttle: add your own name for new class, make new runtime and designtime packages, add then install your new server to IDE VCL Palette and replacing them on all your forms. Another "proper" solution would be forking INDY sources, adding the DATA variable to the very vanilla TIdCustomTCPServer type and then keep maintainging your own forked branch of INDY.
More conservative approach would be just creating a global variable of type TDictionary< TIdCustomTCPServer,Integer > - http://docwiki.embarcadero.com/CodeExamples/XE4/en/Generics_Collections_TDictionary_(Delphi)
Then it would become like this:
procedure TForm1.Button1Click(Sender: TObject);
begin
GlobalServerDictionary.AddOrSetValue( IdTCPServer1, 12345 );
end;
procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);
var
ParentServer : TIdCustomTCPServer;
MyData : Integer;
begin
ParentServer := TIdServerContext(AContext).Server;
MyData := GlobalServerDictionary.Items[ ParentServer ];
end;

Resources