Concat PChar with string in Delphi - string

I need to construct a string and send it via PostMessage, ie.
FileName := String_1 + String_2 + String_3;
PostMessage(FWndHandle, WM_BLA_BLA, NotifyData^.Action, LParam(FileName));
but something isn't working. Plus, FileName is a PChar. The code looks like this:
var
FileName : PChar;
Directory_Str : String;
AnotherString : String;
begin
// Get memory for filename and fill it with data
GetMem(FileName, NotifyData^.FileNameLength + SizeOf(WideChar));
Move(NotifyData^.FileName, Pointer(FileName)^, NotifyData^.FileNameLength);
PWord(Cardinal(FileName) + NotifyData^.FileNameLength)^ := 0;
// TODO: Contact string before sending message
// FileName := AnotherString + Directory_Str + FileName;
PostMessage(FWndHandle, WM_BLA_BLA, NotifyData^.Action, LParam(FileName));
...
end;
Now I need to do contact another string to the variable FileName before calling PostMessage, ie.
FileName := AnotherString + Directory_Str + FileName;
PostMessage(FWndHandle, WM_BLA_BLA, NotifyData^.Action, LParam(FileName));
This would work if FileName was a string, which is not the case here.
Anyone knows how to do that with PChar? I tried these methods, works sometimes but always something breaks at the end:
StrPCopy(FileName, FDirectory + String(FileName));
OR
FileName := PChar(AnotherString + Directory_Str + FileName);

You cannot easily use PostMessage with data passed by reference. The reason being that PostMessage executes asynchronously and you need to keep the memory you are passing alive until the message has been processed by its recipient. I guess that's what is behind your GetMem code.
Obviously this only works within the same process. And you will also find that Windows won't let you use PostMessage for any of its messages that receive pointers. For example, PostMessage with WM_SETTEXT always fails. You can only hope to do this using a user-defined message. And of course you'll need to deallocate the memory in the code that receives the message.
I'm going to assume that you are using a user defined message that does allow sending a string with PostMessage. In which case you already have the solution. Do the concatenation using string variables and then use the first block of code in your answer.
Although you can make it much cleaner like this:
function HeapAllocatedPChar(const Value: string): PChar;
var
bufferSize: Integer;
begin
bufferSize := (Length(Value)+1)*SizeOf(Char);
GetMem(Result, bufferSize);
Move(PChar(Value)^, Result^, bufferSize);
end;
procedure PostString(Window: HWND; Msg: UINT; wParam: WPARAM;
const Value: string);
var
P: PChar;
begin
P := HeapAllocatedPChar(Value);
if not PostMessage(Window, Msg, wParam, LPARAM(P)) then
FreeMem(P);
end;
And you can just call that procedure like this:
PostString(FWndHandle, WM_BLA_BLA, NotifyData^.Action, FDirectory + FileName);
Your current code fails because:
When you call StrPCopy you don't allocate any memory for the longer string.
When you write PChar(AnotherString + Directory_Str + FileName) you fall into the trap of that you were trying to avoid with GetMem. That's a local string which has been deallocated by the time the message is processed.
If you can find a way of solving your problem without using PostMessage to pass a string, that might be preferable to all this complexity.

See David's answer for reasons why your code fails.
I always define a class for distributing strings through PostMessage operations.
No risk of leaks and it's easy to expand with more information.
Type
TMyMessage = class
msg : String;
Constructor Create(aMessage : String);
end;
// Sending the message
var
myMsg : TMyMessage;
...
myMsg := TMyMessage.Create('A message');
if not PostMessage(someHandle,WM_something,WParam(myMsg),0)
then begin
myMsg.free;
... Take care of this condition if PostMessage fails !!
end;
// Receiving the message
procedure TSomeForm.GetMessage(var msg : TMessage);
var
aMsg : TMyMessage;
begin
...
aMsg := TMyMessage(msg.WParam);
try
...
// Do something with the message
finally
aMsg.Free;
end;
end;

Related

How do I get a boolean result from a function using OmniThreadLibrary?

