10

在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;

但是,由于发送到主窗体的 Log 字符串是一个局部变量,调用 SendLog 后会被销毁。而 TForm1.WMUpdateData 是异步处理消息的,所以有可能在调用它的时候,Log 字符串已经被销毁了。如何解决这个问题呢?

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

谢谢

4

3 回答 3

12

如果您有 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;
于 2013-09-29T12:28:50.493 回答
11

除了您发布局部变量这一事实之外,该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;
于 2013-09-28T04:55:14.493 回答
0

使用发送消息()。

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

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

于 2013-09-27T22:48:20.137 回答