我有一个Delphi 6应用程序,它有一个专用于与使用SendMessage()和WM_COPYDATA消息与外部程序连接的外部应用程序进行通信的线程.因此,我使用AllocateHWND()创建一个隐藏窗口来满足此需求,因为由于SendMessage()函数只接受窗口句柄而不是线程ID,因此线程消息队列将无法工作.我不确定的是在线程Execute()方法中放入什么.
我假设如果我使用GetMessage()循环或创建一个带有WaitFor *()函数的循环调用,那么线程将阻塞,因此线程的WndProc()永远不会处理来自外部程序的SendMessage()消息对?如果是这样,放入Execute()循环的正确代码是什么,它不会不必要地消耗cpu周期,但是一旦收到WM_QUIT消息就会退出?如果有必要,我总是可以使用Sleep()循环,但我想知道是否有更好的方法.
解决方法
AllocateHWnd()(更具体地说,MakeObjectInstance())不是线程安全的,所以你必须小心它.最好直接使用CreatWindow / Ex()(或者像
DSiAllocateHwnd()
一样使用AllocateHWnd()的线程安全版本.
在任何情况下,HWND都与创建它的线程上下文绑定,因此您必须在Execute()方法中创建和销毁HWND,而不是在线程的构造函数/析构函数中.此外,即使使用SendMessage()向您发送消息,它们也来自另一个进程,因此在其拥有的线程执行消息检索操作之前,HWND不会处理它们,因此该线程需要自己的消息循环.
您的Execute()方法应如下所示:
procedure TMyThread.Execute; var Message: TMsg; begin FWnd := ...; // create the HWND and tie it to WndProc()... try while not Terminated do begin if MsgWaitForMultipleObjects(0,nil^,False,1000,QS_ALLINPUT) = WAIT_OBJECT_0 then begin while PeekMessage(Message,PM_REMOVE) do begin TranslateMessage(Message); DispatchMessage(Message); end; end; end; finally // destroy FWnd... end; end; procedure TMyThread.WndProc(var Message: TMessage); begin if Message.Msg = WM_COPYDATA then begin ... Message.Result := ...; end else Message.Result := DefWindowProc(FWnd,Message.Msg,Message.WParam,Message.LParam); end;
或者:
// In Delphi XE2,a virtual TerminatedSet() method was added to TThread,// which is called when TThread.Terminate() is called. In earlier versions,// use a custom method instead... type TMyThread = class(TThread) private procedure Execute; override; {$IF RTLVersion >= 23} procedure TerminatedSet; override; {$IFEND} public {$IF RTLVersion < 23} procedure Terminate; reintroduce; {$IFEND} end; procedure TMyThread.Execute; var Message: TMsg; begin FWnd := ...; // create the HWND and tie it to WndProc()... try while not Terminated do begin if WaitMessage then begin while PeekMessage(Message,PM_REMOVE) do begin if Message.Msg = WM_QUIT then Break; TranslateMessage(Message); DispatchMessage(Message); end; end; end; finally // destroy FWnd... end; end; {$IF RTLVersion < 23} procedure TMyThread.Terminate; begin inherited Terminate; PostThreadMessage(ThreadID,WM_QUIT,0); end; {$ELSE} procedure TMyThread.TerminatedSet; begin PostThreadMessage(ThreadID,0); end; {$IFEND}