Coming back to this after fixing GUI component problems in porting to FPC/Lazarus on Linux...
A few additional questions:
1. Data container
As you pointed out the old code uses string as the container even though the data is not necessarily textual.
So if I were to change that to use TIdBytes instead, what would the read function in the thread look like?
Now with FRxData declared as string:
If I instead use TIdBytes is the read function going to look like this?
Do I have to set the size of FRxData before calling the read or is that handled inside the read?
2. Sending the data via the event
Is the synchronized call to the event handler getting a copy of FRxData or FRxData itself?
Does it have to set back the length of the container to zero after processing the data?
Right now I have the code at the bottom, which compiles but the application itself does not because I have not yet gotten rid of the TClientSocket stuff coming from the Delphi7 implementation...
3. How to handle the socket messages?
I am looking for how to handle the various events existing in the old code coming from TClientSocket and in need of an Indy implementation..
This is the section of code I ave problem with there. As you can see I have as yet just copied the Delphi code into a section inside a conditional.
The compiler stops at the first line complaining about the missing identifier:
I have looked at the documentation and found that there is an event OnStatus that gets fired for any status change, so maybe that could replace all 4 of the above separate events?
Something like this:
However, I don't really know if this is the correct place.....
There seems also to be other events available when looking in Lazarus code completion, here are all I found:
Which one should I use for example in order to create and terminate the read thread?
Notice that I know when the socket is commanded to connect but not necessarily when it is disconnected since that can be commanded from the client side.
My current implementation of the event driven data reception class using TIdTcpClient shown below.
Does it look sensible now, especially how the thread is created and destroyed in connect/disconnect?
A few additional questions:
1. Data container
As you pointed out the old code uses string as the container even though the data is not necessarily textual.
So if I were to change that to use TIdBytes instead, what would the read function in the thread look like?
Now with FRxData declared as string:
Code:
FRxData: string;
...
FRxData := FConn.IOHandler.ReadString(FConn.IOHandler.InputBuffer.Size);
If I instead use TIdBytes is the read function going to look like this?
Code:
FRxData: TIdBytes;
...
FConn.IOHandler.ReadBytes(FRxData, FConn.IOHandler.InputBuffer.Size, false);
2. Sending the data via the event
Is the synchronized call to the event handler getting a copy of FRxData or FRxData itself?
Does it have to set back the length of the container to zero after processing the data?
Right now I have the code at the bottom, which compiles but the application itself does not because I have not yet gotten rid of the TClientSocket stuff coming from the Delphi7 implementation...
3. How to handle the socket messages?
I am looking for how to handle the various events existing in the old code coming from TClientSocket and in need of an Indy implementation..
This is the section of code I ave problem with there. As you can see I have as yet just copied the Delphi code into a section inside a conditional.
Code:
{$IFDEF FPC}
procedure OnClientError(Sender: TObject; Socket: TIdTCPClient; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure OnSocketConnect(Sender: TObject; Socket: TIdTCPClient);
procedure OnSocketDisconnect(Sender: TObject; Socket: TIdTCPClient);
procedure OnSocketRead(Sender: TObject; Socket: TIdTCPClient);
{$ELSE}
procedure OnClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer);
procedure OnSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
procedure OnSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket);
procedure OnSocketRead(Sender: TObject; Socket: TCustomWinSocket);
{$ENDIF}
Code:
class_SSRemoteClient.pas(110,82) Error: Identifier not found "TErrorEvent"
I have looked at the documentation and found that there is an event OnStatus that gets fired for any status change, so maybe that could replace all 4 of the above separate events?
Something like this:
Code:
procedure TTcpClientComm.OnStatusChange(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
{This procedure parses the socket status change messages, what to do in the respective cases?}
begin
case AStatus of
hsResolving: ;
hsConnecting: ;
hsConnected: ; //Create the read thread here?
hsDisconnecting: ;
hsDisconnected: ; //Terminate the read thread here?
hsStatusText: ; //What is this accomplishing?
end;
end;
constructor TTcpClientComm.Create;
begin
FConn := TIdTCPClient.Create(NIL);
FConn.OnStatus := OnStatusChange;
end;
However, I don't really know if this is the correct place.....
There seems also to be other events available when looking in Lazarus code completion, here are all I found:
Code:
OnAfterBind
OnBeforeBind
OnConnected
OnDisconnected
OnSocketAllocated
OnStatus
OnWork
OnWorkBegin
OnWorkEnd
Notice that I know when the socket is commanded to connect but not necessarily when it is disconnected since that can be commanded from the client side.
My current implementation of the event driven data reception class using TIdTcpClient shown below.
Does it look sensible now, especially how the thread is created and destroyed in connect/disconnect?
Code:
{$IFDEF FPC}
{$MODE Delphi}
{$ENDIF}
interface
uses
Classes,
SysUtils,
IdBaseComponent,
IdComponent,
IdTCPConnection,
IdTCPClient,
IdGlobal
;
type
TCommRxEvent = procedure (Sender: TObject; Data: TIdBytes) of object;
{ TReadingThread }
TReadingThread = class(TThread)
protected
FConn: TIdTCPConnection;
FOnRxData: TCommRxEvent;
FRxData: TIdBytes;
procedure Execute; override;
procedure DoOnRxData;
public
constructor Create(ACon: TIdTCPConnection);
property OnRxData: TCommRxEvent read FOnRxData write FOnRxData;
end;
{ TTcpClientComm }
TTcpClientComm = class(TObject)
private
FConn: TIdTCPClient;
FReadThread: TReadingThread;
FOnRxData: TCommRxEvent;
FLastError: string;
function GetConnected: boolean;
procedure OnStatusChange(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
public
constructor Create;
destructor Destroy; override;
property OnRxData: TCommRxEvent read FOnRxData write FOnRxData;
property LastError: string read FLastError;
property Connected: boolean read GetConnected;
procedure Connect(Host: string; Port: word);
procedure Disconnect;
function WriteString(Data: string): boolean; overload;
function WriteData(Data: TIdBytes): boolean; overload;
end;
implementation
{ TTcpClientComm }
function TTcpClientComm.GetConnected: boolean;
begin
Result := FConn.Connected;
end;
procedure TTcpClientComm.OnStatusChange(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string);
{This procedure parses the socket status change messages, what to do in the respective cases?}
begin
case AStatus of
hsResolving: ;
hsConnecting: ;
hsConnected: ;
hsDisconnecting: ;
hsDisconnected: ;
hsStatusText: ;
end;
end;
constructor TTcpClientComm.Create;
begin
FConn := TIdTCPClient.Create(NIL);
FConn.OnStatus := OnStatusChange;
end;
destructor TTcpClientComm.Destroy;
begin
if Connected then
Disconnect;
inherited Destroy;
end;
procedure TTcpClientComm.Connect(Host: string; Port: word);
begin
FConn.Connect(Host, Port);
FReadThread := TReadingThread.Create(FConn);
end;
procedure TTcpClientComm.Disconnect;
begin
FReadThread.Terminate;
FConn.Disconnect;
end;
function TTcpClientComm.WriteString(Data: string): boolean;
var
DataBytes: TIdBytes;
begin
Result := false;
if Length(Data) = 0 then
begin
FLastError := 'No data';
exit;
end;
SetLength(DataBytes, Length(Data));
Move(Data[1], DataBytes[0], Length(Data));
Result := WriteData(DataBytes);
end;
function TTcpClientComm.WriteData(Data: TIdBytes): boolean;
begin
Result := false;
if Length(Data) = 0 then
begin
FLastError := 'No data';
exit;
end;
if not FConn.Connected then
begin
FLastError := 'Not connected';
exit;
end;
FLastError := '';
try
FConn.IOHandler.Write(Data);
Result := true;
except
on E: Exception do
FLastError := E.Message;
end;
end;
{ TReadingThread }
procedure TReadingThread.Execute;
begin
while not Terminated do
begin
if FConn.Connected then
begin
if FConn.IOHandler.CheckForDataOnSource(10) then
begin
if FConn.IOHandler.InputBuffer.Size > 0 then
begin
// read bytes from FConn up to InputBuffer.Size bytes ...
FConn.IOHandler.ReadBytes(FRxData, FConn.IOHandler.InputBuffer.Size, true);
// process bytes as needed ...
if Assigned(FOnRxData) then
Synchronize(DoOnRxData);
end;
end;
end;
end;
end;
procedure TReadingThread.DoOnRxData;
begin
if Assigned(FOnRxData) then
FOnRxData(Self, FRxData);
end;
constructor TReadingThread.Create(ACon: TIdTCPConnection);
begin
FConn := ACon;
inherited Create(False);
end;
end.