How to communicate between Delphi Windows Desktop Application and Delphi Windows Service Application
I have already posted how to prepare a Windows Service Application in Delphi in my blog. And this time I am adding how to communicate between a Delphi Windows Desktop Application and Delphi Windows Service Application.
For this I have used IndyTCP Server/Client components to communicate with a Server that deployed as a Windows Service.
A little description about Indy TCP Server/Client Components.
In this blog I have referenced only Indy TCP Server(TIdTCPServer) and Client(TIdTCPClient) components. Indy works in a way to have the client (TidTCPClient) connect to the server (TidTCPServer) and then exchange data between them, back-and-forth until the connection is terminated either willfully or by premature disconnect.
For in detail explanation I have prepared a Server application which can be deployed as Windows Service and a desktop Client application. Desktop application can connect will server application and send request. Then server application can read a client's request data and process on it. Then Server can send a response to Client as per request. In this example I have just passed String data between Client and Server but we can also pass different type of data by using Buffer and Stream type. You can find a good example in following link.
Delphi TidTCPServer and TidTCPClient transferring a record
Now my Examples...
In my example I have just created sample Add, Subtract, Multiply and divide function for two passed values. So to get the Server result Client application need to pass request in following format. However we can change the data format as per our need.
Method;Param1;Param2
Preparing a Windows Service Application with TIdTCPServer component which will act as a server and can response a client request.
To create a service application in Delphi start Delphi IDE and select File->New->Other. Then select "Service Application". Then you can see application which includes a Datamodule descendent Tservice class.
Then put a TIdTCPServer component on TService data module like follow
Following is code of Service application Server form ... But still you face any issue during create a service application please visit Windows Service Application in Delphi
unit TestService;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.SvcMgr, Vcl.Dialogs,
IdContext, IdServerIOHandler, IdServerIOHandlerSocket, IdServerIOHandlerStack,
IdBaseComponent, IdComponent, IdCustomTCPServer, IdTCPServer;
type
TService1 = class(TService)
IdTCPServer1: TIdTCPServer;
procedure ServiceStart(Sender: TService; var Started: Boolean);
procedure IdTCPServer1Execute(AContext: TIdContext);
procedure ServiceStop(Sender: TService; var Stopped: Boolean);
procedure ServiceShutdown(Sender: TService);
private
{ Private declarations }
// PROCEDURE TO PARSE REQUEST PARAMETERS // IN THIS CASE i AM FOLLOWING MATHODNAME;PARAM1;PARAM2 //
procedure ParseInData(strMain: string; var slMain: TStringList);
// SERVER MATHOD TO ADD 2 VALUE //
function Add(var A: Integer; var B: Integer): Integer;
// SERVER MATHOD TO sub 2 VALUE //
function Substract(var A: Integer; var B: Integer): Integer;
// SERVER MATHOD TO sub 2 VALUE //
function Multi(var A: Integer; var B: Integer): Integer;
// SERVER MATHOD TO sub 2 VALUE //
function Division(var A: Integer; var B: Integer): Integer;
public
function GetServiceController: TServiceController; override;
{ Public declarations }
end;
var
Service1: TService1;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
Service1.Controller(CtrlCode);
end;
// SERVER METHODS TO PROCESS REQUEST DATA FOR REPONSE //
function TService1.Add(var A, B: Integer): Integer;
begin
Result := A + B;
end;
function TService1.Substract(var A, B: Integer): Integer;
begin
Result := A - B;
end;
function TService1.Multi(var A, B: Integer): Integer;
begin
Result := A * B;
end;
function TService1.Division(var A, B: Integer): Integer;
begin
Result := A DIV B;
end;
function TService1.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TService1.IdTCPServer1Execute(AContext: TIdContext);
var
sRequest: string;
slReqData: TStringList;
sMethod: string;
ival1, ival2: Integer;
sResponse: string;
begin
sRequest := AContext.Connection.Socket.ReadLn(); // READ CLIENT REQUEST
// PARSE RQUEST DATA TO GET METHOD NAME AND PARAMAS //
slReqData := TStringList.Create;
ParseInData(sRequest, slReqData);
try
// PREPARE RESPONSE AS PER REQUEST //
sResponse := '';
try
// GET MEHOTD NAME AND PARAMETER VALUES FORM PARSED STRING LIST //
sMethod := UpperCase(slReqData.Strings[0]);
ival1 := StrToIntDef(slReqData.Strings[1], 0);
ival2 := StrToIntDef(slReqData.Strings[2], 0);
if (sMethod = 'ADD') then // TO CALL ADD METHODS
sResponse := IntToStr(Add(ival1, ival2))
else if (sMethod = 'SUB') then // TO CALL SUBSTRACT METHODS
sResponse := IntToStr(Substract(ival1, ival2))
else if (sMethod = 'MULT') then // TO CALL MULTIPLICATION METHODS
sResponse := IntToStr(Multi(ival1, ival2))
else if (sMethod = 'DIV') then // TO CALL DIVISION METHODS
sResponse := IntToStr(Division(ival1, ival2))
else
sResponse := 'Method not found';
except
// IF ANY EXCEPTION DURING PROCESS REQUEST DATA //
sResponse := 'Bad REQUEST format. Please follow METHOD;PARAM1;PARAM2 format.';
end;
finally
slReqData.Free;
AContext.Connection.Socket.WriteLn(sResponse); // RESPONSE TO CLIENT
end;
end;
procedure TService1.ParseInData(strMain: string; var slMain: TStringList);
begin
// METHOD TO PARSE REQUEST IN FORMAT METHD;PARAM1;PARAM2
slMain.Clear;
slMain.Delimiter := ';';
slMain.DelimitedText := strMain;
end;
procedure TService1.ServiceShutdown(Sender: TService);
begin
// STOP INDY SERVER ON SERVICE SHUTDOWN//
IdTCPServer1.StopListening;
IdTCPServer1.Active := False;
end;
procedure TService1.ServiceStart(Sender: TService; var Started: Boolean);
begin
// START INDY SERVER ON SERVICE START//
IdTCPServer1.DefaultPort := 12345;
IdTCPServer1.Active := True;
IdTCPServer1.StartListening;
end;
procedure TService1.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
// STOP INDY SERVER ON SERVICE STOP//
IdTCPServer1.StopListening;
IdTCPServer1.Active := False;
end;
end.
Then prepare build of Service application and then install the Service.exe with /silent mode.
And after install Start the Service
Now preparing a Windows Desktop Client Application with TIdTCPPClient component which will communicate with the Server application that we just installed as a windows Service.
Just create new Delphi VCL Application.
Then in form put a TIdTCPClient component.
Following is code of client form...
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent,
IdComponent, IdTCPConnection, IdTCPClient, IdIOHandlerStream, IdIOHandler,
IdIOHandlerSocket, IdIOHandlerStack, IdIntercept, IdContext,
IdCustomTCPServer, IdTCPServer, IDGlobal, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
btnConnect: TButton;
btnDisconnect: TButton;
btnSend: TButton;
btnClose: TButton;
Memo1: TMemo;
IdTCPClient1: TIdTCPClient;
Shape1: TShape;
Label1: TLabel;
edtVal1: TEdit;
edtVal2: TEdit;
Label2: TLabel;
Label3: TLabel;
cmbMethod: TComboBox;
procedure btnCloseClick(Sender: TObject);
procedure btnConnectClick(Sender: TObject);
procedure btnDisconnectClick(Sender: TObject);
procedure btnSendClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.btnSendClick(Sender: TObject);
var
sMethod : string;
iVal1, iVal2: integer;
sRequest: string;
sResponse: string;
begin
// GET METHOD AND PARAMS VALUES //
sMethod := 'NONE';
if cmbMethod.ItemIndex=0 then
sMethod := 'ADD'
else if cmbMethod.ItemIndex=1 then
sMethod := 'SUB'
else if cmbMethod.ItemIndex=2 then
sMethod := 'MULT'
else if cmbMethod.ItemIndex=3 then
sMethod := 'DIV'
else
sMethod := 'NONE';
// VALIDATE VALUE 1 //
Try
iVal1 := StrToInt(edtVal1.Text);
except
MessageDlg('Please check Value1.',mtError,[mbOK],0);
Exit;
End;
// VALIDATE VALUE 2 //
Try
iVal2 := StrToInt(edtVal2.Text);
except
MessageDlg('Please check Value2.',mtError,[mbOK],0);
Exit;
End;
try
// PREPARE REQUEST AND SEND TO SERVER //
sRequest := sMethod + ';' + IntToStr(iVal1) + ';' + IntToStr(iVal2);
IdTCPClient1.Socket.WriteLn(sRequest);
Memo1.Lines.Add('REQUEST: '+sRequest);
// READ RESPONSE FROM SERVER //
sResponse := IdTCPClient1.Socket.ReadLn();
Memo1.Lines.Add('REPONSE: '+sResponse);
except
MessageDlg('Please check that Server is ready', mtWarning, [mbOK], 0);
Exit;
end;
end;
procedure TForm1.btnCloseClick(Sender: TObject);
begin
// DISCONNECT CLIENT //
IdTCPClient1.Disconnect;
Close;
end;
procedure TForm1.btnConnectClick(Sender: TObject);
begin
// CONNECT CLIENT //
IdTCPClient1.Host := '127.0.0.1'; // LOCALHOST // WE CAN CHANGE IP FOR DIFFERENT SERVER //
IdTCPClient1.Port := 12345; // IS PORT NUMBER THAT WE SET AT CLIENT SIDE TOO //
IdTCPClient1.Connect;
Shape1.Brush.Color := clGreen;
end;
procedure TForm1.btnDisconnectClick(Sender: TObject);
begin
// DISCONNECT CLIENT //
IdTCPClient1.Disconnect;
Shape1.Brush.Color := clRed;
end;
end.
After complete coding prepare build of Client application and execute the exe.
Press connect to connect with Server and Send to send a request.
Here I am following below request data format that I prepare at runtime during request send. However we can change the data format as per our need.
Method;Param1;Param2
REQUEST = DIV;300;50
yeah.. Thats all. Here we go....
And if you guys find any other way or using any other third party components the please share with me with comment.
Thank you for the article. It was very educative.
ReplyDelete