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

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

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

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

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

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

EDIT: Возможно, я должен был упомянуть об этом раньше: я загрузил и попробовал пару примеров (хотя, к сожалению, для Delphi не так много доступных, особенно для WH_SHELLor 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.

windows,delphi,winapi,hook,

12

Ответов: 5


9 принят

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

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

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


2

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


2

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

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

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


0

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

НТН


0

Я нашел документацию базы 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, она по-прежнему показывает тот же результат.

окна, Дельфы, WinAPI, крюк,
Похожие вопросы