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. Windows下PHP+Eclipse开发环境搭建 及错误解决(apache2.2服务无法启动 发生服务特定错误:1)

    前言 Eclipse与php/apache的关系:Eclipse只是用来写代码的,如果想要在浏览器查看运行效果就要让php/apache的运行目录指向你的代码目录.Eclipse貌似不会自己和apac ...

  2. php 序列化(serialize)格式详解

    1.前言 PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize.unserialize.不过在 PHP 手册中对这两个函数的说明仅限于如何使用,而对序列 ...

  3. min_free_kbytes

    http://kernel.taobao.org/index.php?title=Kernel_Documents/mm_sysctl min_free_kbytes 先看官方解释:This is u ...

  4. [Everyday Mathematics]20150222

    设 $$\bex a_0=1,\quad a_1=\frac{1}{2},\quad a_{n+1}=\frac{na_n^2}{1+(n+1)a_n}\ (n\geq 1). \eex$$ 试证: ...

  5. Delphi 之前解析串口数据

    //串口接收数据procedure TfrmClientMain.Comm1ReceiveData(Sender: TObject; Buffer: Pointer; BufferLength: Wo ...

  6. java线程实践记录

    框架构建过程中遇到需要用到线程的地方,虽然以前经常听到线程,也看过一些线程类的文章,但真正使用时还是遇到一些问题,此篇正式为了记录自己对线程实操的体会. 入口类代码: public class tes ...

  7. LoadRunner error -27498

    URL=http://172.18.20.70:7001/workflow/bjtel/leasedline/ querystat/ subOrderQuery.do错误分析:这种错误常常是因为并发压 ...

  8. php 在线 mysql 大数据导入程序

    1 <?php header("content-type:text/html;charset=utf-8"); error_reporting(E_ALL); set_tim ...

  9. 从date中获取相应信息

    创建测试用表: CREATE OR REPLACE VIEW v AS SELECT TO_DATE('2015-5-5 13:14:15', 'YYYY-MM-DD HH24:MI:SS') AS ...

  10. Laravel不同数据库的模型之间关联

    假设ModelA和ModelB之间是BelongsTo的关系 如果同属于一个数据库连接 那么 public function a(){ return $this->belongsTo(" ...