PeterDelphin
asked on
Posting a string with PostMessage
Hello! I use the following method to send a string from a thread to the UI:
See also the attached project.
It works well. BUT: Could this be potentially dangerous?
StringPostMessage.zip
unit Unit2;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
const
WM_SETCAPTION = WM_USER;
type
TForm2 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure WMSetCaption(var msg: TMessage); message WM_SETCAPTION;
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
procedure TForm2.WMSetCaption(var msg: TMessage);
begin
Self.Caption := PChar(msg.LParam);
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
System.Classes.TThread.CreateAnonymousThread(
procedure
begin
PostMessage(Handle, WM_SETCAPTION, 0, LParam(PChar('My new caption')));
end).Start;
end;
end.
See also the attached project.
It works well. BUT: Could this be potentially dangerous?
StringPostMessage.zip
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
if this is within the same delphi app,
you could create an object to hold the string
the receiver should free the object
you could create an object to hold the string
the receiver should free the object
const
WM_SETCAPTION = WM_USER + 3;
type
TMsgObject = class(TObject)
fMsg: string;
public
constructor Create(aMsg: string);
property Msg: string read fMsg;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
fMsgId: Integer;
procedure WMSetCaption(var Msg: TMessage); message WM_SETCAPTION;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TMsgObject.Create(aMsg: string);
begin
inherited Create;
fMsg := aMsg;
end;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
begin
Inc(fMsgId);
System.Classes.TThread.CreateAnonymousThread(
procedure
begin
PostMessage(Handle, WM_SETCAPTION, 0, Integer(TMsgObject.Create('New caption ' + IntToStr(fMsgId) )) );
end).Start;
end;
constructor TForm1.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
fMsgId := 0;
end;
destructor TForm1.Destroy;
begin
inherited Destroy;
end;
procedure TForm1.WMSetCaption(var Msg: TMessage);
var mo: TMsgObject;
begin
if Msg.Msg = WM_SETCAPTION then
begin
mo := TMsgObject(Msg.LParam);
Caption := mo.Msg;
mo.Free;
end;
end;
end.
ASKER
Hello Geert Gruwez, this solution is brilliant, thank you! I like this solution the best.
Here I made a more generalized usage example from your solution:
I have these two questions:
1. Is there a particular reason have you defined the Msg constant as WM_USER + 3? Wouldn't it be enough to define it as WM_USER?
2. Is PostMessage strictly FIFO? I.e., can I rely on PostMessage messages being processed in the SAME order they are sent?
SendingStringWithPostMessageUsingObj.zip
Here I made a more generalized usage example from your solution:
unit SendingStringWithPostMessageUsingObjectMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
const
WM_STRINGMESSAGE = WM_USER + 3;
type
TStrMsgObject = class(TObject)
fMsg: string;
public
constructor Create(aMsg: string);
property Msg: string read fMsg;
end;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
fMsgId: Integer;
procedure WMStringMessage(var Msg: TMessage); message WM_STRINGMESSAGE;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
const
ACTION_SETCAPTION = 0;
ACTION_CHANGECAPTION = 1;
constructor TStrMsgObject.Create(aMsg: string);
begin
inherited Create;
fMsg := aMsg;
end;
procedure TForm2.WMStringMessage(var Msg: TMessage);
var
mo: TStrMsgObject;
begin
mo := TStrMsgObject(Msg.LParam);
try
case Msg.WParam of
ACTION_SETCAPTION:
begin
Caption := mo.Msg;
end;
ACTION_CHANGECAPTION:
begin
Caption := Caption + mo.Msg;
end;
end
finally
mo.Free;
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
begin
System.Classes.TThread.CreateAnonymousThread(
procedure
begin
PostMessage(Handle, WM_STRINGMESSAGE, ACTION_SETCAPTION, Integer(TStrMsgObject.Create('My new caption')));
end).Start;
end;
procedure TForm2.Button2Click(Sender: TObject);
begin
System.Classes.TThread.CreateAnonymousThread(
procedure
begin
PostMessage(Handle, WM_STRINGMESSAGE, ACTION_CHANGECAPTION, Integer(TStrMsgObject.Create(' changed')));
end).Start;
end;
end.
I have these two questions:
1. Is there a particular reason have you defined the Msg constant as WM_USER + 3? Wouldn't it be enough to define it as WM_USER?
2. Is PostMessage strictly FIFO? I.e., can I rely on PostMessage messages being processed in the SAME order they are sent?
SendingStringWithPostMessageUsingObj.zip
WM_User +3 is just a brainf*** :)
using wm_user would do just as well
PostMessage is Fifo ... unless you met the same idiot i have
and there is some routine using peekMessage and PopMessage to alter the queue
if you haven't programmed this, then it's fifo :)
i met an idiot once, who was only interested in specific messages
instead of only processing those specific messages he processed the whole queue like this pseudo code
I needn't say he didn't really understand windows messages ?
and his apps had very unpredictable behaviour ... works as designed was a good description
using wm_user would do just as well
PostMessage is Fifo ... unless you met the same idiot i have
and there is some routine using peekMessage and PopMessage to alter the queue
if you haven't programmed this, then it's fifo :)
i met an idiot once, who was only interested in specific messages
instead of only processing those specific messages he processed the whole queue like this pseudo code
n := CountMessagesOnQueue;
for I := 1 to n do
begin
M := PopMessage;
if M.Message <> MyMessage then
PostMessage(M)
else
ProcessMessage(M);
end;
I needn't say he didn't really understand windows messages ?
and his apps had very unpredictable behaviour ... works as designed was a good description
I discourage to use PostMessage (with reason I post). Please read the differences.... Better use SendMessage - cause handler will wait to store right message to Caption.
If you don't believe me - try to PostMessage:
process.
Your example works - but is not general solution for threads....
If you don't believe me - try to PostMessage:
PostMessage(Handle, WM_STRINGMESSAGE, ACTION_SETCAPTION, Integer(TStrMsgObject.Create('Caption '+IntToStr(id)));
... where id is your thread id (maybe index if you want) ... and start 100 threads with something like Sleep(Random(10)*500) to simulate someprocess.
Your example works - but is not general solution for threads....
Sinisa ...
can you post a project proving it doesn't work ?
can you post a project proving it doesn't work ?
anyways ... for threads, i use PostThreadMessage
Geert your example works - cause you create object and send pointer of it - no hassle with strings. But if you look at my example ...
with PostMessage works bad, and with SendMessage better. Note - this is example to show bad behavior ....
with PostMessage works bad, and with SendMessage better. Note - this is example to show bad behavior ....
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
const
WM_THREAD_MSG = WM_USER+1;
type
TWorkerThread = class(TThread)
private
FId: Integer;
FMsgRoot: String;
FParentHnd: THandle;
protected
procedure Execute; override;
public
constructor Create(ParentHnd: THandle; MsgRoot: String; Id: Integer); overload;
end;
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
procedure WMThreadMessage(var Msg: TMessage); message WM_THREAD_MSG;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TWorkerThread }
constructor TWorkerThread.Create(ParentHnd: THandle; MsgRoot: String; Id: Integer);
begin
FMsgRoot := MsgRoot;
FId := Id;
FParentHnd := ParentHnd;
inherited Create(False);
end;
procedure TWorkerThread.Execute;
var
w: Integer;
s: String;
begin
FreeOnTerminate := True;
w := Random(10)*500;
s := FMsgRoot + ' ' + IntToStr(FId);
Sleep(w);
//PostMessage(FParentHnd, WM_THREAD_MSG, 0, Integer(PChar(s)));
SendMessage(FParentHnd, WM_THREAD_MSG, 0, Integer(PChar(s)));
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Randomize;
end;
procedure TForm1.WMThreadMessage(var Msg: TMessage);
var
s: String;
begin
s := String(PChar(msg.LParam));
Memo1.Lines.Add(s);
Msg.Result := 0;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
for i := 1 to 100 do
begin
TWorkerThread.Create(Self.Handle, 'My thread', i);
end;
end;
end.
ASKER
Sinisa, thanks for your remarks. I am aware of the difference between PostMessage and SendMessage. For my specific scenario, I nevertheless prefer PostMessage for the targeted purpose because I can make sure the messages are sent from one single thread in the intended order.
ASKER
Geert, the MS documentation about PostThreadMessage says:
But when I PostMessage(Handle, ... (where Handle refers to the current form) the message is automatically directed to the UI thread, isn't it?
Posts a message to the message queue of the specified thread.
But when I PostMessage(Handle, ... (where Handle refers to the current form) the message is automatically directed to the UI thread, isn't it?
well ... conceptually it's the same ... :)
the message gets processed by WndProc
The difference is ...
PostMessage uses the handle of the window
PostThreadMessage uses the handle of the thread
The difference is working with visual controls or not
it's rather odd to send WM_SET_CURSOR to a thread
the message gets processed by WndProc
The difference is ...
PostMessage uses the handle of the window
PostThreadMessage uses the handle of the thread
The difference is working with visual controls or not
it's rather odd to send WM_SET_CURSOR to a thread
Sinisa ...
that's not really the issue.
You use PostMessage if you want to work ansynchronously
and Sendmessage if not
in my opinion ...
moving a progressbar to indicate progress is best done asynchronously
I know i contradict my article with a progressbar with a callback
SendMessage and callback tecnhiques are synchronous
working with a queue ... like what Peter seems to be doing ... should also be asynchronously
that's not really the issue.
You use PostMessage if you want to work ansynchronously
and Sendmessage if not
in my opinion ...
moving a progressbar to indicate progress is best done asynchronously
I know i contradict my article with a progressbar with a callback
SendMessage and callback tecnhiques are synchronous
working with a queue ... like what Peter seems to be doing ... should also be asynchronously
...anyway PostMessage is not good to transfer (as parameter) something like pointer to string itself, but good enough to post pointer to some object instance or integer value for progressbar for ex.
it might not be good ... but that's irrelevant if you have to work asynchronously
ASKER
Geert, the task I'm working on is a little bit more complicated.
Can I be sure that all single loop threads from a System.Threading.TParallel .For loop are terminated AFTER this loop, right? In other words: The TParallel.For loop returns only AFTER all its loop threads are terminated, right?
Can I be sure that all single loop threads from a System.Threading.TParallel
i like the words little complicated and thread in the next senctence
sure ?
when are we ever sure ?
the only certainty i have:
if the electric power fails before the processing is finished, then i'm sure all processes are finished
however, i believe you still required a second step ... with the power on ... :)
this is a different ballgame :)
you are going from 1 anonymous thread to waiting for multiple threads to finish
this is a WaitForAll to finish
and since you are using Delphi System ... that should be documented
and it is ... but they created a wrapper for managing threads: The TTask:
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Using_TTask_from_the_Parallel_Programming_Library
inside that you have the waitforall
sure ?
when are we ever sure ?
the only certainty i have:
if the electric power fails before the processing is finished, then i'm sure all processes are finished
however, i believe you still required a second step ... with the power on ... :)
this is a different ballgame :)
you are going from 1 anonymous thread to waiting for multiple threads to finish
this is a WaitForAll to finish
and since you are using Delphi System ... that should be documented
and it is ... but they created a wrapper for managing threads: The TTask:
http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Using_TTask_from_the_Parallel_Programming_Library
inside that you have the waitforall
ASKER
Geert, sorry for expanding the question topic. (Should I open a new question?)
As I understand it, TTask.WaitForAll concerns an array of ITask while TParallel.For manages its loop threads itself. Is this correct? So if this is correct then TParallel.For should return only after its loop threads are completely terminated. Is this correct?
As I understand it, TTask.WaitForAll concerns an array of ITask while TParallel.For manages its loop threads itself. Is this correct? So if this is correct then TParallel.For should return only after its loop threads are completely terminated. Is this correct?
i haven't used that PPL yet ... I wrote my own PPL.
I have considered moving to it, but haven't got there yet
Looks like TParallel is the low level implementation
and TTask is the interface which you should be using
to me it looks like you write descendants of TParallel to write your own flavor of such an engine
to give a conclusion I would have to read the docs too
i can't give advice on my own experience with this library as i haven't used it yet
it looks to be very similary to The Delphi Geek's Omni Thread Library
I'm assuming Embarcadero rewrote it somewhat and used the ideas or just hired the guy
I have considered moving to it, but haven't got there yet
Looks like TParallel is the low level implementation
and TTask is the interface which you should be using
to me it looks like you write descendants of TParallel to write your own flavor of such an engine
to give a conclusion I would have to read the docs too
i can't give advice on my own experience with this library as i haven't used it yet
it looks to be very similary to The Delphi Geek's Omni Thread Library
I'm assuming Embarcadero rewrote it somewhat and used the ideas or just hired the guy
looks like this is your answer:
All overloads of TParallel.For return TParallel.TLoopResult. When all the iterations of a TParallel.For iterator event have been executed, the Completed property of the TLoopResult instance returned by TParallel.For is set to True.
All overloads of TParallel.For return TParallel.TLoopResult. When all the iterations of a TParallel.For iterator event have been executed, the Completed property of the TLoopResult instance returned by TParallel.For is set to True.
ASKER
Geert, many thanks for the comprehensive and helpful answers.
This has convinced me, so I will buy a Premium Plan.
Unfortunately, I cannot find a DETAILED description of the features of the Premium Plan.
This has convinced me, so I will buy a Premium Plan.
Unfortunately, I cannot find a DETAILED description of the features of the Premium Plan.
Premium Plan ?
is that for health care ?
is that for health care ?
ASKER
Geert, I like your sarcastic humor ;-)
No, I mean the Experts Exchange Premium Plan.
No, I mean the Experts Exchange Premium Plan.
ASKER