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

时间:2013-09-27 21:03:01

标签: multithreading delphi 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;

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

const
  WM_UPDATEDATA = WM_USER + 100;

在主要形式中,它将按以下方式更新列表:

procedure TForm1.WMUpdateData(var msg : TMessage);
begin
  List1.Items.Add(PChar(msg.WParam));
end;

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

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

由于

3 个答案:

答案 0 :(得分: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;

procedure TMyForm.TheCallback(const msg : String);
begin
  // Show msg
end;

procedure TMyForm.StartBackgroundTask(Sender : TObject);
begin
  ... 
  FMyThread := TMyThread.Create(TheCallback);
  ...
end;

答案 1 :(得分: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;

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;

可替换地:

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;

答案 2 :(得分:0)

使用SendMessage()。

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

相反,SendMessage()绕过消息队列并直接(同步)调用窗口proc。在SendMessage()返回时,释放字符串是安全的。