Подтвердить что ты не робот

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

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

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

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

AFAICT, я сделал все по книге (очевидно, я этого не делал, но до сих пор я не вижу, где).

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

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

EDIT2: ОК, здесь источник моего тестового проекта.

DLL, содержащая процедуру hook:

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.

И основная форма приложения с помощью hook:

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.
4b9b3361

Ответ 1

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

Lol, похоже, что ошибка находится в тестовом коде.

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

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

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

Запустите приложение. Нажмите "Инициировать", а затем кнопку "Тест", появится следующий вывод:

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

Затем появится окно сообщения.

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

destroyed handle #1967978

НТН

Ответ 5

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

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

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

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

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

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