http://www.techques.com/question/1-10415481/How-can-I-terminate-a-thread-that-has-a-seperate-message-loop

I am writing a utility unit for the SetWindowsHookEx API.

To use it, I'd like to have an interface like this:

var
Thread: TKeyboardHookThread;
begin
Thread := TKeyboardHookThread.Create(SomeForm.Handle, SomeMessageNumber);
try
Thread.Resume;
SomeForm.ShowModal;
finally
Thread.Free; // <-- Application hangs here
end;
end;

In my current implementation of TKeyboardHookThread I am unable to make the thread exit correctly.

The code is:

TKeyboardHookThread = class(TThread)
private
class var
FCreated : Boolean;
FKeyReceiverWindowHandle : HWND;
FMessage : Cardinal;
FHiddenWindow : TForm;
public
constructor Create(AKeyReceiverWindowHandle: HWND; AMessage: Cardinal);
destructor Destroy; override;
procedure Execute; override;
end; function HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
S: KBDLLHOOKSTRUCT;
begin
if nCode < then begin
Result := CallNextHookEx(, nCode, wParam, lParam)
end else begin
S := PKBDLLHOOKSTRUCT(lParam)^;
PostMessage(TKeyboardHookThread.FKeyReceiverWindowHandle, TKeyboardHookThread.FMessage, S.vkCode, );
Result := CallNextHookEx(, nCode, wParam, lParam);
end;
end; constructor TKeyboardHookThread.Create(AKeyReceiverWindowHandle: HWND;
AMessage: Cardinal);
begin
if TKeyboardHookThread.FCreated then begin
raise Exception.Create('Only one keyboard hook supported');
end;
inherited Create('KeyboardHook', True);
FKeyReceiverWindowHandle := AKeyReceiverWindowHandle;
FMessage := AMessage;
TKeyboardHookThread.FCreated := True;
end; destructor TKeyboardHookThread.Destroy;
begin
PostMessage(FHiddenWindow.Handle, WM_QUIT, , );
inherited;
end; procedure TKeyboardHookThread.Execute;
var
m: tagMSG;
hook: HHOOK;
begin
hook := SetWindowsHookEx(WH_KEYBOARD_LL, @HookProc, HInstance, );
try
FHiddenWindow := TForm.Create(nil);
try
while GetMessage(m, , , ) do begin
TranslateMessage(m);
DispatchMessage(m);
end;
finally
FHiddenWindow.Free;
end;
finally
UnhookWindowsHookEx(hook);
end;
end;

AFAICS the hook procedure only gets called when there is a message loop in the thread.

The problem is I don't know how to correctly exit this message loop.

I tried to do this using a hidden TForm that belongs to the thread,

but the message loop doesn't process messages I'm sending to the window handle of that form.

How to do this right, so that the message loop gets terminated on thread shutdown?

Edit: The solution I'm now using looks like this (and works like a charm):

 TKeyboardHookThread = class(TThread)
