Multithreading 具有隐藏窗口的线程的线程消息循环?
我有一个Delphi 6应用程序,它有一个专用于与使用SendMessage()和WM_COPYDATA消息与外部程序接口的外部应用程序通信的线程。因此,我使用AllocateHWND()创建了一个隐藏窗口来满足需要,因为SendMessage()函数只接受窗口句柄而不接受线程ID,所以线程消息队列无法工作。我不确定的是在threadexecute()方法中放什么 我假设如果使用GetMessage()循环或创建一个包含WaitFor*()函数调用的循环,线程将被阻塞,因此线程的WndProc()将永远不会处理来自外部程序的SendMessage()消息,对吗?如果是这样的话,在Execute()循环中放入的正确代码是什么?该循环不会不必要地消耗CPU周期,但会在收到WM_QUIT消息后退出?如果有必要,我总是可以使用Sleep()进行循环,但我想知道是否有更好的方法。Multithreading 具有隐藏窗口的线程的线程消息循环?,multithreading,delphi,blocking,sendmessage,wm-copydata,Multithreading,Delphi,Blocking,Sendmessage,Wm Copydata,我有一个Delphi 6应用程序,它有一个专用于与使用SendMessage()和WM_COPYDATA消息与外部程序接口的外部应用程序通信的线程。因此,我使用AllocateHWND()创建了一个隐藏窗口来满足需要,因为SendMessage()函数只接受窗口句柄而不接受线程ID,所以线程消息队列无法工作。我不确定的是在threadexecute()方法中放什么 我假设如果使用GetMessage()循环或创建一个包含WaitFor*()函数调用的循环,线程将被阻塞,因此线程的WndProc(
AllocateHWnd()
(更具体地说,MakeObjectInstance()
)不是线程安全的,所以您必须小心使用它。最好直接使用createWindow/Ex()
(或AllocateHWnd()
的线程安全版本,如
在任何情况下,HWND
都与创建它的线程上下文相关联,因此您必须在Execute()
方法中创建并销毁HWND
,而不是在线程的构造函数/析构函数中用于向您发送消息,它们来自另一个进程,因此在其所属线程执行消息检索操作之前,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, 0, 0, 0, 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, 0, 0, 0, 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, 0);
end;
{$ELSE}
procedure TMyThread.TerminatedSet;
begin
PostThreadMessage(ThreadID, WM_QUIT, 0, 0);
end;
{$IFEND}
//在Delphi XE2中,向TThread添加了一个虚拟TerminatedSet()方法,
//在调用TThread.Terminate()时调用。在早期版本中,
//请改用自定义方法。。。
类型
TMyThread=class(TThread)
私有的
程序执行;重写;
{$IF RTLVersion>=23}
过程终止集;覆盖;
{$IFEND}
公众的
{$IF RTLVersion<23}
程序终止;重新引入;
{$IFEND}
结束;
程序TMyThread.Execute;
变量
信息:TMsg;
开始
FWnd:=…;//创建HWND并将其绑定到WndProc()。。。
尝试
虽然没有终止
开始
如果等待消息,那么
开始
而PeekMessage(Message,0,0,PM_REMOVE)则执行以下操作
开始
如果Message.Msg=WM_退出,则中断;
翻译信息;
调度消息(Message);
结束;
结束;
结束;
最后
//摧毁FWnd。。。
结束;
结束;
{$IF RTLVersion<23}
程序TMyThread.终止;
开始
继承终止;
PostThreadMessage(线程ID,WM_QUIT,0,0);
结束;
{$ELSE}
程序TMyThread.TerminatedSet;
开始
PostThreadMessage(线程ID,WM_QUIT,0,0);
结束;
{$IFEND}
这里有一个循环,它不需要class.pas,仅依赖System.pas来实现一些辅助函数,Windows.pas用于Win32 API函数,Messages.pas用于WM_uu常量
请注意,此处的窗口句柄是从工作线程创建和销毁的,但主线程会等待工作线程完成初始化。您可以将此等待推迟到稍后的某个时刻,当您实际需要窗口句柄时,以便在工作线程设置其精灵起来
unit WorkerThread;
interface
implementation
uses
Messages,
Windows;
var
ExitEvent, ThreadReadyEvent: THandle;
ThreadId: TThreadID;
ThreadHandle: THandle;
WindowHandle: HWND;
function HandleCopyData(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
begin
Result := 0; // handle it
end;
function HandleWmUser(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT;
// you may handle other messages as well - just an example of the WM_USER handling
begin
Result := 0; // handle it
end;
function MyWindowProc(hWnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
begin
if Msg = WM_COPYDATA then
begin
Result := HandleCopyData(hWnd, Msg, wParam, lParam);
end else
if Msg = WM_USER then
begin
// you may handle other messages as well - just an example of the WM_USER handling
// if you have more than 2 differnt messag types, use the "case" switch
Result := HandleWmUser(hWnd, Msg, wParam, lParam);
end else
begin
Result := DefWindowProc(hWnd, Msg, wParam, lParam);
end;
end;
const
WindowClassName = 'MsgHelperWndClass';
WindowClass: TWndClass = (
style: 0;
lpfnWndProc: @MyWindowProc;
cbClsExtra: 0;
cbWndExtra: 0;
hInstance: 0;
hIcon: 0;
hCursor: 0;
hbrBackground: 0;
lpszMenuName: nil;
lpszClassName: WindowClassName);
procedure CreateWindowFromThread;
var
A: ATOM;
begin
A := RegisterClass(WindowClass);
WindowHandle := CreateWindowEx(WS_EX_TOOLWINDOW, WindowClassName, 'Message Helper Window', WS_POPUP, 0, 0, 0, 0, 0, 0, hInstance, nil);
end;
procedure FreeWindowFromThread;
var
H: HWND;
begin
H := WindowHandle;
WindowHandle := 0;
DestroyWindow(H);
UnregisterClass(WindowClassName, hInstance);
end;
function ThreadFunc(P: Pointer): Integer; //The worker thread main loop, windows handle initialization and finalization
const
EventCount = 1;
var
EventArray: array[0..EventCount-1] of THandle;
R: Cardinal;
M: TMsg;
begin
Result := 0;
CreateWindowFromThread;
try
EventArray[0] := ExitEvent; // you may add other events if you need - just enlarge the Events array
SetEvent(ThreadReadyEvent);
repeat
R := MsgWaitForMultipleObjects(EventCount, EventArray, False, INFINITE, QS_ALLINPUT);
if R = WAIT_OBJECT_0 + EventCount then
begin
while PeekMessage(M, WindowHandle, 0, 0, PM_REMOVE) do
begin
case M.Message of
WM_QUIT:
Break;
else
begin
TranslateMessage(M);
DispatchMessage(M);
end;
end;
end;
if M.Message = WM_QUIT then
Break;
end else
if R = WAIT_OBJECT_0 then
begin
// we have the ExitEvent signaled - so the thread have to quit
Break;
end else
if R = WAIT_TIMEOUT then
begin
// do nothing, the timeout should not have happened since we have the INFINITE timeout
end else
begin
// some errror happened, or the wait was abandoned with WAIT_ABANDONED_0 to (WAIT_ABANDONED_0 + nCount– 1)
// just exit the thread
Break;
end;
until False;
finally
FreeWindowFromThread;
end;
end;
procedure InitializeFromMainThread;
begin
ExitEvent := CreateEvent(nil, False, False, nil);
ThreadReadyEvent := CreateEvent(nil, False, False, nil);
ThreadHandle := BeginThread(nil, 0, @ThreadFunc, nil, 0, ThreadId);
end;
procedure WaitUntilHelperThreadIsReady;
begin
WaitForSingleObject(ThreadReadyEvent, INFINITE); // wait until the worker thread start running and initialize the main window
CloseHandle(ThreadReadyEvent); // we won't need it any more
ThreadReadyEvent := 0;
end;
procedure FinalizeFromMainThread;
begin
SetEvent(ExitEvent); // we should call it AFTER terminate for the Terminated property would already be True when the tread exits from MsgWaitForMultipleObjects
WaitForSingleObject(ThreadHandle, INFINITE);
CloseHandle(ThreadHandle); ThreadHandle := 0;
CloseHandle(ExitEvent); ExitEvent := 0;
end;
initialization
InitializeFromMainThread;
WaitUntilHelperThreadIsReady; // we can call it later, just before we need the window handle
finalization
FinalizeFromMainThread;
end.
SendMessage
不应与线程MQ一起工作,PostMessage
是。如果HWND属于另一个进程,SendMessage()仍然需要接收线程执行消息检索(即消息循环)。感谢@Remy Lebeau。MsgWaitForMultipleObjects()是我缺少的关键成分。您应该使用DSiAllocateHwnd而不是AllocateHwnd。@David:WaitMessage()
在新消息到达之前不会返回,从而阻止调用线程。MsgWaitForMultipleObjects()
有一个超时,因此当消息队列空闲时,线程可以醒来做其他事情,比如检查Terminated
属性。您可以这样做WaitMessage()
除非您自己发布消息。@Maximasiutin:是的,我知道所有这些。我以前的评论是几年前写的。在现代Delphi版本中,TThread
有一个虚拟的TerminatedSet()
方法,当Terminate()时,可以重写该方法来执行此类发布/信号操作调用了
,但最初的问题是针对Delphi 6的,它没有TerminatedSet()
,因此需要一个自定义方法。我已经更新了我的答案。@NasreddineAbdelillahGalfout Yes如果我在程序中使用了Halt
,则不会执行终结部分。这可以吗。@NasreddineAbdelillahGalfout不使用Halt
。除了极端情况外,很少有好的理由使用它conditions@RemyLebeau感谢您的两个回复。我一直在阅读有关AllocateHWnd()
和其他备选方案的文档。最后定稿部分出现了,当我读到它时,我发现了关于Halt
。我没有使用它,但很高兴知道。再次感谢您。