Delphi: Call a function whose name is stored in a string - string

Is it possible to call a function whose name is stored in a string in Delphi?

Please give more details on what are you trying to achieve.
As far as I know:
It is not possible to call a random function like that.
For class and object functions (MyObject.Function) this can be done with RTTI, but it's a lot of work.
If you just need to call one particular type of functions (say, function(integer, integer): string), it's a lot easier.
For the last one, declare a function type, then get a function pointer and cast it like this:
type
TMyFuncType = function(a: integer; b: integer): string of object;
TMyClass = class
published
function Func1(a: integer; b: integer): string;
function Func2(a: integer; b: integer): string;
function Func3(a: integer; b: integer): string;
public
function Call(MethodName: string; a, b: integer): string;
end;
function TMyClass.Call(MethodName: string; a, b: integer): string;
var m: TMethod;
begin
m.Code := Self.MethodAddress(MethodName); //find method code
m.Data := pointer(Self); //store pointer to object instance
Result := TMyFuncType(m)(a, b);
end;
{...}
//use it like this
var MyClass: TMyClass;
begin
MyClass := TMyClass.Create;
MyClass.Call('Func1', 3, 5);
MyClass.Call('Func2', 6, 4);
MyClass.Destroy;
end.

You didn't specify your Delphi version, However if you have Delphi 2010(+) you can do it using the enhanced RTTI, I'm not expert on them, but I tried this sample for you:
TProcClass = class
public
procedure SayHi;
function GetSum(X,Y:Integer): Integer;
end;
uses
Rtti;
{ TProcClass }
procedure TProcClass.SayHi;
begin
ShowMessage('Hi');
end;
function TProcClass.GetSum(X, Y: Integer): Integer;
begin
ShowMessage(IntToStr(X + Y));
end;
procedure ExecMethod(MethodName:string; const Args: array of TValue);
var
R : TRttiContext;
T : TRttiType;
M : TRttiMethod;
begin
T := R.GetType(TProcClass);
for M in t.GetMethods do
if (m.Parent = t) and (m.Name = MethodName)then
M.Invoke(TProcClass.Create,Args)
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ExecMethod('SayHi',[]);
ExecMethod('GetSum',[10,20]);
end;
The good things, if you have procedure or function with parameters it will work without more work.

I'm surprised no one has suggested a dispatch table. This is exactly what it's for.
program RPS;
uses
SysUtils,
Generics.Collections;
type
TDispatchTable = class(TDictionary<string, TProc>);
procedure Rock;
begin
end;
procedure Paper;
begin
end;
procedure Scissors;
begin
end;
var
DispatchTable: TDispatchTable;
begin
DispatchTable := TDispatchTable.Create;
try
DispatchTable.Add('Rock', Rock);
DispatchTable.Add('Paper', Paper);
DispatchTable.Add('Scissors', Scissors);
DispatchTable['Rock'].Invoke; // or DispatchTable['Rock']();
finally
DispatchTable.Free;
end;
end.
The implementation I wrote uses generics so it would only work with Delphi 2009+. For older versions it would probably be easiest to implement using TStringList and the command pattern

With Delphi 2010 you can uses JSON and SuperObject to invoke method with parametters.
http://code.google.com/p/superobject/source/browse/#svn/trunk
If you need, there is also an xml parser to transform xml to json.
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure TestMethod(const value: string);
end;
var
Form1: TForm1;
implementation
uses superobject;
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
SOInvoke(Self, 'TestMethod', SO('{value: "hello"}'));
end;
procedure TForm1.TestMethod(const value: string);
begin
Caption := value;
end;

If you are asking if there is something like the JavaScript eval() is possible in Delphi, no this is not (easily) achievable since Delphi compiles to native code.
If you need only to support some strings you can always do many if or a case... Something like:
if myString = 'myFunction' then
myFunction();

OK, I'm very late to the party, but you can definitely call routines by name with this code (There are some limitations thought)
type
TExec = procedure of Object;
// rest of section...
procedure TMainForm.ExecuteMethod(MethodName : String);
var
Exec : TExec;
Routine : TMethod;
begin
Routine.Data := Pointer(Form1);
Routine.Code := Form1.MethodAddress(MethodName);
if Not Assigned(Routine.Code) then
Exit;
Exec := TExec(Routine);
Exec;
end;
Just in case someone needs this for Delphi 7 / 2010

