我有一个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}