I have a Delphi (Windows) application created using Delphi 10 that has some blocking calls that I would like to convert to using threads. Unfortunately for me, these are not procedures, but functions. (And information on how to return function results from threads appears to be much more limited.) I am trying to familiarize myself with the OmniThreadLibrary, since it seems to be the most flexible and best supported threading library for Delphi, but I'm having trouble with understanding how to do this.
I have been able to get the various OmniThreadLibrary routines to work well with procedures, but when I try to set up a function, I get an error about capturing the result. When I use OmniThreadLibrary's Future example as a starting point, I can get the function to work, but I can't figure out how to connect to the event monitor, how to send messages from the task, etc. So, it seems as if I'm overlooking something no matter which way I try to solve this problem.
Currently, my program does something like this:
If myPing(IPAddress) then
begin
//Do other things hereā€¦
end;
Because myPing is blocking, and I actually need it to wait until myPing returns true before processing further, the application gets sluggish during this process. I'd like to put the myPing call in a thread, which would solve the sluggishness problem, but I can't figure out how to do that in the form of a function using OmniThreadLibrary. (Unless I use a future, in which case I can't figure out how to connect to the Event Monitor.)
Edit 1: Since my original post, I have made a little progress. I was able to connect the Event Monitor to the Future by adding Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1) to my code, right after the function. However, I still can't figure out how to send messages to that event monitor from within the Future function.
Edit 2: I now have some sample code. My first attempt was similar to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end
);
end;
The basic function worked, but did not allow me to send any messages to the TOmniEventMonitor. I was able to get that part working by changing the code to this:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function: boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Now, I can successfully monitor the OnTaskTerminated event, but I still can't send messages to the Event Monitor from the task. By changing my code once again, I can access the task itself and send messages using task.Comm.Send(), but the messages don't reach the EventMonitor:
function myPing(HostName: string): IOmniFuture<boolean>;
begin
Result := Parallel.Future<boolean>(function(const task: IOmniTask): boolean
begin
Result := False;
//Do actual ping here... Set Result := True if successful.
task.Comm.Send(0,'Test 1');
end,
Parallel.TaskConfig.MonitorWith(Form1.OmniEventMonitor1)
);
end;
Here's a simple example on how to retrieve the function result from the async call. It does not use an "OmniEventMonitor" but instead calls a function once the async call returns ("Ping" is defined in PingU.pas, but not of importance here):
unit MainFormU;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Menus;
type
TPingResultEvent = procedure (const bResult: Boolean) of object;
TOnTerminateTestForm = class(TForm)
LogMemo: TMemo;
MainMenu: TMainMenu;
PingMenu: TMenuItem;
procedure PingMenuClick(Sender: TObject);
private
procedure BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
procedure PingResult (const bResult: Boolean);
public
{ Public declarations }
end;
var
OnTerminateTestForm: TOnTerminateTestForm;
implementation
{$R *.dfm}
uses PingU, OtlParallel, OtlTaskControl;
procedure TOnTerminateTestForm.PingMenuClick (Sender: TObject);
var
sServer : String;
begin
if (InputQuery ('Ping computer', 'Computer name:', sServer)) then
if (sServer <> '') then
begin
PingMenu.Enabled := false;
LogMemo.Lines.Add (Format ('Pinging %s',[sServer]));
BackgroundPing (sServer, PingResult);
end; { if }
end; { TOnTerminateTestForm.PingMenuClick }
procedure TOnTerminateTestForm.BackgroundPing (const sServer: String;
const OnResult: TPingResultEvent);
var
bResult : Boolean;
begin
Parallel.Async (
procedure
begin
bResult := Ping (sServer);
end,
Parallel.TaskConfig.OnTerminated(
procedure (const task: IOmniTaskControl)
begin
// executed in main thread after the async has finished
if Assigned (OnResult) then
OnResult (bResult);
end
)
);
end; { TOnTerminateTestForm.BackgroundPing }
procedure TOnTerminateTestForm.PingResult (const bResult: Boolean);
begin
PingMenu.Enabled := true;
LogMemo.Lines.Add ('Ping result = ' + BoolToStr (bResult, true));
end; { TOnTerminateTestForm.PingResult }
end.
Code source: Get a function result asynchronously in Delphi using Omni Thread Library

main application locks up using idPop3 to retrieve mail messages (even in a thread)

