SetWindowsHookEx создает локальный хук. Как сделать это глобальным? - PullRequest
9 голосов
/ 04 марта 2012

В приложении Delphi XE я пытаюсь настроить глобальный хук для отслеживания изменений фокуса. Крюк создан в dll:

focusHook := SetWindowsHookEx( WH_CBT, @FocusHookProc, HInstance, 0 );
// dwThreadId (the last argument) set to 0 should create a global hook

В той же dll у меня есть процедура ловушки, которая отправляет сообщение в окно приложения хоста:

function FocusHookProc( code : integer; wParam: WPARAM; lParam: LPARAM ) : LResult; stdcall;
begin
  if ( code < 0 ) then
  begin
    result := CallNextHookEx( focusHook, code, wParam, lParam );
    exit;
  end;

  result := 0;

  if ( code = HCBT_SETFOCUS ) then
  begin
    if ( hostHWND <> INVALID_HANDLE_VALUE ) then
      PostMessage( hostHWND, cFOCUSMSGID, wParam, lParam );
  end;
end;

Это работает, но хост получает уведомления только об изменениях фокуса внутри самого приложения. На главной форме есть памятка и несколько кнопок, а переключение между ними приводит к ожидаемому сообщению. Однако о любых изменениях фокуса вне самого приложения никогда не сообщается.

Я полагаю, это как-то связано с несколькими экземплярами DLL, внедряемыми в другие процессы. Есть похожий вопрос с принятым ответом здесь , но он для C, и я не совсем понимаю, как я могу сделать то же самое в dll Delphi (например, прагматические операторы для настройки общей памяти ).

(Это в основном подтверждение концепции, но я все же хотел бы заставить его работать. Мне нужно знать, какое окно было активным незадолго до того, как мое приложение было активировано, путем нажатия клавиш alt + tab, горячая клавиша активации и т. д. Проблема заключается в том, что если используется мышь или вкладка alt +, GetForegroundWindow всегда возвращает дескриптор окна моего собственного приложения, независимо от того, как рано я его поместил, например, перехватывая основную очередь сообщений приложения. единственное жизнеспособное решение, хотя мне не очень нравится идея.)

Ответы [ 2 ]

15 голосов
/ 04 марта 2012

Поскольку DLL внедряется в другой процесс, вы не собираетесь получить любые точки останова для чего-либо, кроме процесса, который вы отлаживаете. Кроме того, каждый экземпляр DLL в другом процессе также получает свои собственные глобальные / статические данные. Если hostHWND является глобальным, он не будет иметь такого же значения в другом процессе, как в этом. На самом деле он даже не будет инициализирован. Вам нужно использовать блок общей памяти, чтобы делиться значениями между процессами. Общие мьютексы и другие объекты синхронизации могут потребоваться для обеспечения защиты любых записей в общую память. Наконец, если вы используете Windows Vista +, только процессы с одинаковым уровнем доступа и ниже будут внедрены в DLL. IOW, если вы запускаете процесс как вошедший в систему пользователь, только обработанный, запущенный как вошедший в систему пользователь получит эту DLL-библиотеку.

4 голосов
/ 04 марта 2012

Попробуйте использовать WinEvents вместо ловушки CBT: SetWinEventHook ищет EVENT_OBJECT_FOCUS как минимальное и максимальное событие, с флагом WINEVENT_OUTOFPROC и 0 для idThread и idProcess. Это даст вам возможность ловить события фокуса от любого процесса на одном рабочем столе, не требуя отдельной DLL, и будет работать как в 32-битных, так и в 64-битных приложениях.

Есть несколько предостережений: во-первых, события не являются мгновенными; есть небольшая задержка, поскольку они по существу отправляются в ваш процесс (именно так работает опция out-of-proc, которая позволяет избежать необходимости использования DLL), но они вполне могут быть достаточно быстрыми для вашего использования. (И у вас возникнет та же проблема, если вы все равно используете PostMessage в своем хуке DLL!)

Кроме того, вы получите больше событий, чем фактических изменений фокуса HWND: различные элементы управления отправляют эти события изменения фокуса в сигнал внутренний изменение фокуса - например, перемещение фокуса между элементами в списке. Вы можете отфильтровать их, отфильтровав в обратном вызове только те, у которых idObject = OBJID_WINDOW и idChild = 0.

В качестве альтернативы, если вы прослушиваете события EVENT_SYSTEM_FOREGROUND вместо EVENT_OBJECT_FOCUS ( полный список событий см. В MSDN ), то, похоже, вы должны получать только события верхнего уровня окна верхнего уровня, которые звучат так, как вы на самом деле после здесь.

...