将字符串数据从Thread发送到主窗体

ala*_*ncc 8 delphi multithreading message postmessage

在Dephi中,我创建了一个这样的线程,它会不时地向主表单发送消息

Procedure TMyThread.SendLog(I: Integer);
Var
  Log: array[0..255] of Char;
Begin
  strcopy(@Log,PChar('Log: current stag is ' + IntToStr(I)));
   PostMessage(Form1.Handle,WM_UPDATEDATA,Integer(PChar(@Log)),0);
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;
Run Code Online (Sandbox Code Playgroud)

其中WM_UPDATEDATA是自定义消息,定义如下:

const
  WM_UPDATEDATA = WM_USER + 100;
Run Code Online (Sandbox Code Playgroud)

在主要表单中,它将执行以下更新列表:

procedure TForm1.WMUpdateData(var msg : TMessage);
begin
  List1.Items.Add(PChar(msg.WParam));
end;
Run Code Online (Sandbox Code Playgroud)

但是,由于发送到主窗体的日志字符串是一个局部变量,在调用SendLog后将被销毁.虽然TForm1.WMUpdateData异步处理消息,但是在调用它时,Log字符串可能已被销毁.如何解决这个问题呢?

我想也许我可以在全局系统空间中分配字符串空间,然后将其传递给消息,然后在TForm1.WMUpdateData处理消息之后,它可以破坏全局空间中的字符串空间.这是一个可行的解决方案吗?怎么实现这个?

谢谢

LU *_* RD 10

如果您有D2009或更高版本,还有另一种方法可以将消息发布到主表单.TThread.Queue是来自线程的异步调用,其中可以在主线程中执行方法或过程.

这里的优点是设置消息传递的框架不那么复杂.只需在创建线程时传递回调方法即可.没有句柄,也没有明确处理字符串分配/释放.

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;
Run Code Online (Sandbox Code Playgroud)
procedure TMyForm.TheCallback(const msg : String);
begin
  // Show msg
end;

procedure TMyForm.StartBackgroundTask(Sender : TObject);
begin
  ... 
  FMyThread := TMyThread.Create(TheCallback);
  ...
end;
Run Code Online (Sandbox Code Playgroud)


Rem*_*eau 8

除了发布局部变量之外,该TWinControl.Handle属性也不是线程安全的.您应该使用该TApplication.Handle属性,或者用于AllocateHWnd()创建自己的窗口.

您需要在堆上动态分配字符串,将该指针发布到主线程,然后在完成使用后释放内存.

例如:

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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

或者:

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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

  • 根据postmessage分配数据和解除分配不是我建议的.您的消息可能永远不会被处理,然后您创建一个难以检测的内存泄漏.在WINAPI中没有出现这种模式可能会给出一个指示,这不是一个好主意.例如,消息队列大小是有限的.或者目标代码可能会更改并引入内存泄漏.或者目标窗口可能通过默认窗口proc处理WM_UPDATEDATA,因此永远不会释放内存.等等... (3认同)
  • 如果队列填满,则没有泄漏.PostMessage()将失败,然后原始线程释放它分配的内存,因为它无法发布. (3认同)

Jen*_*nsG 0

使用SendMessage()。

PostMessage() 将异步处理您的消息,它基本上放入目标消息队列并立即返回。当处理程序代码访问 wparam/lparam 中发送的数据时,您的调用者已经释放了该字符串。

相反,SendMessage() 绕过消息队列并直接(同步)调用窗口过程。当 SendMessage() 返回时,可以安全地释放字符串。