2012-05-02 40 views
3

我正在編寫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; 

回答

6

您需要發送WM_QUIT消息到該線程的消息隊列退出線程。 GetMessage如果從隊列中提取的消息是WM_QUIT,則返回false,因此它將在收到該消息時退出循環。

爲此,請使用PostThreadMessage函數將WM_QUIT消息直接發送到線程的消息隊列。例如:

PostThreadMessage(Thread.Handle, WM_QUIT, 0, 0); 
+0

我不知道PostThreadMessage調用,我想我應該花更多時間學習WinAPI。 –

2

的消息泵永遠不會退出,所以當你釋放它的線程塊無限期地等待Execute方法來完成。從線程中調用PostQuitMessage來終止消息泵。如果你想從主線程調用這個,那麼你將需要發佈WM_QUIT到線程。

此外,您的隱藏窗口是一個等待發生的災難。你不能在主線程之外創建一個VCL對象。你將不得不使用原始的Win32創建窗口句柄,或者甚至更好,使用DsiAllocateHwnd。

+0

窗戶只是一個「最後的手段」,我之前嘗試了各種各樣的東西。我實際上並不需要一個窗口句柄。 –

+0

@JensMühlenhoff - 但你的消息循環需要一個窗口句柄; 'DispatchMessage'發送一個消息到一個窗口過程,'DSiAllocateHWND'是創建這個過程的最佳方法。 – kludg

+0

@Serg實際上它不需要窗口句柄,一切都正常工作,沒有一個。消息循環的唯一目的是讓Windows有機會調用鉤子程序。有沒有辦法使用Windows Hook沒有消息循環? –