I am using a thread to access a pop3 account and retrieve messages. It works fine, but it locks up my application until it is complete. Cant move the window, shut down, click buttons, nothing.
It runs fine and allows me to access the main application up until the spot i commented out (or after the IdPOP31.Connect();)
//Getting the number of the messages that server has
then it locks up
procedure TPopThread.Pop;
var
vName, vEmail, vServerIn, vServerOut, vUserId, vPassword: String;
vPop3Port, vSMTPPort, vSSL: String; vHTML: TStringList;
MsgCount : Integer;
i,j : Integer;
FMailMessage : TIdMessage;
begin
with frmMain do
begin
RzMemo1.Lines.Clear;
vHTML:= TStringList.Create;
GetAccount(lbxMain.SelectedItem,vName, vEmail, vServerIn, vServerOut, vUserId, vPassword,
vPop3Port, vSMTPPort, vSSL, vHTML);
IdPOP31.Host := vServerIn;
IdPOP31.Username := vUserId;
IdPOP31.Password := vPassword;
IdPOP31.Port := StrToInt(vPop3Port);
try
Prepare(IdPOP31);
IdPOP31.Connect();
// {
// //Getting the number of the messages that server has.
// MsgCount := IdPOP31.CheckMessages;
// for i:= 0 to Pred(MsgCount) do
// begin
// try
// FMailMessage := TIdMessage.Create(nil);
// IdPOP31.Retrieve(i,FMailMessage);
// RzMemo1.Lines.Add('=================================================');
// RzMemo1.Lines.Add(FMailMessage.From.Address);
// RzMemo1.Lines.Add(FMailMessage.Recipients.EMailAddresses);
// RzMemo1.Lines.Add(FMailMessage.Subject);
// RzMemo1.Lines.Add(FMailMessage.Sender.Address);
// RzMemo1.Lines.Add(FMailMessage.Body.Text);
//
// for J := 0 to Pred( FMailMessage.MessageParts.Count ) do
// begin
// // if the part is an attachment
// if ( FMailMessage.MessageParts.Items[ J ] is TIdAttachment) then
// begin
// RzMemo1.Lines.Add('Attachment: ' + TIdAttachment(FMailMessage.MessageParts.Items[J]).Filename);
// end;
// end;
// RzMemo1.Lines.Add('=================================================');
// finally
// FMailMessage.Free;
// end;
// RzMemo1.Clear;
// end;
// }
finally
IdPOP31.Disconnect;
vHTML.Free;
end;
end;
end;
It actually did this before I added the thread, so it has something to do with that portion that is commented out and not the thread
What did i do wrong or didn't do?
here is my Execute
procedure TPopThread.Execute;
begin
try
Synchronize(Pop);
except
on Ex: Exception do
fExceptionMessage := Ex.Message;
end;
end;
here is how i call it
PopThread := TPopThread.Create(lbxMain.SelectedItem, frmMain.DonePopping);
You are locking up the application yourself, because you're synchronizing the call to the pop method.
Synchronize causes the call specified by AMethod to be executed using the main thread, thereby avoiding multithread conflicts.
The current thread is passed in the AThread parameter.
If you are unsure whether a method call is thread-safe, call it from within the Synchronize method to ensure it executes in the main thread.
Execution of the current thread is suspended while the method executes in the main thread.
So, for practical purposes, you're like you have no extra thread, since all your code is executed in the main thread.
An example of when you would want to use Synchronize is when you want to interact with a VCL component
On the other hand, because you're directly accessing a number of visual controls from your method, and the VCL is not thread safe, you have to execute your method in the main thread.
The best you can do is to make your thread independent from the VCL by not accessing any VCL component from the thread, but rather collecting all the input and output values in memory and setting/reading it from the main thread before the thread starts and after the thread finishes.
Or, if for any reason you don't want to do that, you can dissect your method to separate the parts that need access to the VCL and synchronize only that parts, for example:
type
TPopThread = class
private
FMailMessage : TIdMessage; //now the message belongs to the class itself
...
public
//all the values are passed via constructor or the thread is
//created in suspended state, configured and then started
property Host: string read FHost write FHost;
property UserName: string read FUserName write FUserName;
property Password: string read ...;
property Port: Integer read ...;
end;
procedure TPopThread.CopyMailToGUI;
var
J: Integer;
begin
frmMain.RzMemo1.Lines.Add('=================================================');
frmMain.RzMemo1.Lines.Add(FMailMessage.From.Address);
frmMain.RzMemo1.Lines.Add(FMailMessage.Recipients.EMailAddresses);
frmMain.RzMemo1.Lines.Add(FMailMessage.Subject);
frmMain.RzMemo1.Lines.Add(FMailMessage.Sender.Address);
frmMain.RzMemo1.Lines.Add(FMailMessage.Body.Text);
for J := 0 to Pred( FMailMessage.MessageParts.Count ) do
begin
// if the part is an attachment
if ( FMailMessage.MessageParts.Items[ J ] is TIdAttachment) then
begin
frmMain.RzMemo1.Lines.Add('Attachment: ' + TIdAttachment(FMailMessage.MessageParts.Items[J]).Filename);
end;
end;
frmMain.RzMemo1.Lines.Add('=================================================');
end;
procedure TPopThread.Pop;
var
MsgCount : Integer;
i,j : Integer;
Pop: TIdPOP3;
begin
Pop := TIdPOP3.Create(nil);
try
Pop.Host := FHost;
Pop.Username := FUserName;
Pop.Password := FPassword;
Pop.Port := FPort;
Prepare(Pop);
Pop.Connect();
//Getting the number of the messages that server has.
MsgCount := Pop.CheckMessages;
for I := 0 to Pred(MsgCount) do
begin
try
FMailMessage := TIdMessage.Create(nil);
try
IdPOP31.Retrieve(i,FMailMessage);
Synchronize(CopyMailToGUI);
finally
FMailMessage.Free;
end;
end;
finally
Pop.Free;
end;
end;
procedure TPopThread.Execute;
begin
//no need of a try/except, if an exception occurs, it
//is stored in the FatalException property
Pop;
end;
Now, your thread will ask the main thread to copy just the processed message to the VCL. During that copy your thread will block and your application will not respond to messages because the main thread is busy, but that will be for very shorts intervals, so even if it is not the ideal case, I think it will work for what you want.
You put all your logic inside a Synchronize call. Synchronize runs its function in the main VCL thread, so you've essentially nullified any benefits you might have gained from using a separate thread in the first place.
Remove the call to Synchronize so that Pop runs in the thread you created for it.
If you still need some operations to execute in the main thread, then put them in subroutines so that you can run only them in Synchronize. The parts I see in that code are the places where you add lines to a memo control.

