Что мне нужно сделать, чтобы моя процедура-перехватчик WH_SHELL или WH_CBT получала события от других процессов?

StackOverflow https://stackoverflow.com/questions/299370

  •  08-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь использовать SetWindowsHookEx чтобы создать WH_SHELL крючок для получения уведомлений об общесистемных HSHELL_WINDOWCREATED и HSHELL_WINDOWDESTROYED события.Я набираю 0 в финале dwThreadId аргумент, который, по мнению документы, должен «связать процедуру перехвата со всеми существующими потоками, работающими на том же рабочем столе, что и вызывающий поток».Я также передаю дескриптор моей DLL (HInstance в Delphi) для hMod параметр, как и все примеры, которые я рассматривал.

Тем не менее, я получаю уведомления только об окнах, созданных моим собственным приложением, и - чаще всего - мои тесты приводят к тому, что процесс рабочего стола загорается, как только я закрываю свое приложение.Прежде чем ты спросишь, я позвоню UnhookWindowsHookEx.я тоже всегда звоню CallNextHookEx изнутри моего обработчика.

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

В действительности, я сделал все по инструкции (очевидно, что нет, но пока не вижу, где).

Я использую Delphi (2007), но я думаю, это не имеет особого значения.

РЕДАКТИРОВАТЬ: Возможно, мне следовало упомянуть об этом раньше:Я скачал и попробовал пару примеров (хотя, к сожалению, их не так много для Delphi, особенно для Delphi). WH_SHELL или WH_CBT).Хотя они не приводят к сбою системы, как это делает мое тестовое приложение, они все равно не фиксируют события из других процессов (хотя я могу проверить с помощью ProcessExplorer, что они загружаются в них нормально).Похоже, что-то не так с конфигурацией моей системы, либо примеры неверны, либо просто невозможно перехватить события из других процессов.Может ли кто-нибудь просветить меня?

РЕДАКТИРОВАТЬ2: Хорошо, вот исходный код моего тестового проекта.

DLL, содержащая процедуру перехвата:

library HookHelper;

uses
  Windows;

{$R *.res}

type
  THookCallback = procedure(ACode, AWParam, ALParam: Integer); stdcall;

var
  WndHookCallback: THookCallback;
  Hook: HHook;

function HookProc(ACode, AWParam, ALParam: Integer): Integer; stdcall;
begin
  Result := CallNextHookEx(Hook, ACode, AWParam, ALParam);
  if ACode < 0 then Exit;
  try
    if Assigned(WndHookCallback)
//    and (ACode in [HSHELL_WINDOWCREATED, HSHELL_WINDOWDESTROYED]) then
    and (ACode in [HCBT_CREATEWND, HCBT_DESTROYWND]) then
      WndHookCallback(ACode, AWParam, ALParam);
  except
    // plop!
  end;
end;

procedure InitHook(ACallback: THookCallback); register;
begin
//  Hook := SetWindowsHookEx(WH_SHELL, @HookProc, HInstance, 0);
  Hook := SetWindowsHookEx(WH_CBT, @HookProc, HInstance, 0);
  if Hook = 0 then
    begin
//      ShowMessage(SysErrorMessage(GetLastError));
    end
  else
    begin
      WndHookCallback := ACallback;
    end;
end;

procedure UninitHook; register;
begin
  if Hook <> 0 then
    UnhookWindowsHookEx(Hook);
  WndHookCallback := nil;
end;

exports
  InitHook,
  UninitHook;

begin
end.

И основная форма приложения с использованием хука:

unit MainFo;

interface

uses
  Windows, SysUtils, Forms, Dialogs, Classes, Controls, Buttons, StdCtrls;

type
  THookTest_Fo = class(TForm)
    Hook_Btn: TSpeedButton;
    Output_Lbx: TListBox;
    Test_Btn: TButton;
    procedure Hook_BtnClick(Sender: TObject);
    procedure Test_BtnClick(Sender: TObject);
  public
    destructor Destroy; override;
  end;

var
  HookTest_Fo: THookTest_Fo;

implementation

{$R *.dfm}

type
  THookCallback = procedure(ACode, AWParam, ALParam: Integer); stdcall;

procedure InitHook(const ACallback: THookCallback); register; external 'HookHelper.dll';
procedure UninitHook; register; external 'HookHelper.dll';

procedure HookCallback(ACode, AWParam, ALParam: Integer); stdcall;
begin
  if Assigned(HookTest_Fo) then
    case ACode of
  //    HSHELL_WINDOWCREATED:
      HCBT_CREATEWND:
          HookTest_Fo.Output_Lbx.Items.Add('created handle #' + IntToStr(AWParam));
  //    HSHELL_WINDOWDESTROYED:
      HCBT_DESTROYWND:
        HookTest_Fo.Output_Lbx.Items.Add('destroyed handle #' + IntToStr(AWParam));
    else
      HookTest_Fo.Output_Lbx.Items.Add(Format('code: %d, WParam: $%x, LParam: $%x', [ACode, AWParam, ALParam]));
    end;
