GetFocus() returns 对于 MS Word 为空

GetFocus() returns null for MS Word

我正在编写一个键盘应用程序来挂接键盘并重新映射键。为此,我创建了两个项目,一个.exe 和.dll。在 .dll 项目中,我通过 GetFocus() 检测到用户正在键入的 Window 的句柄。但是,它在记事本中工作正常,但在 MS Word 中却不行,因为我无法使用 GetFocus()

获取 MS Word 的 Window 句柄

我明白了,那是因为它可能在不同的线程下 运行 因此,我需要通过 GetForegroundWindow() 获取 Parent Window Handle 并遍历它child windows 并以某种方式获得正确的句柄。 在互联网上搜索时,我发现了以下代码 (http://www.codeproject.com/Articles/34752/Control-in-Focus-in-Other-Processes)

activeWindowHandle:= GetForegroundWindow();
activeWindowThread:= GetWindowThreadProcessId(activeWindowHandle, 0);
thisWindowThread:= GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
lpHookRec^.TheAppWinHandle:= GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);

但是,它对我不起作用:(

在我写的代码中

lpHookRec^.TheAppWinHandle := GetFocus();

这在 lpHookRec^.TheAppWinHandle 中为我提供了记事本 window 的句柄。但是,如果我使用 MS Word 而不是记事本,上面的代码会给我 null(零)。所以需要编写 returns 正确句柄的函数,不管它在 运行 下的线程如何,比如

function GetAppliWinHandle: Hwnd;
  var
    activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
  begin { GetAppliWinHandle }
    focusedControlHandle := GetFocus();
    if focusedControlHandle = 0 then
      begin
        activeWindowHandle := GetForegroundWindow();
        activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
        thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
        AttachThreadInput(activeWindowThread, thisWindowThread, true);
        focusedControlHandle := GetFocus();
        AttachThreadInput(activeWindowThread, thisWindowThread, false);
      end;
    Result:=focusedControlHandle
  end; { GetAppliWinHandle }

这里是 dll 的完整代码

library TheHook;

uses
  Windows, Messages, SysUtils;

{Define a record for recording and passing information process wide}
type
  PHookRec = ^THookRec;
  THookRec = packed record
    TheHookHandle: HHOOK;
    TheAppWinHandle: HWND;
    TheCtrlWinHandle: HWND;
    TheKeyCount: DWORD;
  end;

var
  hObjHandle: THandle; {Variable for the file mapping object}
  lpHookRec: PHookRec; {Pointer to our hook record}

procedure MapFileMemory(dwAllocSize: DWORD);
begin
  {Create a process wide memory mapped variable}
  hObjHandle := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize,
    'HookRecMemBlock');
  if (hObjHandle = 0) then
  begin
    MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK);
    exit;
  end;
  {Get a pointer to our process wide memory mapped variable}
  lpHookRec := MapViewOfFile(hObjHandle, file_MAP_write, 0, 0, dwAllocSize);
  if (lpHookRec = nil) then
  begin
    CloseHandle(hObjHandle);
    MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK);
    exit;
  end;
end;

procedure UnMapFileMemory;
begin
  {Delete our process wide memory mapped variable}
  if (lpHookRec <> nil) then
  begin
    UnMapViewOfFile(lpHookRec);
    lpHookRec := nil;
  end;
  if (hObjHandle > 0) then
  begin
    CloseHandle(hObjHandle);
    hObjHandle := 0;
  end;
end;

function GetHookRecPointer: pointer stdcall;
begin
  {Return a pointer to our process wide memory mapped variable}
  result := lpHookRec;
end;

{The function that actually processes the keystrokes for our hook}

function KeyBoardProc(Code: integer; wParam: integer; lParam: integer): integer;
  stdcall;
  function GetAppliWinHandle: Hwnd;
  var
    activeWindowHandle,activeWindowThread,thisWindowThread,focusedControlHandle: Hwnd;
  begin { GetAppliWinHandle }
    focusedControlHandle := GetFocus();
    if focusedControlHandle = 0 then
      begin
        activeWindowHandle := GetForegroundWindow();
        activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
        thisWindowThread := GetWindowThreadProcessId(lpHookRec^.TheHookHandle, 0);
        AttachThreadInput(activeWindowThread, thisWindowThread, true);
        focusedControlHandle := GetFocus();
        AttachThreadInput(activeWindowThread, thisWindowThread, false);
      end;
    Result:=focusedControlHandle
  end; { GetAppliWinHandle }

var
  KeyUp: bool;
  {Remove comments for additional functionability ... :

  IsAltPressed: bool;
  IsCtrlPressed: bool;
  IsShiftPressed: bool;
  }
