我正在为SetWindowsHookEx
API编写实用程序单元。
要使用它,我希望有这样的界面:
var
Thread: TKeyboardHookThread;
begin
Thread := TKeyboardHookThread.Create(SomeForm.Handle, SomeMessageNumber);
try
Thread.Resume;
SomeForm.ShowModal;
finally
Thread.Free; // <-- Application hangs here
end;
end;
在我当前的TKeyboardHookThread
实现中,我无法正确退出线程。
代码是:
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 < 0 then begin
Result := CallNextHookEx(0, nCode, wParam, lParam)
end else begin
S := PKBDLLHOOKSTRUCT(lParam)^;
PostMessage(TKeyboardHookThread.FKeyReceiverWindowHandle, TKeyboardHookThread.FMessage, S.vkCode, 0);
Result := CallNextHookEx(0, 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, 0, 0);
inherited;
end;
procedure TKeyboardHookThread.Execute;
var
m: tagMSG;
hook: HHOOK;
begin
hook := SetWindowsHookEx(WH_KEYBOARD_LL, @HookProc, HInstance, 0);
try
FHiddenWindow := TForm.Create(nil);
try
while GetMessage(m, 0, 0, 0) do begin
TranslateMessage(m);
DispatchMessage(m);
end;
finally
FHiddenWindow.Free;
end;
finally
UnhookWindowsHookEx(hook);
end;
end;
AFAICS只有在线程中有消息循环时才会调用钩子过程。问题是我不知道如何正确退出此消息循环。
我尝试使用属于该线程的隐藏TForm
来执行此操作,但是消息循环不会处理我发送到该窗体的窗口句柄的消息。
如何正确执行此操作,以便在线程关闭时终止消息循环?
编辑:我现在使用的解决方案看起来像这样(并且像魅力一样):
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 < 0 then begin
Result := CallNextHookEx(0, nCode, wParam, lParam)
end else begin
S := PKBDLLHOOKSTRUCT(lParam)^;
PostMessage(TKeyboardHookThread.FKeyReceiverWindowHandle, TKeyboardHookThread.FMessage, S.vkCode, 0);
Result := CallNextHookEx(0, 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, 0, 0);
inherited;
end;
procedure TKeyboardHookThread.Execute;
var
m: tagMSG;
hook: HHOOK;
begin
hook := SetWindowsHookEx(WH_KEYBOARD_LL, @HookProc, HInstance, 0);
try
while GetMessage(m, 0, 0, 0) do begin
TranslateMessage(m);
DispatchMessage(m);
end;
finally
UnhookWindowsHookEx(hook);
end;
end;
答案 0 :(得分:7)
您需要将WM_QUIT消息发送到该线程的消息队列以退出该线程。如果从队列中提取的消息是WM_QUIT,则GetMessage返回false,因此它将在接收到该消息时退出循环。
为此,使用PostThreadMessage函数将WM_QUIT消息直接发送到线程的消息队列。例如:
PostThreadMessage(Thread.Handle, WM_QUIT, 0, 0);
答案 1 :(得分:2)
消息泵永远不会退出,所以当你释放线程时,它会无限期地阻塞,等待Execute方法完成。从线程调用PostQuitMessage来终止消息泵。如果你想从主线程中调用它,那么你需要将一个WM_QUIT发布到该线程。
此外,您隐藏的窗口是等待发生的灾难。您无法在主线程外创建VCL对象。您将不得不使用原始Win32创建一个窗口句柄,甚至更好,使用DsiAllocateHwnd。