private
class var
FCreated : Boolean;
FKeyReceiverWindowHandle : HWND;
FMessage : Cardinal;
public
constructor Create(AKeyReceiverWindowHandle: HWND; AMessage: Cardinal);
destructor Destroy; override;
procedure Execute; override;
end; function HookProc(nCode: Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
var
S: KBDLLHOOKSTRUCT;
begin
if nCode < then begin
Result := CallNextHookEx(, nCode, wParam, lParam)
end else begin
S := PKBDLLHOOKSTRUCT(lParam)^;
PostMessage(TKeyboardHookThread.FKeyReceiverWindowHandle, TKeyboardHookThread.FMessage, S.vkCode, );
Result := CallNextHookEx(, nCode, wParam, lParam);
end;
end; constructor TKeyboardHookThread.Create(AKeyReceiverWindowHandle: HWND;
AMessage: Cardinal);
begin
if TKeyboardHookThread.FCreated then begin
raise Exception.Create('Only one keyboard hook supported');
end;
inherited Create('KeyboardHook', True);
FKeyReceiverWindowHandle := AKeyReceiverWindowHandle;
FMessage := AMessage;
TKeyboardHookThread.FCreated := True;
end; destructor TKeyboardHookThread.Destroy;
begin
PostThreadMessage(ThreadId, WM_QUIT, , );
inherited;
end; procedure TKeyboardHookThread.Execute;
var
m: tagMSG;
hook: HHOOK;
begin
hook := SetWindowsHookEx(WH_KEYBOARD_LL, @HookProc, HInstance, );
try
while GetMessage(m, , , ) do begin
TranslateMessage(m);
DispatchMessage(m);
end;
finally
UnhookWindowsHookEx(hook);
end;
end;

You need to send the WM_QUIT message to that thread's message queue to exit the thread.

GetMessage returns false if the message it pulls from the queue is WM_QUIT, so it will exit the loop on receiving that message.

To do this, use the PostThreadMessage function to send the WM_QUIT message directly to the thread's message queue.

For example:

PostThreadMessage(Thread.Handle, WM_QUIT, , );

The message pump never exits and so when you free the thread

it blocks indefinitely waiting for the Execute method to finish.

Call PostQuitMessage, from the thread, to terminate the message pump.

If you wish to invoke this from the main thread then you will need to

post a WM_QUIT to the thread.

Also, your hidden window is a disaster waiting to happen.

You can't create a VCL object outside the main thread.

You will have to create a window handle using raw Win32,

or even better, use DsiAllocateHwnd.

http://www.techques.com/question/1-10451535/How-to-exit-a-thread's-message-loop?

It turns out that it's better for everyone that you don't use a windowless message queue.

A lot of things can be unintentionally, and subtly, broken if you don't have a window for messages to be dispatched to.

Instead allocate hidden window (e.g. using Delphi's thread-unsafe AllocateHwnd)

and post messages to it using plain old PostMessage:

procedure TMyThread.Execute;
var
msg: TMsg;
begin
Fhwnd := AllocateHwnd(WindowProc);
if Fhwnd = then Exit;
try
while Longint(GetMessage(msg, , , )) > do // will block until a message arrives on the queue.
begin
TranslateMessage(msg);
DispatchMessage(msg);
end;
finally
DeallocateHwnd(Fhwnd);
Fhwnd := ;
end;
end;

Where we can have a plain old window procedure to handle the messages:

WM_TerminateYourself = WM_APP + ;

procedure TMyThread.WindowProc(var msg: TMessage);
begin
case msg.Msg of
WM_ReadyATractorBeam: ReadyTractorBeam;
WM_TerminateYourself: PostQuitMessage(0);
else
msg.Result := DefWindowProc(Fhwnd, msg.msg, msg.wParam, msg.lParam);
end;
end;

and when you want the thread to finish, you tell it:

procedure TMyThread.Terminate;
begin
PostMessage(Fhwnd, WM_TerminateYourself, , );
end; PostThreadMessage( FThreadId, WM_QUIT, 0, 0 );

Using PostThreadMessage is not necessarily incorrect. Raymond's article that you linked to says:

PostQuitMessage(0)

Because the system tries not to inject a WM_QUIT message at a "bad time";

instead it waits for things to "settle down" before generating the WM_QUIT message,

thereby reducing the chances that the program might be in the middle of a multi-step

procedure triggered by a sequence of posted messages.

If the concerns outlined here do not apply to your message queue,

then call PostThreadMessage with WM_QUIT and knock yourself out.

Otherwise you'll need to create a special signal, i.e. a user-defined message,

that allows you to call PostQuitMessage from the thread.

How can I terminate a thread that has a seperate message loop?的更多相关文章

  1. Thread message loop for a thread with a hidden window? Make AllocateHwnd safe

    Thread message loop for a thread with a hidden window? I have a Delphi 6 application that has a thre ...

  2. The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.

    好久没有冒泡了,最近在新环境上搭建应用时,启动报错: INFO: Illegal access: this web application instance has been stopped alre ...

  3. Jmeter:运行报:Error occurred starting thread group :线程组, error message:Invalid duration 0 set in Thread Group:线程组, see log file for more details

    最近在用jmeter做压测,上周五压测的脚本,今天早晨结束后. 点击同样的脚本,运行就报Error occurred starting thread group :线程组, error message ...

  4. Await, and UI, and deadlocks! Oh my!

    It’s been awesome seeing the level of interest developers have had for the Async CTP and how much us ...

  5. WaitForMultipleObject与MsgWaitForMultipleObjects用法

    http://blog.csdn.net/byxdaz/article/details/5638680 用户模式的线程同步机制效率高,如果需要考虑线程同步问题,应该首先考虑用户模式的线程同步方法. 但 ...

  6. Android Guts: Intro to Loopers and Handlers

    One of the reasons I love Android API is because it contains so many useful little things. Many of t ...

  7. Correct thread terminate and destroy

    http://www.techques.com/question/1-3788743/Correct-thread-destroy Hello At my form I create TFrame a ...

  8. 多线程爬坑之路-Thread和Runable源码解析

    多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...

  9. Worker Thread

    http://www.codeproject.com/Articles/552/Using-Worker-Threads Introduction Worker threads are an eleg ...

随机推荐

  1. [转]Linux read用法

    来源:http://www.cnblogs.com/iloveyoucc/archive/2012/04/16/2451328.html 1.基本读取 read命令接收标准输入(键盘)的输入,或其他文 ...

  2. 关于RF 315MHz

    1.https://www.pjrc.com/teensy/td_libs_VirtualWire.html These modules worked very reliably when sitti ...

  3. Linux上修改weblogic的内存大小

    我们经常在使用WebLoigc部署应用程序后,发现程序运行速度并不是很快,遇到这种情况我们可以尝试调整启动时分配的内存,设置方法有两种: 一.在../domain/setDomainEnv.sh文件中 ...

  4. 【转】Linux下创建、销毁、使用 SWAP

    转自:http://blog.csdn.net/wxqee/article/details/7970110 创建swap文件方法 1) 创建一个足够大的文件 dd if=/dev/zero of=/l ...

  5. C# 一个简单的秒表引发的窗体卡死问题

    一个秒表程序也是我的一个心病,因为一直想写这样的一个东西,但是总往GUI那边想,所以就比较怵,可能是上学的时候学MFC搞出的后遗症吧,不过当我今天想好用Win Form(话说还是第一次写win for ...

  6. 类 .xml

    pre{ line-height:1; color:#1e1e1e; background-color:#d2d2d2; font-size:16px;}.sysFunc{color:#627cf6; ...

  7. JDK Environment Variable And Change default JDK

    Environment Variable : change(import) /etc/bashrc export JAVA_HOME=/software/jdk1.8.0 export PATH=$J ...

  8. [转]32位和64位系统区别及int字节数

    一)64位系统和32位有什么区别? 1.64bit CPU拥有更大的寻址能力,最大支持到16GB内存,而32bit只支持4G内存 2.64位CPU一次可提取64位数据,比32位提高了一倍,理论上性能会 ...

  9. Python进程和线程

    引入进程和线程的概念及区别 1.线程的基本概念 概念 线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但 ...

  10. debug 64bit dump of a 32bit process in windows 7 64bit

    In Windows 7 the TaskMgr provides one easy way to create dump for the applications. You can right cl ...