begin
  result := 0;
  case Code of
    HC_ACTION:
      begin
        {We trap the keystrokes here}
        {is this a key up message?}
        KeyUp := ((lParam and (1 shl 31)) <> 0);

        {Remove comments for additional functionability ... :

        {is the Alt key pressed}
        if ((lParam and (1 shl 29)) <> 0) then
        begin
          IsAltPressed := TRUE;
        end
        else
        begin
          IsAltPressed := FALSE;
        end;
        {is the Control key pressed}
        if ((GetKeyState(VK_CONTROL) and (1 shl 15)) <> 0) then
        begin
          IsCtrlPressed := TRUE;
        end
        else
        begin
          IsCtrlPressed := FALSE;
        end;
        {if the Shift key pressed}
        if ((GetKeyState(VK_SHIFT) and (1 shl 15)) <> 0) then
        begin
          IsShiftPressed := TRUE;
        end
        else
        begin
          IsShiftPressed := FALSE;
        end;
        }

        {if KeyUp then increment the key count}
        if (KeyUp <> FALSE) then
        begin
          Inc(lpHookRec^.TheKeyCount);
        end;
        case wParam of
          {Was the enter key pressed?}
          VK_RETURN:
            begin
              {if KeyUp}
              if (KeyUp <> FALSE) then
              begin
                {Post a bogus message to the window control in our app}
                PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYDOWN, 0, 0);
                PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYUP, 0, 0);
              end;
              {if you wanted to swallow the keystroke then return -1, else if you 
want 
                            to allow the keystroke then return 0}
              result := 0;
              exit;
            end; {VK_RETURN}
          {if the left arrow key is pressed then lets play a joke!}
          VK_LEFT:
            begin
              {Get the Handle of the Application Window in lpHookRec^.TheAppWinHandle}
             lpHookRec^.TheAppWinHandle:=GetAppliWinHandle;

              {if KeyUp}
              if (KeyUp <> FALSE) then
              begin
                {Create a UpArrow keyboard event}
                keybd_event(VK_RIGHT, 0, 0, 0);
                keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0);
              end;
              {Swallow the keystroke}
              result := -1;
              exit;
            end; {VK_LEFT}
        end; {case wParam}
        {Allow the keystroke}
        result := 0;
      end; {HC_ACTION}
    HC_NOREMOVE:
      begin
        {This is a keystroke message, but the keystroke message has not been removed
             from the message queue, since an application has called PeekMessage() 
            specifying PM_NOREMOVE}
        result := 0;
        exit;
      end;
  end; {case code}
  if (Code < 0) then
    {Call the next hook in the hook chain}
    result := CallNextHookEx(lpHookRec^.TheHookHandle, Code, wParam, lParam);
end;

procedure StartKeyBoardHook stdcall;
begin
  {if we have a process wide memory variable and the hook has not already been 
set...}
  if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle = 0)) then
  begin
    {set the hook and remember our hook handle}
    lpHookRec^.TheHookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardProc,
      hInstance, 0);
  end;
end;

procedure StopKeyBoardHook stdcall;
begin
  {if we have a process wide memory variable and the hook has already been set...}
  if ((lpHookRec <> nil) and (lpHookRec^.TheHookHandle <> 0)) then
  begin
    {Remove our hook and clear our hook handle}
    if (UnHookWindowsHookEx(lpHookRec^.TheHookHandle) <> FALSE) then
    begin
      lpHookRec^.TheHookHandle := 0;
    end;
  end;
end;

procedure DllEntryPoint(dwReason: DWORD);
begin
  case dwReason of
    Dll_Process_Attach:
      begin
        {if we are getting mapped into a process, then get a pointer to our 
                process wide memory mapped variable}
        hObjHandle := 0;
        lpHookRec := nil;
        MapFileMemory(sizeof(lpHookRec^));
      end;
    Dll_Process_Detach:
      begin
        {if we are getting unmapped from a process then, remove the pointer to 
                our process wide memory mapped variable}
        UnMapFileMemory;
      end;
  end;
end;

exports
  KeyBoardProc name 'KEYBOARDPROC',
  GetHookRecPointer name 'GETHOOKRECPOINTER',
  StartKeyBoardHook name 'STARTKEYBOARDHOOK',
  StopKeyBoardHook name 'STOPKEYBOARDHOOK';

begin
  {set our Dll's main entry point}
  DLLProc := @DllEntryPoint;
  {Call our Dll's main entry point}
  DllEntryPoint(Dll_Process_Attach);

end.

根据 GetFocus() 文档:

Retrieves the handle to the window that has the keyboard focus, if the window is attached to the calling thread's message queue.

...

GetFocus returns the window with the keyboard focus for the current thread's message queue. If GetFocus returns NULL, another thread's queue may be attached to a window that has the keyboard focus.

Use the GetForegroundWindow function to retrieve the handle to the window with which the user is currently working. You can associate your thread's message queue with the windows owned by another thread by using the AttachThreadInput function.

你正在尝试做这部分,但你做的不正确,而且你没有检查过程中的错误。您还错误地将 HWND 数据类型用于线程 ID,但它们不是 HWND,而是 DWORD

试试像这样的东西:

function GetAppliWinHandle: HWND;
var
  activeWindowHandle: HWND;
  activeWindowThread, thisThread: DWORD;
begin
  Result := GetFocus();
  if Result = 0 then
  begin
    activeWindowHandle := GetForegroundWindow();
    if activeWindowHandle <> 0 then
    begin
      activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
      thisThread := GetCurrentThreadId();
      if AttachThreadInput(activeWindowThread, thisThread, TRUE) then
      begin
        Result := GetFocus();
        AttachThreadInput(activeWindowThread, thisThread, FALSE);
      end;
    end;
  end;
end;

但是,相同的文档说:

To get the window with the keyboard focus on the foreground queue or the queue of another thread, use the GetGUIThreadInfo function.

例如:

function GetAppliWinHandle: HWND;
var
  activeWindowHandle: HWND;
  activeWindowThread: DWORD;
  gui: TGUIThreadinfo;
begin
  Result := GetFocus();
  if Result = 0 then
  begin
    activeWindowHandle := GetForegroundWindow();
    if activeWindowHandle <> 0 then
    begin
      activeWindowThread := GetWindowThreadProcessId(activeWindowHandle, 0);
      gui.cbSize := sizeof(gui);
      if GetGUIThreadInfo(activeWindowThread, gui) then
        Result := gui.hwndFocus;
    end;
  end;
end;

或更简单:

function GetAppliWinHandle: HWND;
var
  gui: TGUIThreadinfo;
begin
  gui.cbSize := sizeof(gui);
  if GetGUIThreadInfo(0, gui) then
    Result := gui.hwndFocus
  else
    Result := 0;
end;