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?的更多相关文章
- 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 ...
 - 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 ...
 - 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 ...
 - 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 ...
 - WaitForMultipleObject与MsgWaitForMultipleObjects用法
		
http://blog.csdn.net/byxdaz/article/details/5638680 用户模式的线程同步机制效率高,如果需要考虑线程同步问题,应该首先考虑用户模式的线程同步方法. 但 ...
 - 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 ...
 - Correct thread terminate and destroy
		
http://www.techques.com/question/1-3788743/Correct-thread-destroy Hello At my form I create TFrame a ...
 - 多线程爬坑之路-Thread和Runable源码解析
		
多线程:(百度百科借一波定义) 多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提 ...
 - Worker Thread
		
http://www.codeproject.com/Articles/552/Using-Worker-Threads Introduction Worker threads are an eleg ...
 
随机推荐
- 【转】谈一谈PHP字串清除空格函数不安全
			
清除空格的方法是不安全的,部分原因是因为字符中的空格非常多,例如 "addslashes的问题在 于黑客 可以用0xbf27来代替单引号,而addslashes只是将0xbf27修改为0xb ...
 - JDBC数据源(DataSource)的简单实现
			
数据源技术是Java操作数据库的一个很关键技术,流行的持久化框架都离不开数据源的应用. 数据源提供了一种简单获取数据库连接的方式,并能在内部通过一个池的机制来复用数据库连接,这样就大大减少创建数据 ...
 - 【剑指offer 面试题34】丑数
			
只包含因子2.3.5的数称作丑数. #include <iostream> #include <vector> using namespace std; int GetUgly ...
 - Linux基本命令(10)其他命令
			
其他命令 命令 功能 命令 功能 echo 显示一字串 passwd 修改密码 clear 清除显示器 lpr 打印 lpq 查看在打印队列中等待的作业 lprm 取消打印队列中的作业 10.1 ec ...
 - [转]inux之touch命令
			
转自:http://www.2cto.com/os/201309/242518.html Linux学习之touch命令 Linux的touch命令一般用来更改文档或目录的日期时间,包括存取时间和 ...
 - winform form
			
WinForm:Windows Form,.Net中用来开发Windows窗口程序的技术,无论是之前学的控制台程序,还是后面要学的asp.net都是调用.net框架,因此所有知识点都是一样的.新建一个 ...
 - 数往知来C#之 正则表达式   委托  XML<六>
			
C# 正则表达式篇 一.正则表达式 正则表达式就是一个字符串,不要想着一下子可以写出一个通用的表达式,先写,不正确再改 写正则表达式就是在找规律 关键字:Regex -->引入命名空间 ...
 - 常用SQL整理
			
整理了日常用到的一些sqls 1.插入表 insert into table_B select * from table_A 2.清空表 truncate table test #清空表,结构还存在d ...
 - Linux进程间通信——使用共享内存
			
一.什么是共享内存 顾名思义,共享内存就是允许两个不相关的进程访问同一个逻辑内存.共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式.不同进程之间共享的内存通常安排为同一段物理内存. ...
 - 桶排序-C-结构体排序
			
struct TS { int index; ]; }; ] = {{,,,,,"s8"}}; ]; int i; int length = sizeof(a) / sizeof ...