I am using Delphi 2007 & Indy 10; I am a bit of a Delphi noob so apologies if I have missed something obvious...
Background: I have a simple server app which simply sends the word "PING" when you connect to its port. It will also respond if it receives the word "PONG". This is working fine, I have manually tested this using netcat/wireshark.
I am trying to code my client to connect to the port and automatically respond to the word PING whenever it receives it. I have created a simple form with a button to manually connect.
The client connects, but it does not respond to the word PING.
I think the problem lies with:
TLog.AddMsg(FConn.IOHandler.ReadLn);
My debug log reports only as far as "DEBUG: TReadingThread.Execute - FConn.Connected".
My client code:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdCustomTransparentProxy, IdSocks, IdBaseComponent,
IdComponent, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack,
IdTCPConnection, IdTCPClient, IdSync;
type
TReadingThread = class(TThread)
protected
FConn: TIdTCPConnection;
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection); reintroduce;
end;
TLog = class(TIdSync)
protected
FMsg: String;
procedure DoSynchronize; override;
public
constructor Create(const AMsg: String);
class procedure AddMsg(const AMsg: String);
end;
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
IdIOHandlerStack1: TIdIOHandlerStack;
client: TIdTCPClient;
IdSocksInfo1: TIdSocksInfo;
procedure Button1Click(Sender: TObject);
procedure clientConnected(Sender: TObject);
procedure clientDisconnected(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
rt: TReadingThread = nil;
implementation
{$R *.dfm}
constructor TReadingThread.Create(AConn: TIdTCPConnection);
begin
Form1.Memo1.Lines.Add('DEBUG: TReadingThread.Create'); // Debug
FConn := AConn;
inherited Create(False);
end;
procedure TReadingThread.Execute;
begin
Form1.Memo1.Lines.Add('DEBUG: TReadingThread.Execute'); // Debug
while not Terminated and FConn.Connected do
begin
Form1.Memo1.Lines.Add('DEBUG: TReadingThread.Execute - FConn.Connected'); // Debug
TLog.AddMsg(FConn.IOHandler.ReadLn);
end;
end;
constructor TLog.Create(const AMsg: String);
begin
Form1.Memo1.Lines.Add('DEBUG: TLog.Create'); // Debug
FMsg := AMsg;
inherited Create;
end;
procedure TLog.DoSynchronize;
var
cmd : string;
begin
Form1.Memo1.Lines.Add('DEBUG: TLog.DoSynchronize'); // Debug
cmd := copy(FMsg, 1, 1);
if cmd='PING' then begin
Form1.client.Socket.WriteLn('PONG');
end
end;
class procedure TLog.AddMsg(const AMsg: String);
begin
Form1.Memo1.Lines.Add('DEBUG: TLog.AddMsg'); // Debug
with Create(AMsg) do try
Synchronize;
finally
Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Clear;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Host : String;
Port : Integer;
begin
Host := '127.0.0.1';
Port := StrToInt('1234');
client.Host := Host;
client.Port := Port;
with client do
begin
try
Connect;
except
on E: Exception do
Memo1.Lines.Add('Error: ' + E.Message);
end;
end;
end;
procedure TForm1.clientConnected(Sender: TObject);
begin
Form1.Memo1.Lines.Add('DEBUG: TForm1.clientConnected'); // Debug
rt := TReadingThread.Create(client);
end;
procedure TForm1.clientDisconnected(Sender: TObject);
begin
Form1.Memo1.Lines.Add('DEBUG: TForm1.clientDisconnected'); // Debug
if rt <> nil then
begin
rt.Terminate;
rt.WaitFor;
FreeAndNil(rt);
end;
end;
end.
Any help/advice would be appreciated.
Thanks
The reading thread is directly accessing Form1.Memo1, which is not thread safe and can cause deadlocks, crashes, corrupted memory, etc. So it is possible that the reading thread is not even reaching the ReadLn() call at all. You MUST synchronize ALL access to UI controls to the main thread, no matter how trivial the access actually is. Just don't risk it.
Also, you are doing your thread's ping/pong logic inside of TLog itself, where it does not belong. Not to mention that you are truncating the cmd to only its first character before checking its value, so it will NEVER detect a PING command. You need to move the logic back into the thread, where it really belongs, and remove the truncation.
Try this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, IdCustomTransparentProxy, IdSocks, IdBaseComponent,
IdComponent, IdIOHandler, IdIOHandlerSocket, IdIOHandlerStack,
IdTCPConnection, IdTCPClient, IdSync;
type
TReadingThread = class(TThread)
protected
FConn: TIdTCPConnection;
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(AConn: TIdTCPConnection); reintroduce;
end;
TLog = class(TIdSync)
protected
FMsg: String;
procedure DoSynchronize; override;
public
constructor Create(const AMsg: String);
class procedure AddMsg(const AMsg: String);
end;
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
IdIOHandlerStack1: TIdIOHandlerStack;
client: TIdTCPClient;
IdSocksInfo1: TIdSocksInfo;
procedure Button1Click(Sender: TObject);
procedure clientConnected(Sender: TObject);
procedure clientDisconnected(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
rt: TReadingThread = nil;
implementation
{$R *.dfm}
constructor TReadingThread.Create(AConn: TIdTCPConnection);
begin
TLog.AddMsg('DEBUG: TReadingThread.Create');
FConn := AConn;
inherited Create(False);
end;
procedure TReadingThread.Execute;
var
cmd: string;
begin
TLog.AddMsg('DEBUG: TReadingThread.Execute');
while not Terminated do
begin
cmd := FConn.IOHandler.ReadLn;
TLog.AddMsg('DEBUG: TReadingThread.Execute. Cmd: ' + cmd);
if cmd = 'PING' then begin
FConn.IOHandler.WriteLn('PONG');
end
end;
end;
procedure TReadingThread.DoTerminate;
begin
TLog.AddMsg('DEBUG: TReadingThread.DoTerminate');
inherited;
end;
constructor TLog.Create(const AMsg: String);
begin
inherited Create;
FMsg := AMsg;
end;
procedure TLog.DoSynchronize;
begin
Form1.Memo1.Lines.Add(FMsg);
end;
class procedure TLog.AddMsg(const AMsg: String);
begin
with Create(AMsg) do
try
Synchronize;
finally
Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Memo1.Clear;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Host : String;
Port : Integer;
begin
Host := '127.0.0.1';
Port := StrToInt('1234');
client.Host := Host;
client.Port := Port;
try
client.Connect;
except
on E: Exception do
TLog.AddMsg('Error: ' + E.Message);
end;
end;
end;
procedure TForm1.clientConnected(Sender: TObject);
begin
TLog.AddMsg('DEBUG: TForm1.clientConnected');
rt := TReadingThread.Create(client);
end;
procedure TForm1.clientDisconnected(Sender: TObject);
begin
TLog.AddMsg('DEBUG: TForm1.clientDisconnected');
if rt <> nil then
begin
rt.Terminate;
rt.WaitFor;
FreeAndNil(rt);
end;
end;
end.
If that still does not work, then make sure the server is actually delimiting the PING string with a CRLF sequence, or at least a LF character (which is the minimum that ReadLn() looks for by default).
Related
I've been working just for a while trying to make a modal form to inform the user to wait until the job is ends.
This is a simple example of what I'm trying to do:
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)
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TLoader = class(TThread)
private
FStrings: TStrings;
procedure ShowWait;
procedure EndsWait;
public
Constructor Create(AStrings: TStrings);
Destructor Destroy; override;
procedure Execute; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
Var
List: TStrings;
begin
List := TStringList.Create;
try
// Load Some Data here
TLoader.Create(List);
finally
List.Free;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
end;
{ TLoader }
constructor TLoader.Create(AStrings: TStrings);
begin
inherited Create;
FreeOnTerminate:= True;
FStrings:= TStringList.Create;
FStrings.AddStrings(AStrings);
end;
destructor TLoader.Destroy;
begin
FStrings.Free;
inherited;
end;
procedure TLoader.EndsWait;
begin
TForm(Application.FindComponent('FWait')).Free;
end;
procedure TLoader.Execute;
begin
inherited;
Synchronize(ShowWait);
// Do Some Job while not terminated
Sleep(1000);
// Free Wait Form
// This part is not working
Synchronize(EndsWait);
end;
procedure TLoader.ShowWait;
begin
With TForm.Create(Application) do
begin
// Some code
Name:= 'FWait';
ShowModal;
end;
end;
end.
Everything is working as I expected, except Synchronize(EndsWait); which did not close and free the modal form.
How can I display a modal form while a TThread is running and free it when the TThread terminated?
UPDATE:
I've try to do as Remy suggest as the following:
type
TForm2 = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
TLoader = class(TThread)
protected
procedure DoTerminate; override;
procedure DoCloseModal;
public
constructor Create;
procedure Execute; override;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
{ TLoader }
constructor TLoader.Create;
begin
inherited Create;
FreeOnTerminate:= True;
end;
procedure TLoader.DoCloseModal;
begin
Form2.ModalResult:= mrOk;
end;
procedure TLoader.DoTerminate;
begin
inherited DoTerminate;
Synchronize(DoCloseModal);
end;
procedure TLoader.Execute;
begin
inherited;
Sleep(200);
end;
procedure TForm2.FormShow(Sender: TObject);
begin
TLoader.Create;
end;
end.
The main form button click event handler:
procedure TForm1.Button1Click(Sender: TObject);
begin
with TForm2.Create(nil) do
try
ShowModal;
finally
Free;
end;
end;
You have two choices:
Do not use a modal form to begin with. TThread.Synchronize() blocks your thread until the synced method exits, but TForm.ShowModal() blocks that method until the Form is closed. Use TThread.Synchronize() (or better, TThread.Queue()) to Create()+Show() (not ShowModal()) the Wait Form, then return to the thread and let it do its work as needed, then Synchronize()/Queue() again (or, use the thread's OnTerminate event) to Close()+Free() the Wait Form when done.
alternatively, if you want to use a modal Wait Form, then do not let the thread manage the Wait Form at all. Have your button OnClick handler Create()+ShowModal() (not Show()) the Wait Form, and Free() it when ShowModal() exits. Have the Wait Form internally create and terminate the thread when the Wait Form is shown and closed, respectively. If the thread ends before the Wait Form is closed, the thread's OnTerminate handler can Close() the Form (which simply sets the Form's ModalResult) so that ShowModal() will exit in the OnClick handler.
After a while, I made a uWaitForm.pas unit:
unit uWaitForm;
interface
uses
System.Classes, Vcl.Controls, Vcl.Forms;
type
TWaitForm = class(TForm)
private
FThread: TThread;
protected
procedure Activate; override;
procedure DoClose(var Action: TCloseAction); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
TWaitThread = class(TThread)
private
FForm: TWaitForm;
FModalResult: TModalResult;
protected
procedure Execute; override;
procedure DoSetModalResult;
public
constructor Create(AForm: TWaitForm);
destructor Destroy; override;
end;
implementation
{ TWaitForm }
procedure TWaitForm.Activate;
begin
inherited;
FThread:= TWaitThread.Create(Self);
end;
constructor TWaitForm.Create(AOwner: TComponent);
begin
inherited CreateNew(AOwner);
Name:= 'WaitForm';
BorderStyle:= bsDialog;
Caption:= 'Please wait...';
Width:= 200;
Height:= 150;
Position:= poDesktopCenter;
end;
destructor TWaitForm.Destroy;
begin
if Assigned(FThread) then
FThread.Terminate;
inherited;
end;
procedure TWaitForm.DoClose(var Action: TCloseAction);
begin
inherited;
if Assigned(FThread) then
begin
TWaitThread(FThread).FModalResult:= mrCancel;
FThread.Terminate;
end;
end;
{ TWaitThread }
constructor TWaitThread.Create(AForm: TWaitForm);
begin
inherited Create;
FreeOnTerminate:= True;
FForm:= AForm;
FModalResult:= mrOk;
end;
destructor TWaitThread.Destroy;
begin
if Assigned(FForm) then
Synchronize(nil, DoSetModalResult);
inherited;
end;
procedure TWaitThread.DoSetModalResult;
begin
FForm.ModalResult:= FModalResult;
end;
procedure TWaitThread.Execute;
begin
inherited;
// Do the work while not terminated
// You can check for Terminated here and set FModalResult accordingly to receive it from ShowModal
// By default it's mrOk (Job is done, the thread doesn't cancled)
Sleep(200);
end;
end.
Then use it
with TWaitForm.Create(nil) do
try
//Get ModalResult here and do something accordingly
// mrCancel means the dialog form closed and the the Thread job aborted
// mrOk The job is done and the form closed
ShowModal;
finally
Free;
end;
I have problem when run a thread in subform.
main form
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses Unit2;
procedure TForm1.Button1Click(Sender: TObject);
begin
TForm2.create(form1).ShowModal;
end;
SUBform
type
TMthread=class(Tthread)
protected
procedure execute; override;
end;
type
TForm2 = class(TForm)
Label1: TLabel;
procedure FormShow(Sender: TObject);
private
public
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
uses Unit1;
procedure TMthread.Execute;
begin
synchronize( procedure
begin
sleep(200);
freeonterminate:=true;
sleep(200);
form2.label1.Caption:='beep';
form1.button1.Caption:='beep';
end);
end;
procedure TForm2.FormShow(Sender: TObject);
var Loadcombo2: TMthread;
begin
Loadcombo2:=TMthread.Create(False);
end;
Program
program Project1;
uses
Vcl.Forms,
Unit1 in 'Unit1.pas' {Form1},
Unit2 in 'Unit2.pas' {Form2};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TForm1, Form1);
Application.Run;
end.
I got error in Execute Procedure when trying to access Form2.Label1.caption.
my test:
When I add the subform(Form2) in the Initialize section(last code) the the application runs without error, but doesn't change Label1.caption on the Form2.(Button1.caption on the main form is changed)
When I put exactly the same thread in main form it works without issues.
The variable Form2 is never assigned. Because it is a global variable, its value is nil. Thus you encounter an error when you attempt to reference members of Form2.
You create an instance of Form2 like this:
TForm2.Create(Form1).ShowModal;
I suspect that instead you mean to write something like this:
Form2 := TForm2.Create(Form1);
Try
Form2.ShowModal;
Finally
Form2.Free;
End;
When program starts, automatically downloads given EXE file, but if I want to abort the current process and restart to download again or/and if EXE is downloaded successfully one time and would like to download again, program stops with error message: "raised exception class EIdHTTPProtocolException"
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs,idhttp, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
IdHTTP1: TIdHTTP;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Integer);
procedure IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure DownloadFile;
end;
type
xy = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
procedure friss;
end;
var
Form1: TForm1;
szal:xy;
Stream: TMemoryStream;
implementation
{$R *.dfm}
procedure xy.friss;
begin
ShowMessage('kész');
szal.terminate;
end;
procedure TForm1.Button1Click(Sender: TObject); //abort
begin
szal.Suspend;
szal.Terminate;
end;
procedure TForm1.Button2Click(Sender: TObject); //restart
begin
szal:=xy.Create(true);
szal.Resume;
end;
procedure tform1.DownloadFile;
var
Url, FileName: String;
begin
idhttp1:=idhttp1.Create(self);
Url := 'http://livecd.com/downloads/ActiveDataStudioSetup.exe';
Filename := 'c:\setup.zip';
Stream := TMemoryStream.Create;
try
IdHTTP1.Get(Url, Stream);
Stream.SaveToFile(FileName);
finally
Stream.Free;
IdHTTP1.free;
end;
end;
procedure xy.execute;
begin
form1.DownloadFile;
Synchronize(friss);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
szal:=xy.Create(true);
szal.Resume;
end;
procedure TForm1.IdHTTP1Work(ASender: TObject; AWorkMode: TWorkMode;
AWorkCount: Integer);
begin
form1.ProgressBar1.Position:=AWorkCount;
end;
procedure TForm1.IdHTTP1WorkBegin(ASender: TObject; AWorkMode: TWorkMode;
AWorkCountMax: Integer);
begin
form1.ProgressBar1.Max:=AWorkCountMax;
form1.ProgressBar1.Position:=0;
end;
end.
Source code: http://pastebin.com/9DvSyTD7
Project: http://osztott.com/ubXN/cucc.zip
EIdHTTPProtocolException means the HTTP server sent back an error, such as if the requested resource is not found or cannot be accessed. That has nothing to do with your threading logic.
However, there are a lot of problems with your code in general - misuse of TThread and dynamic components, not syncing the worker thread with the main UI thread, etc.
Try something more like this instead:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls, StdCtrls;
type
TForm1 = class(TForm)
ProgressBar1: TProgressBar;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
procedure StartDownload;
procedure StopDownload;
procedure DownloadFinished(Sender: TObject);
public
end;
var
Form1: TForm1;
implementation
uses
IdHTTP, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, IdSync;
{$R *.dfm}
type
TDownloadThread = class(TThread)
private
{ Private declarations }
procedure HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Integer);
procedure HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Integer);
protected
procedure Execute; override;
public
property ReturnValue;
property Terminated;
end;
TDownloadStatusNotify = class(TIdNotify)
protected
Value: Integer;
DownloadBegin: Boolean;
procedure DoNotify; override;
public
constructor Create(AValue: Integer: ADownloadBegin: Boolean); reintroduce;
end;
TFreeDownloadThreadNotify = class(TIdNotify)
protected
Thread: TDownloadThread;
procedure DoNotify; override;
public
constructor Create(AThread: TDownloadThread); reintroduce;
end;
procedure TDownloadThread.Execute;
var
Url, Filename: string;
HTTP: TIdHTTP;
Stream: TMemoryStream;
begin
Url := 'http://livecd.com/downloads/ActiveDataStudioSetup.exe';
Filename := 'c:\setup.zip';
HTTP := TIdHTTP.Create(nil);
try
HTTP.OnWorkBegin := HTTPWorkBegin;
HTTP.OnWork := HTTPWork;
Stream := TMemoryStream.Create;
try
HTTP.Get(Url, Stream);
Stream.SaveToFile(Filename);
finally
Stream.Free;
end;
finally
HTTP.Free;
end;
ReturnValue := 1;
end;
procedure TDownloadThread.HTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Integer);
begin
if Terminated then SysUtils.Abort;
if AWorkMode = wmRead then
TDownloadStatusNotify.Create(AWorkCountMax, True).Notify;
end;
procedure TDownloadThread.HTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Integer);
begin
if Terminated then SysUtils.Abort;
if AWorkMode = wmRead then
TDownloadStatusNotify.Create(AWorkCount, False).Notify;
end;
constructor TDownloadStatusNotify.Create(AValue: Integer; ADownloadBegin: Boolean);
begin
inherited Create;
Value := AValue;
DownloadBegin := ADownloadBegin;
end;
procedure TDownloadStatusNotify.DoNotify;
begin
if DownloadBegin then
begin
Form1.ProgressBar1.Position := 0;
Form1.ProgressBar1.Max := Value;
end else
begin
if Form1.ProgressBar1.Max > 0 then
begin
Form1.ProgressBar1.Position := Value;
end else
begin
// the download size is unknown (most likely chunked) so
// display the current Value somewhere else...
end;
end;
end;
constructor TFreeDownloadThreadNotify.Create(AThread: TDownloadThread);
begin
inherited Create;
MainThreadUsesNotify := True;
Thread := AThread;
end;
procedure TFreeDownloadThreadNotify.DoNotify;
begin
Thread.Free;
end;
var
szal: TDownloadThread = nil;
procedure TForm1.FormCreate(Sender: TObject);
begin
StartDownload;
end;
procedure TForm1.Button1Click(Sender: TObject); //abort
begin
StopDownload;
end;
procedure TForm1.Button2Click(Sender: TObject); //restart
begin
StopDownload;
StartDownload;
end;
procedure TForm1.StartDownload;
begin
szal := TDownloadThread.Create(True);
sza1.OnTerminate := DownloadFinished;
szal.Resume;
end;
procedure TForm1.StopDownload;
begin
if sza1 <> nil then
begin
szal.Terminate;
sza1.WaitFor;
FreeAndNil(sza1);
end;
end;
procedure TForm1.DownloadFinished(Sender: TObject);
begin
if sza1.ReturnValue = 1 then
ShowMessage('kész')
else if sza1.Terminated then
ShowMessage('félbeszakadt')
else
ShowMessage('hiba');
if not sza1.Terminated then
begin
TFreeDownloadThreadNotify.Create(sza1).Notify;
sza1 := nil;
end;
end;
end.
What is the best way to update a status bar in mainform from one tthread class object.
For Example, I have one TThread object that make a very big quanty of stuff and i want that the verbose message acording to what soft do you do appear on status bar.
Create the thread with a reference to a callback method, which can be called synchonized if assigned.
Example;
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TInfoMessageCall=Procedure (const info:String) of object;
TMyThread=Class(TThread)
Constructor Create(Susp:Boolean;CallBack:TInfoMessageCall);overload;
Procedure Execute;override;
private
FMessage:String;
FInfoProc:TInfoMessageCall;
Procedure CallCallBack;
End;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
procedure ACallBack(const s: String);
{ Private-Deklarationen }
public
{ Public-Deklarationen }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TMyThread }
Procedure TForm1.ACallBack(Const s:String);
begin
Caption := s;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
With TMyThread.Create(false,ACallBack) do
FreeOnTerminate := true;
end;
procedure TMyThread.CallCallBack;
begin
if Assigned(FInfoProc) then FInfoProc(FMessage);
end;
constructor TMyThread.Create(Susp: Boolean; CallBack: TInfoMessageCall);
begin
Inherited Create(Susp);
FInfoProc := CallBack;
end;
procedure TMyThread.Execute;
var
i:Integer;
begin
inherited;
for I := 1 to 10 do
begin
FMessage := Format('Info %d',[i]);
Synchronize(CallCallBack);
Sleep(200);
end;
end;
end.
'MyThread' does not run. I do not know whether the problem happens on 'DataTransferServiceStart' procedure. I guess the 'DataTransferServiceStart' procedure does not execute. IDE is Delphi XE. Please help me, thank you very much.
Thread's Unit:
unit Unit_MyThread;
interface
uses
Classes, SysUtils;
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
procedure TMyThread.Execute;
var
log: TextFile;
logPath: String;
i: Integer;
begin
logPath := 'd:\test.log';
AssignFile(log, logPath);
Append(log);
i := 0;
while not self.Terminated do
begin
Sleep(1);
Writeln(log, IntToStr(i));
if i=10 then
Terminate;
i := i + 1;
end;
CloseFile(log);
end;
end.
Main Service Unit:
unit Unit_main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs,
ExtCtrls, DB, ADODB, Unit_MyThread;
type
TDataTransferService = class(TService)
DBSrc: TADOConnection;
procedure DataTransferServiceStart(Sender: TService; var Started: Boolean);
procedure DataTransferServiceContinue(Sender: TService; var Continued: Boolean);
procedure DataTransferServicePause(Sender: TService; var Paused: Boolean);
procedure DataTransferServiceStop(Sender: TService; var Stopped: Boolean);
public
function GetServiceController: TServiceController; override;
end;
var
DataTransferService: TDataTransferService;
MyThread: TMyThread;
implementation
{$R *.DFM}
procedure ServiceController(CtrlCode: DWord); stdcall;
begin
DataTransferService.Controller(CtrlCode);
end;
function TDataTransferService.GetServiceController: TServiceController;
begin
Result := ServiceController;
end;
procedure TDataTransferService.DataTransferServiceStart(Sender: TService;
var Started: Boolean);
begin
MyThread := TMyThread.Create(False);
Started := True;
end;
procedure TDataTransferService.DataTransferServiceContinue(Sender: TService;
var Continued: Boolean);
begin
MyThread.Start;
Continued := True;
end;
procedure TDataTransferService.DataTransferServicePause(Sender: TService;
var Paused: Boolean);
begin
MyThread.Suspended := true;
Paused := True;
end;
procedure TDataTransferService.DataTransferServiceStop(Sender: TService;
var Stopped: Boolean);
begin
MyThread.Terminate;
Stopped := True;
end;
end.
Your service is most likely failing to start because you have a TADOConnection component dropped into your service. You cannot do this in services. Since ADO is COM, you must initialize each thread with CoInitialize(nil) and CoUninitialize, and only create/use your database components within this.
uses
ActiveX;
procedure TDataTransferService.DataTransferServiceStart(Sender: TService;
var Started: Boolean);
begin
CoInitialize(nil);
DBSrc:= TADOConnection.Create(nil);
//Initialize and Connect DBSrc
MyThread := TMyThread.Create(False);
Started := True;
end;
procedure TDataTransferService.DataTransferServiceStop(Sender: TService;
var Stopped: Boolean);
begin
MyThread.Terminate;
//Disconnect DBSrc
DBSrc.Free;
CoUninitialize;
Stopped := True;
end;
Read here: Ok to use TADOConnection in threads