How to send a string (serial port) , wait (timeout) and catch the reply string?

After reading this very interesting topic on stackoverflow --> How to wait for COM port receive event before sending more data in a loop
I've ran into many problems and i've tried many solutions ... nothing work well unfortunately !
Many serial port libraries are event-driven and i'm having a hard time understanding them.
I've tried with Asyncpro, Synaser and TComport.
Is it possible to have a function like this:
SerialSendandReply(tx string here,timeout) return rx string and if timeout send a error string
Response from the device are withing milliseconds a blocking way to do it would be better?
Like this:
Dosomething here
showmessage(SerialSendandReply('test',100 )); //100 ms timeout
dosomething else
With this code
TForm1 = class(TForm)
...
private
IOEvent : THandle; // used for IO events
IORx : string;
Comport : TapdComport;
...
procedure TForm1.ComportTriggerAvail(CP: TObject; Count: Word);
var i : integer;
begin
for i:=1 to Count do
IORx:=IORx+Comport.GetChar;
SetEvent(IOEvent);
end;
function TForm1.SerialSAWR(tx : string; TimeOut : integer) : boolean;
begin
Result := False;
try
IORx := ''; // your global var
ResetEvent(IOEvent);
Comport.PutString(tx);
Result := WaitForSingleObject(IOEvent, TimeOut) = WAIT_OBJECT_0;
except
on E : Exception do
// dosomething with exception
end;
end;
// constructor part
IOEvent := CreateEvent(nil, True, False, nil);
// destructor part
if IOEvent <> 0 then
CloseHandle(IOEvent);
Then i've tried to call this function :
if SerialSAWR('test'; 5000) then showmessage(IORx);
Sending is working great but doesn't return anything in the string.
Any advices?
Thank you very much!
Regards,
Laurent
I use TComPort and have created the following routine to do what you ask. TComPort monitors received characters in its monitoring thread and my routine waits for characters without calling Application.ProcessMessages. It may not be the most elegant code but it works fine.
function TArtTComPort.SerialPort_AwaitChars(AMinLength: integer;
ATerminator: char; AQtyAfterTerm: integer; ARaise: boolean): string;
var
fDueBy : TDateTime;
function IsEndOfReplyOrTimeout( var AStr : string ) : boolean;
var
I : integer;
begin
Result := False;
If ATerminator <> #0 then
begin
I := Length( AStr ) - AQtyAfterTerm;
If I > 0 then
Result := AStr[I] = ATerminator;
end;
If not Result then
Result := Length(AStr) >= AMinLength;
// Un-comment this next line to disable the timeout.
//Exit;
If not Result then
begin
Result := Now > fDueBy;
If Result then
If ARaise then
raise EArtTComPort.Create( 'Serial port reply timeout' )
else
AStr := '';
end;
end;
var
Events : TComEvents;
iCount : integer;
S : string;
begin
Assert( AMinLength > 0, 'Invalid minimum length' );
If not FComPort.Connected then
begin
Result := '';
Exit;
end;
fDueBy := Now + (FTimeoutMS * TDMSec );
Result := '';
Repeat
// Setup events to wait for:
Events := [evRxChar, evTxEmpty, evRxFlag, evRing, evBreak,
evCTS, evDSR, evError, evRLSD, evRx80Full];
// Wait until at least one event happens.
FComPort.WaitForEvent(
Events,
FStopEvent.Handle,
FTimeOutMS);
If Events = [] then // timeout
begin
If ARaise then
raise EArtTComPort.Create( 'Serial port reply timeout' )
end
else
begin
If evRxChar in Events then
begin
iCount := FComport.InputCount;
FComPort.ReadStr( S, iCount );
Result := Result + S;
end;
end;
until IsEndOfReplyOrTimeout( Result );
end;
I switched for nrComm Lib (v9.31)... very simple of use and well supported.
The only drawback is that isn't free and open source ... but it's worth it !
It's also thread-safe which is good too :).
Thank you very much everyone for the replies!
You are trying to do async I/O from the main thread. This will never play well with the GUI.
Doing complex async I/O is better suited in a separate thread. I have a blocking serial communication package (I think Synaser also has a blocking mode) and a function like this:
function TransmitReceive(const msg: AnsiString; var reply: AnsiString;
timeOut: Integer): Boolean;
Put the complex code logic inside a thread.execute and trig the start of the logic with an event signal.
Communicate results etc to the main thread through PostMessage calls for example.

