3
\$\begingroup\$

I am sending a string from an anonymous thread to the UI with PostMessage in the following code:

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.

This works seemingly well, but could this potentially create memory leaks? Or is there a better way to accomplish this?

\$\endgroup\$
5
  • \$\begingroup\$ Is there any particular reason why you want to do this via an extra thread, instead of calling it directly? \$\endgroup\$
    – holroy
    Apr 30, 2017 at 15:50
  • \$\begingroup\$ This is only a simplified example from a more complex scenario where a string must be sent from an anonymous thread to the main thread. The central question here is whether doing this with PostMessage in the above way could cause any problems whatsoever (e.g. memory leaks). \$\endgroup\$ Apr 30, 2017 at 19:26
  • \$\begingroup\$ Why would you expect there to be a memory leak? The TThread that gets created is FreeOnTerminate, so it will Free itself. \$\endgroup\$
    – Dangph
    May 2, 2017 at 0:43
  • \$\begingroup\$ Regarding the string, that could be a problem if you are casting from a regular Delphi string. Delphi strings are reference counted, so by the time your message handler tries to access the string, it may have been deallocated. It might be a good idea to allocate the string yourself with StrNew. \$\endgroup\$
    – Dangph
    May 3, 2017 at 5:43
  • \$\begingroup\$ Thanks for the idea. In the meantime I've found a better solution: I create an object containing the string as a property and pass the pointer to the object as Integer LParam. Then, in the message handler, I use the string and free the object. In this way, I cannot only send strings but also other data as well. I will post this as a solution. \$\endgroup\$ May 4, 2017 at 8:31

1 Answer 1

4
\$\begingroup\$

Create an object containing the string as a property and pass the pointer to the object as Integer LParam. Then, in the message handler, use the string and free the object. In this way, you could not only send strings but also other data as well.

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;

type
  TStrMsgObject = class(TObject)
  private
    fMsg: string;
  public
    constructor Create(const aMsg: string);
    property Msg: string read fMsg;
  end;

type
  TForm2 = class(TForm)
    btnSetCaption: TButton;
    btnChangeCaption: TButton;
    procedure btnChangeCaptionClick(Sender: TObject);
    procedure btnSetCaptionClick(Sender: TObject);
  private
    { Private declarations }
    procedure WMStringMessage(var Msg: TMessage); message WM_STRINGMESSAGE;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

const
  // WM_STRINGMESSAGE Actions:
  ACTION_SETCAPTION    = 0;
  ACTION_CHANGECAPTION = 1;

constructor TStrMsgObject.Create(const aMsg: string);
begin
  inherited Create;
  fMsg := aMsg;
end;

procedure TForm2.WMStringMessage(var Msg: TMessage);
// Process WM_STRINGMESSAGE messages
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.btnSetCaptionClick(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.btnChangeCaptionClick(Sender: TObject);
begin
  System.Classes.TThread.CreateAnonymousThread(
    procedure
    begin
      PostMessage(Handle, WM_STRINGMESSAGE, ACTION_CHANGECAPTION, Integer(TStrMsgObject.Create(' changed')));
    end).Start;
end;

end.
\$\endgroup\$
1
  • 1
    \$\begingroup\$ What if the message is not processed, or PostMessage fails? You leak that object? P.S. do not post messages to the main form. Its handle can change when the window is recreated. Dedicate a hidden window for it (AllocateHWnd). And use casting to LPARAM, or WPARAM (not Integer). Finally, record would suffice. \$\endgroup\$
    – Victoria
    Jun 26, 2017 at 19:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.