end;

procedure THookTest_Fo.Test_BtnClick(Sender: TObject);
begin
  ShowMessage('Boo!');
end;

destructor THookTest_Fo.Destroy;
begin
  UninitHook; // just to make sure
  inherited;
end;

procedure THookTest_Fo.Hook_BtnClick(Sender: TObject);
begin
  if Hook_Btn.Down then
    InitHook(HookCallback)
  else
    UninitHook;
end;

end.
Это было полезно?

Решение

Проблема в том, что ваша подключаемая DLL фактически загружается в несколько разных адресных пространств. Каждый раз, когда Windows обнаруживает событие в каком-то внешнем процессе, которое должно быть обработано вашей ловушкой, она загружает DLL-ловушку в этот процесс (если он, конечно, еще не загружен).

Однако каждый процесс имеет свое собственное адресное пространство. Это означает, что указатель на функцию обратного вызова, который вы передали в InitHook (), имеет смысл только в контексте вашего EXE-файла (поэтому он работает для событий в вашем приложении). В любом другом процессе этот указатель является мусором ; это может указывать на недопустимую область памяти или (что еще хуже) на какой-то случайный код. Результатом может быть либо нарушение прав доступа, либо повреждение памяти без вывода сообщений.

Как правило, решение состоит в том, чтобы использовать какой-то межпроцессный связь (IPC) для правильного уведомления вашего EXE Самый безболезненный способ для вашего случая - отправить сообщение и втиснуть нужную информацию (событие и HWND) в свой WPARAM / LPARAM. Вы можете использовать WM_APP + n или создать его с помощью RegisterWindowMessage (). Убедитесь, что сообщение отправлено и не отправлено, чтобы избежать тупиков.

Другие советы

Это может быть третичным для вашего вопроса, но, как вы видите, ловушки очень трудно понять - если вы можете избежать этого каким-либо образом, сделайте это. С ними вы столкнетесь с самыми разными проблемами, особенно в Vista, где вам придется иметь дело с UIPI.

Просто чтобы уточнить что-то, что " efotinis " упомянуто о публикации сообщений обратно в ваш процесс - wParam и lParam, которые вы публикуете в своем основном процессе, не могут быть указателями, они могут быть просто «числами».

Например, допустим, вы перехватили сообщение WM_WINDOWPOSCHANGING, Windows передает вам указатель на WINDOWPOS в lparam. Вы не можете просто отправить этот lparam обратно в ваш основной процесс, потому что память, на которую указывает lparam, действительна только в том процессе, который получает сообщение.

Это то, что " efotinis " имел в виду, когда он сказал " впишите необходимую информацию (событие и HWND) в свой WPARAM / LPARAM " ;. Если вы хотите передать более сложные сообщения обратно, вам потребуется использовать какой-либо другой IPC (например, именованные каналы, TCP или файлы с отображением в памяти).

Лол, похоже, ошибка в тестовом коде.

Если вы создадите две отдельные кнопки: одну для Init и одну для UnInit (я предпочитаю Выход).

procedure THooktest_FO.UnInitClick(Sender: TObject);
begin
  UninitHook;
end;

procedure THooktest_FO.InitClick(Sender: TObject);
begin
  InitHook(HookCallback)
end;

Запустите приложение.Нажмите «Init», а затем кнопку «Test», появится следующий результат:

created handle #1902442
destroyed handle #1902442
created handle #1967978
created handle #7276488

Затем отображается окно сообщения.

Если вы нажмете ОК, вы получите:

destroyed handle #1967978

ХТХ

Я нашел базовую документацию Delphi для SetWindowsHookEx.Но текст немного расплывчатый.

function SetWindowsHookEx(idHook: Integer; lpfn: TFNHookProc; 
  hmod: HInst; dwThreadId: DWORD): HHOOK;
  • хмод:Дескриптор модуля (DLL), содержащий функцию-перехватчик, на которую указывает параметр lpfn.Этот параметр должен быть установлен в ноль, если dwThreadId идентифицирует поток, созданный текущим процессом, а dlpfn указывает на функцию-перехватчик, расположенную в коде, связанном с текущим процессом.

  • dwThreadId:Идентификатор потока, с которым будет связана установленная функция-перехватчик.Если для этого параметра установлено значение 0, перехватчик будет общесистемным перехватчиком, связанным со всеми существующими потоками.

Кстати, для параметра hmod вы должны были использовать дескриптор модуля.(HINSTANCE указывает на дескриптор приложения).

hand := GetModuleHandle('hookhelper.dll');
Hook := SetWindowsHookEx(WH_SHELL, @HookProc, hand, 0);

Но хотя рука и отличается от HINSTANCE, она все равно показывает тот же результат.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top