Thread OnTerminate

I am working on kind of a download manager that's Multithreaded.
Each Thread has it's own ID/Handles/URL/etc.
I would to implement something like Pause/Resume/Cancel a download.
I have the ThreadHandles stored in a listview so I could Suspend/Resume/Terminate the download Threads. I haven't tried Suspend/Resume yet because I am currently working on cancel a download. The only problem is if I suspend a thread, the FileHandles/DLHandles get not closed.
Since the handles are stacked in the thread. Is there a possibility to get the pointer that I passed for CreateThread?
How I create Threads
type
PTR_Download = ^TTDownload;
TTDownload = record
URL: string;
ThreadHandle : Longword;
// .....
end;
function DownloadFile ( p : pointer ) : Integer; stdcall; // The Thread
var
_infos: TTDownload;
begin
CopyMemory(#_infos, p, SizeOf(_infos));
DownloadFile (_infos.URL); // just example
// .... and so on
// .... Handles get created here FileHandle/InternetOpenHandle/etc..
end;
function StartNewDownload (Link : String)
var
DL : PTR_Download;
ThreadID : DWORD;
begin
DL := PTR_Download(LocalAlloc(LPTR, SizeOf(TTDownload)));
DL^.URL := Link;
DL.ThreadHandle := CreateThread(nil, 0, #DownloadFile, DL, 0, ThreadID);
end;
function AnotherFunction (dummy : String) : Bool;
begin
GetParameterPointerOfThreadHandle (AnyHandleHere) // Something like that?!
end;
I basically need just something that closes the open handles from the terminated threads.
Any Ideas?

Create event and share variables

I am using Delphi 2007 and threads.
My problem (sorry, i'll try to explain better):
1) I created a file "utilities.pas" where i have the function i use more.
2) I created a new program, in this program i have one thread
3) in the execute method of the thread i call one function in my file "utilities.pas".
this function connects to an ftp using clever components (tclftp). This components logs the server responce in a dedicated event. What i would like to do is to save the log in a stringlist and then send the stringlist back to the calling thread.
This is part of the file "utilities.pas":
// I created TEventHandlers because it's the only way to assign the event runtime
// without having a class
type
TEventHandlers = class
procedure clFtp1SendCommand(Sender: TObject; const AText: string);
end;
var EvHandler: TEventHandlers;
// this is the porcedure called from the thread. i want to send the stringlist
// back to it containing the ftp log
procedure Test(VAR slMain: tStringlist);
var cFTP: TclFtp;
begin
cFTP := TclFtp.Create(nil);
cFTP.Server := 'XXX';
cFTP.UserName := 'XXX';
cFTP.Password := 'XXX';
cFTP.OnSendCommand := EvHandler.clFtp1SendCommand;
// i connect to the ftp
cFTP.Open;
FreeAndNil(cFTP);
end;
procedure TEventHandlers.clFtp1SendCommand(Sender: TObject; const AText: string);
begin
// here the component (cftp) sends me back the answer from the server.
// i am logging it
// HERE IT'S THE PROBLEM:
// I can't reach slMain from here.....
slmain.add(Atext);
end;
this is the calling thread:
procedure TCalcThread.Execute;
var slMain: tstringlist;
begin
inherited;
slmain := tstringlist.create(nil);
Test(slmain);
if slMain.count > 0 then
slMain.savetofile('c:\a.txt');
// i won't free the list box now, but in the thread terminated.
end;
this is the main program:
procedure TfMain.ThreadTerminated(Sender: TObject);
Var ExThread: TCalcThread;
begin
ExThread := (Sender as TCalcThread);
if ExThread.slMain.Count > 0 then
ExThread.slMain.SaveToFile('LOG\Errori.log');
freeandnil(slMain);
end;
Please can anybody help me in solving this? I really don't know what to do.
I hope now it more clear.
p.s. thanks for all the answer..
Another approach would be to have your thread object have its own instance of the stringlist and its own cFTP. If you need to have one "master thread" that everything writes to (perhaps for a summary of what each thread accomplished), use this class:
TThreadStringList by Tilo Eckert
http://www.swissdelphicenter.ch/torry/showcode.php?id=2167
I think one (BAD) approach would be to create a pool of components in the main thread or at design time, and assign one to each thread. i.e. 5 instances of cFTP, 5 stringlists, 5 threads.
Update: Martin James points out why this is a terrible idea, and I agree. So don't do this. Post stays as a deterrent.
Intercept the event within the thread class, and fire an own typed event from within that handler. Synchronize this call! And try to prevent the global variable. All this as follows:
type
TFtpSendCommandEvent = procedure(Mail: TStrings; const AText: String) of object;
TMyThread = class(TThread)
private
FclFtp: TclFtp;
FslMail: TStrings;
FOnFtpSendCommand: TFtpSendCommandEvent;
FText: String;
procedure clFtpSendCommand(Sender: TObject; const AText: String);
procedure DoFtpSendCommand;
protected
procedure Execute; override;
public
// You could add this property as parameter to the constructor to prevent the
// need to assign it separately
property OnFtpSendCommand: TFtpSendCommandEvent read FOnFtpSendCommand
write FOnFtpSendCommand;
end;
// If you dont want to make this a property or private field of the thread class:
var
EvHandler: TFtpSendCommandEvent;
{ TMyThread }
procedure TMyThread.clFtpSendCommand(Sender: TObject; const AText: string);
begin
// Store the AText parameter temporarily in a private field: Synchronize only
// takes a parameterless method
FText := AText;
Synchronize(DoFtpSendCommand);
end;
procedure TMyThread.DoFtpSendCommand;
begin
if Assigned(FOnFtpSendCommand) then
FOnFtpSendCommand(FslMail, FText);
// Or, if you really like to use that global variable:
if Assigned(EvHandler) then
EvHandler(FslMail, FText);
end;
procedure TMyThread.Execute;
begin
...
FclFtp := TclFtp.Create(nil);
FslMail := TStringList.Create(nil);
try
FclFtp.Server := 'XXX';
FclFtp.UserName := 'XXX';
FclFtp.Password := 'XXX';
FclFtp.OnSendCommand := clFtpSendCommand;
FclFtp.Open;
finally
FreeAndNil(FclFtp);
FreeAndNil(FslMail);
end;
...
end;

Resources