In Dephi, I create a thread, like this, which will send message to main form from time to time
Procedure TMyThread.SendLog(I: Integer);
Var
Log: array[0..2
Use SendMessage().
PostMessage() will process your message asynchronously, it basically puts into the target message queue and Returns immediately. At the time where the handler code accesses the data sent in wparam/lparam, your caller has already freed the string.
In contrast, SendMessage() bypasses the message queue and calls the window proc directly (synchronously). At the time when SendMessage() returns, it is safe to free the string.
If you have D2009 or later version, there is another way to post messages to your main form. TThread.Queue is an asynchronous call from a thread, where a method or procedure can be executed in the main thread.
The advantage here is that the frame to set up the message passing is less complex. Just pass your callback method when creating your thread. No handles and no explicit handling of string allocation/deallocation.
Type
TMyCallback = procedure(const s : String) of object;
TMyThread = class(TThread)
private
FCallback : TMyCallback;
procedure Execute; override;
procedure SendLog(I: Integer);
public
constructor Create(aCallback : TMyCallback);
end;
constructor TMyThread.Create(aCallback: TMyCallback);
begin
inherited Create(false);
FCallback := aCallback;
end;
procedure TMyThread.SendLog(I: Integer);
begin
if not Assigned(FCallback) then
Exit;
Self.Queue( // Executed later in the main thread
procedure
begin
FCallback( 'Log: current stag is ' + IntToStr(I));
end
);
end;
procedure TMyThread.Execute;
var
I: Integer;
begin
for I := 0 to 1024 * 65536 do
begin
if ((I mod 65536) = 0) then
begin
SendLog(I);
End;
End;
end;
procedure TMyForm.TheCallback(const msg : String);
begin
// Show msg
end;
procedure TMyForm.StartBackgroundTask(Sender : TObject);
begin
...
FMyThread := TMyThread.Create(TheCallback);
...
end;
In addition to the fact that you are posting a local variable, the TWinControl.Handle
property is not thread-safe, either. You should use the TApplication.Handle
property instead, or use AllocateHWnd()
to create your own window.
You do need to dynamically allocate the string on the heap, post that pointer to the main thread, and then free the memory when you are done using it.
For example:
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnMessage := AppMessage;
// or use a TApplicationEvents component...
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Application.OnMessage := nil;
end;
procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
var
S: PString;
begin
if Msg.Message = WM_UPDATEDATA then
begin
S := PString(msg.LParam);
try
List1.Items.Add(S^);
finally
Dispose(S);
end;
Handled := True;
end;
end;
procedure TMyThread.SendLog(I: Integer);
var
Log: PString;
begin
New(Log);
Log^ := 'Log: current stag is ' + IntToStr(I);
if not PostMessage(Application.Handle, WM_UPDATEDATA, 0, LPARAM(Log)) then
Dispose(Log);
end;
Alternatively:
var
hLogWnd: HWND = 0;
procedure TForm1.FormCreate(Sender: TObject);
begin
hLogWnd := AllocateHWnd(LogWndProc);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if hLogWnd <> 0 then
DeallocateHWnd(hLogWnd);
end;
procedure TForm1.LogWndProc(var Message: TMessage);
var
S: PString;
begin
if Message.Msg = WM_UPDATEDATA then
begin
S := PString(msg.LParam);
try
List1.Items.Add(S^);
finally
Dispose(S);
end;
end else
Message.Result := DefWindowProc(hLogWnd, Message.Msg, Message.WParam, Message.LParam);
end;
procedure TMyThread.SendLog(I: Integer);
var
Log: PString;
begin
New(Log);
Log^ := 'Log: current stag is ' + IntToStr(I);
if not PostMessage(hLogWnd, WM_UPDATEDATA, 0, LPARAM(Log)) then
Dispose(Log);
end;