Put each function in an Action. Then you can find the Action by name and Execute it
function ExecuteActionByName(const S: String);
var
I: Integer;
begin
for I := 0 to MainForm.ComponentCount-1 do
if (MainForm.Components[I] is TAction)
and SameText(TAction(MainForm.Components[I]).Name,S) then
begin
TAction(MainForm.Components[I]).Execute;
Break;
end;
end;

You can do something like this by crafting one or more classes with published properties that use functions to implement their read and write functionality. The properties can then be discovered using RTTI reflection and referenced, causing the underlying functions to get called.
Alternatively, you can store function pointers in a table, or even the Object property of TStringList and effectively index them by string name.
Straight calling of a function by name is not possible in Delphi.

The following simple solution using exports and GetProcAddress also works for old Delphi versions:
type
TMyProc = procedure(const value: Integer);
procedure Test(const value: Integer);
exports Test;
implementation
procedure Test(const value: string);
begin
ShowMessage('It works! ' + value);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
p: TMyProc;
begin
p := GetProcAddress(HInstance, 'Test');
if Assigned(p) then P('Yes');
end;

function ExecuteMethod(AClass : TClass; AMethodName : String; const AArgs: Array of TValue) : TValue;
var
RttiContext : TRttiContext;
RttiMethod : TRttiMethod;
RttiType : TRttiType;
RttiObject : TObject;
begin
RttiObject := AClass.Create;
try
RttiContext := TRttiContext.Create;
RttiType := RttiContext.GetType(AClass);
RttiMethod := RttiType.GetMethod(AMethodName);
Result := RttiMethod.Invoke(RttiObject,AArgs);
finally
RttiObject.Free;
end;
end;

Related

Delphi Split-Merge String without seperators?

Hello I m working about string split-merge like this:
Button1.click...etc.
var
s:String;
ars:array[1..10] of String;
i:integer;
begin
ars[1]:='0';
ars[2]:='012';
ars[3]:='23';
ars[4]:='458';
ars[5]:='022'; // These values are example of.
ars[6]:='001';
ars[7]:='0125';
ars[8]:='250';
ars[9]:='859';
ars[10]:='9';
for i:=1 to 10 do
begin
s:=s+ars[i];
end;
//Finally I get string like this example: '01123641054257867420..etc..'
end;
How can I split the final string back? I dont know the Length of the ars array value. As you see some of it 4 bytes, 3 bytes, 2 bytes.. I merge all of it and then I want to split it again.. How can I do this? Is there any idea or Algorithms? I searched in Google but I did not anything.. Thank you..
#Jens Borrisholt My codes are:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{ TMyStrings }
{$R *.dfm}
type TMyStrings = class(TStringList)
protected
function GetTextStr: string; override;
end;
function TMyStrings.GetTextStr: string;
var
Element: String;
begin
Result := '';
for Element in Self do
Result := Result + Element;
end;
end.
Where is the problem?
There is no built-in functionality to do that, since you throw away information (the length of each string). So that information have to be stored somewhere.
You could use a TStringList descendant :
Interface
TMyStrings = class(TStringList)
protected
function GetTextStr: string; override;
end;
Implementation
{ TMyStrings }
function TMyStrings.GetTextStr: string;
var
Element: String;
begin
Result := '';
for Element in Self do
Result := Result + Element;
end;
And how to use it :
procedure TForm5.FormCreate(Sender: TObject);
var
MyStrings : TMyStrings;
begin
MyStrings := TMyStrings.Create;
MyStrings.Add('0');
MyStrings.Add('012');
MyStrings.Add('23');
MyStrings.Add('458');
MyStrings.Add('022'); // These values are example of.
MyStrings.Add('001');
MyStrings.Add('0125');
MyStrings.Add('250');
MyStrings.Add('859');
MyStrings.Add('9');
Caption := MyStrings.Text;
FreeAndNil(MyStrings);
end;
With this in hand you can get your list as a joined string, and you still have the original information about each string.

Why don't I get my return value from my form

