4

SetWindowsHookExAPIのユーティリティユニットを書いています。

それを使用するには、次のようなインターフェイスが必要です。

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スレッドに属するhiddenを使用してこれを実行しようとしましたが、メッセージループは、そのフォームのウィンドウハンドルに送信しているメッセージを処理しません。

スレッドのシャットダウン時にメッセージループが終了するように、これを正しく行うにはどうすればよいですか?

編集:私が現在使用しているソリューションは次のようになります(そして魅力のように機能します):

  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;
4

2 に答える 2

7

スレッドを終了するには、WM_QUITメッセージをそのスレッドのメッセージキューに送信する必要があります。GetMessageは、キューからプルするメッセージがWM_QUITの場合、falseを返すため、そのメッセージを受信するとループを終了します。

これを行うには、PostThreadMessage関数を使用して、WM_QUITメッセージをスレッドのメッセージキューに直接送信します。例えば:

PostThreadMessage(Thread.Handle, WM_QUIT, 0, 0);
于 2012-05-02T14:27:42.743 に答える
2

メッセージポンプは決して終了しないため、スレッドを解放すると、Executeメソッドが終了するのを無期限に待機してブロックされます。スレッドからPostQuitMessageを呼び出して、メッセージポンプを終了します。これをメインスレッドから呼び出したい場合は、WM_QUITをスレッドに投稿する必要があります。

また、あなたの隠された窓は起こるのを待っている災害です。メインスレッドの外部でVCLオブジェクトを作成することはできません。生のWin32を使用してウィンドウハンドルを作成する必要があります。さらに良いのは、DsiAllocateHwndを使用することです。

于 2012-05-02T14:31:31.503 に答える