在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处理消息之后,它可以破坏全局空间中的字符串空间。这是一个可行的解决方案吗?如何实现这个?
由于
答案 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()返回时,释放字符串是安全的。