With this code I call a form
procedure TfrmMain.actDevTest_2Execute(Sender: TObject);
var
SelectedApp: string;
begin
if ApplicationSelect(Self, SelectedApp) then
ShowMessage(SelectedApp);
end;
The form is looking like the following
unit F_JsApplicationSelect;
interface
uses
{$Include UniDACCommon.inc}
Db, MemDS, DbAccess, Uni,
Classes, Controls, Forms,
U_Forms.Move,
Winapi.Messages, U_CustomMessages,
Dialogs, StdCtrls, Buttons, ComCtrls,
cxGroupBox, cxGraphics, cxControls, cxLookAndFeels,
cxLookAndFeelPainters, cxStyles, dxSkinsCore, dxSkinOffice2010Blue,
dxSkinscxPCPainter, cxCustomData, cxFilter, cxData, cxDataStorage, cxEdit,
cxNavigator, cxDBData, cxCheckBox, cxTextEdit, cxContainer, Vcl.Menus,
cxButtons, cxGridLevel, cxGridCustomTableView, cxGridTableView,
cxGridDBTableView, cxClasses, cxGridCustomView, cxGrid,
dxmdaset;
type
TfrmJsApplicationSelect = class(TForm)
grdApplicationsView1: TcxGridDBTableView;
grdApplicationsLevel1: TcxGridLevel;
grdApplications: TcxGrid;
colContact: TcxGridDBColumn;
colSection: TcxGridDBColumn;
colSelected: TcxGridDBColumn;
cxGroupBox1: TcxGroupBox;
btnOK: TcxButton;
srcApplications: TUniDataSource;
mdApplications: TdxMemData;
mdApplicationsfldselected: TBooleanField;
mdApplicationsfldcontact: TStringField;
mdApplicationsfldsection: TStringField;
mdApplicationsfldposition: TStringField;
mdApplicationsflddate: TDateField;
mdApplicationsfldguid: TStringField;
colPosition: TcxGridDBColumn;
colDdate: TcxGridDBColumn;
procedure FormKeyPress(Sender: TObject; var Key: Char);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormShow(Sender: TObject);
procedure grdApplicationsView1CellDblClick(Sender: TcxCustomGridTableView;
ACellViewInfo: TcxGridTableDataCellViewInfo; AButton: TMouseButton;
AShift: TShiftState; var AHandled: Boolean);
private
procedure SetupApplications;
procedure MessageClose(var aMessage: TMessage); message WM_FORMCLOSE;
public
constructor Create(aOwner: TComponent; var aApplication: string); reintroduce;
end;
function ApplicationSelect(aOwner: TComponent; var aApplication: string): boolean;
implementation
{$R *.dfm}
uses
System.SysUtils, Winapi.Windows,
F_UniConn,
U_Logfile,
U_AppDb, U_User;
var
lApplication : string;
function ApplicationSelect(aOwner: TComponent; var aApplication: string): boolean;
begin
with TfrmJsApplicationSelect.Create(aOwner, aApplication) do
try
Result := ShowModal = mrOK;
finally
Release;
end;
end;
procedure TfrmJsApplicationSelect.MessageClose(var aMessage: TMessage);
begin
Close;
end;
procedure TfrmJsApplicationSelect.SetupApplications;
var
Query: TUniQuery;
begin
Query := frmUniConn.CreateQuery;
try
Query.SQL.Clear;
Query.SQL.Add('SELECT fldapplication_guid');
Query.SQL.Add(' ,fldapplication_date');
Query.SQL.Add(' ,fldcontact_name');
Query.SQL.Add(' ,fldsection_desc');
Query.SQL.Add(' ,fldposition_desc');
Query.SQL.Add(' ,fldcreated_by');
Query.SQL.Add(' FROM ' + QueryJsApplications);
Query.SQL.Add(' WHERE (fldcreated_by = :fldcreated_by)');
Query.SQL.Add(' ORDER BY fldapplication_date DESC');
Query.ParamByName('fldcreated_by').AsString := User.ID;
try
Query.Execute;
if Query.RecordCount > 0 then
begin
while not Query.Eof do
begin
mdApplications.Open;
mdApplications.Append;
mdApplications.FieldByName('fldselected').AsBoolean := False;
mdApplications.FieldByName('fldguid').AsString := Query.FieldByName('fldapplication_guid').AsString;
mdApplications.FieldByName('flddate').AsDateTime := Query.FieldByName('fldapplication_date').AsDateTime;
mdApplications.FieldByName('fldcontact').AsString := Query.FieldByName('fldcontact_name').AsString;
mdApplications.FieldByName('fldsection').AsString := Query.FieldByName('fldsection_desc').AsString;
mdApplications.FieldByName('fldposition').AsString := Query.FieldByName('fldposition_desc').AsString;
mdApplications.FieldByName('fldguid').AsString := Query.FieldByName('fldapplication_guid').AsString;
mdApplications.Post;
Query.Next;
end;
mdApplications.First;
end;
except
on E:exception do
Logfile.Error('F_JsApplicationSelect.SetupApplications: ' + E.Message);
end;
finally
Query.Free;
end;
end;
constructor TfrmJsApplicationSelect.Create(aOwner: TComponent; var aApplication: string);
begin
inherited Create(aOwner);
lApplication := aApplication;
end;
procedure TfrmJsApplicationSelect.FormClose(Sender: TObject; var Action: TCloseAction);
begin
try
mdApplications.First;
while not mdApplications.Eof do
begin
if mdApplications.FieldByName('fldselected').AsBoolean = True then
begin
ShowMessage(mdApplications.FieldByName('fldguid').AsString);
lApplication := mdApplications.FieldByName('fldguid').AsString;
ShowMessage(lApplication);
end;
mdApplications.Next;
end;
except
on E: exception do
Logfile.Error('F_JsApplicationSelect.FormClose: ' + E.Message);
end;
end;
procedure TfrmJsApplicationSelect.FormKeyPress(Sender: TObject; var Key: Char);
begin
If Ord(Key) = 27 Then
ModalResult := mrAbort;
end;
procedure TfrmJsApplicationSelect.FormShow(Sender: TObject);
begin
SetupApplications;
ActiveControl := grdApplications;
if grdApplicationsView1.DataController.RecordCount > 0 then
begin
grdApplicationsView1.Controller.GoToFirst(False);
grdApplicationsView1.Controller.FocusedRecord.MakeVisible;
end;
end;
procedure TfrmJsApplicationSelect.grdApplicationsView1CellDblClick(
Sender: TcxCustomGridTableView; ACellViewInfo: TcxGridTableDataCellViewInfo;
AButton: TMouseButton; AShift: TShiftState; var AHandled: Boolean);
begin
try
mdApplications.Edit;
mdApplications.FieldByName('fldselected').AsBoolean := Not mdApplications.FieldByName('fldselected').AsBoolean;
mdApplications.UpdateRecord;
except
on E: exception do
Logfile.Error('F_JsApplicationSelect.grdApplicationsView1CellDblClick: ' + E.Message);
end;
end;
end.
But why don't I get any value in my SelectedApp variable?
I have another form with identical functions only the var I send to it is a TStringList - that works OK. But the string doesn't work at all.
The code that is needed to understand this is:
function ApplicationSelect(aOwner: TComponent;
var aApplication: string): boolean;
begin
with TfrmJsApplicationSelect.Create(aOwner, aApplication) do
try
Result := ShowModal = mrOK;
finally
Release;
end;
end;
which in turn calls
constructor TfrmJsApplicationSelect.Create(aOwner: TComponent;
var aApplication: string);
begin
inherited Create(aOwner);
lApplication := aApplication;
end;
So, you are asking why the caller of ApplicationSelect does not observe any modification to aApplication when the call to ApplicationSelect returns.
You don't modify the var parameter aApplication in ApplicationSelect. You do pass it as a var parameter to TfrmJsApplicationSelect.Create but again TfrmJsApplicationSelect.Create does not modify it. Since a string variable is a value, the caller sees no modification to the variable, because it was not modified.
My other comment about ApplicationSelect is that you should call Free rather than Release.
Beyond that I could make many more comments about your code, but I will refrain from attempting a comprehensive code review and comment solely on the direct question that you asked.
In the comments you ask why changing aApplication to TStringList allows the caller to observe modifications. That's because Delphi class variables are references to the object. When you pass a TStringList variable as a parameter, you are passing a reference to the object. When you call methods on that object, any mutations are performed on the actual object.
So, how would I change this code to allow a string value to be returned? First of all I would make ApplicationSelect be a function that returns a string. In case of cancellation I would Abort.
function SelectApplication(aOwner: TComponent): string;
var
Form: TfrmJsApplicationSelect;
begin
Form := TfrmJsApplicationSelect.Create(aOwner);
try
if Form.ShowModal <> mrOK then
Abort;
Result := Form.Application;
finally
Free;
end;
end;
I would absolutely remove the global variable lApplication. You should avoid using global variables if at all possible. I'd remove every single one from the code here.
Instead add a private field to the form to hold the information:
FApplication: string;
And expose it as a public property:
property Application: string read FApplication;
Then the form merely needs to set FApplication and the caller can see that value.

Passing value to synchronize thread

I'm new to thread, so i've been trying with this for hours (i'm using XE4),
i have a simple thread
type
TSendThread = class(TThread)
private
public
procedure proc(const s : string);
protected
procedure Execute; override;
end;
procedure TSendThread.proc(const S: String);
begin
showmessage(s);
end;
Now, in my main form, i want to call that "proc" with :
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
begin
t := TSendThread.create(true);
t.Synchronize(nil, t.proc('foo'));
end;
But whenever i try t compile that i get :
There is no overloaded version of 'Synchronize' that can be called
with these arguments
that does not make sense (to me) because when i remove the "S" parameter from "proc" it works fine.
Look at the various declarations of TThread.Synchronize(). You are trying to call the version that takes a TThreadMethod as input. TThreadMethod is parameter-less:
TThreadMethod = procedure of object;
That is why passing just t.Proc works but passing t.Proc('foo') does not.
With that said, you are completely misusing TThread.Synchronize(). You don't need to create a TThread object in order to use the static version of Synchronize(). And if you do create a TThread object, make it actually do something, like this:
type
TSendThread = class(TThread)
public
fStr: String;
procedure DoProc;
procedure Proc(const S: string);
protected
procedure Execute; override;
end;
procedure TSendThread.Execute;
begin
Proc('foo');
end;
procedure TSendThread.Proc(const S: string);
begin
fStr := S;
Synchronize(DoProc);
end;
procedure TSendThread.DoProc;
begin
ShowMessage(fStr);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
begin
t := TSendThread.Create(False);
t.WaitFor;
t.Free;
end;
However, because you are using XE4, Synchronize() also supports anonymous procedures as well, which would eliminate your TSendThread class completely in this example, eg:
procedure TForm1.Button1Click(Sender: TObject);
begin
TThread.Synchronize(nil,
procedure
begin
ShowMessage('foo');
end
);
end;
Update: given new info about what you REALLY want to do with your thread, you need to go about it like this instead:
type
TSendThread = class(TThread)
private
fURL, fMethod, fParam: string;
protected
procedure Execute; override;
public
constructor Create(aURL, aMethod, aParam: string);
end;
constructor TSendThread.Create(aURL, aMethod, aParam: string);
begin
inherited Create(False);
fURL := aUrl;
fMethod := aMethod;
fParam := aParam;
end;
procedure TSendThread.Execute;
begin
// use fURL, fMethod, and fParam as needed...
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
begin
t := TSendThread.Create('url', 'method', 'param');
...
end;
Or like this:
type
TSendThread = class(TThread)
private
fURL, fMethod, fParam: string;
procedure GetValues;
protected
procedure Execute; override;
end;
procedure TSendThread.GetValues;
begin
fURL := ...;
fMethod := ...;
fParam := ...;
end;
procedure TSendThread.Execute;
begin
Synchronize(GetValues);
// use fURL, fMethod, and fParam as needed...
end;
procedure TForm1.Button1Click(Sender: TObject);
var
t : TSendThread;
begin
t := TSendThread.Create(False);
...
end;
Synchronize only takes a parameterless procedure, as I'm sure you've figured out. That means you have to use properties you get from the main thread in order to do certain things with it. For instance, my thread object is named monitor:
Synchronize(UpdateCaption); // as called in the thread code.
procedure monitor.UpdateCaption;
// synchronize procedure for monitor thread - updates memo on form.
begin
With Form1.CommandText.Lines do
Add(TextString);
end;
Alternatively, you pass messages to the main thread, but this is a quick in a pinch way to do it.
Another approach could be to write a wrapper function which takes no arguments, and call this parameterless method in Synchronize().

Delphi - Create Custom Form Instance (Having Custom Constructor Create) From String Form Name

I'm currently confused about creating form using string form name (as in Is there a way to instantiate a class by its name in delphi?) but my form has it's own constructor create.
//-BASE CLASS-//
TBaseForm = class(TForm)
constructor Create(param1, param2: string); overload;
protected
var1, var2: string;
end;
constructor TBaseForm.Create(param1, param2: string);
begin
inherited Create(Application);
var1 := param1;
var2 := param2;
end;
//-REAL CLASS-//
TMyForm = class(TBaseForm)
end;
//-CALLER-//
TCaller = class(TForm)
procedure Btn1Click(Sender: TObject);
procedure Btn2Click(Sender: TObject);
end;
uses UnitBaseForm, UnitMyForm;
procedure TCaller.Btn1Click(Sender: TObject);
begin
TMyForm.Create('x', 'y');
end;
procedure TCaller.Btn1Click(Sender: TObject);
var PC: TPersistentClass;
Form: TForm;
FormBase: TBaseForm;
begin
PC := GetClass('TMyForm');
// This is OK, but I can't pass param1 & 2
Form := TFormClass(PC).Create(Application);
// This will not error while compiled, but it will generate access violation because I need to create MyForm not BaseForm.
FormBase := TBaseForm(PC).Create('a', 'z');
end;
Based on the code I provided, how can I create a dynamically custom constructor form just by having string form name?
Or it's really impossible? (I started to think it's impossible)
You need to define a class of TBaseForm data type and then type-cast the result to GetClass() to that type, then you can invoke your constructor. You also need to declare your construuctor as virtual so derived class constructors can be called correctly.
Try this:
type
TBaseForm = class(TForm)
public
constructor Create(param1, param2: string); virtual; overload;
protected
var1, var2: string;
end;
TBaseFormClass = class of TBaseForm;
.
procedure TCaller.Btn1Click(Sender: TObject);
var
BF: TBaseFormClass;
Form: TBaseForm;
begin
BF := TBaseFormClass(GetClass('TMyForm'));
Form := BF.Create('a', 'z');
end;

Delphi: How to wait all info to be read from socket using thread

I have Delphi lib which must return information read via socket.
function GetBufferInfo(Address: PChar): PChar; export; stdcall;
var
BD: TBufferData;
begin
BD := TBufferData.Create;
Result := PChar(TBufferData.GetData);
BD.Free;
end;
TBufferData class has a method ReadData which is being called when socket Read event fires. So it can be called several times until all info is read. The problem is how to wait while information is being read and don't go out of GetBufferInfo method. I thought about threads but don't know how exactly it can be done.
I created a small example which demonstrates the issue:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes, Windows;
type
TBufferData = class
private
FResult: string;
public
constructor Create;
procedure ReadData(Sender: TObject; Buf: string; var Size: Integer);
function GetData: string;
end;
{ TBufferData }
var
BD: TBufferData;
s: string;
{ TBufferData }
constructor TBufferData.Create;
begin
FResult := 'Some text received via socket';
end;
function TBufferData.GetData: string;
begin
Result := FResult;
end;
procedure TBufferData.ReadData(Sender: TObject; Buf: string; var Size: Integer);
begin
//info is being received from socket
FResult := FResult + Buf;
end;
begin
BD := TBufferData.Create;
s := BD.GetData;
Writeln(s);
BD.Free;
Readln;
end.
Thanks in advance
Yura
Only the code that is reading the socket will know when the data is finished. Until it detects that condition, it should not be storing any data for GetBufferInfo() to access yet. Only when the data is finished should GetBufferInfo() then be able to return it. So you need to re-write your code to do that.
Application.ProcessMessages for console would look like this. State flag will be set outside.
while State <> stDone do begin
...
ProcessMessages;
end;
procedure ProcessMessages;
var
Msg: TMsg;
begin
if PeekMessage(Msg,0,0,0,0) then begin
GetMessage(Msg,0,0,0);
DispatchMessage(Msg);
end;
Sleep(10);//sleep to avoid 25% processor decrease
end;

Resources