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

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

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

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

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

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

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

EDIT2: Хорошо, вот источник моего тестового проекта.

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.

Ответы [ 5 ]

9 голосов
/ 22 ноября 2008

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

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

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

2 голосов
/ 28 января 2009

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

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

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

2 голосов
/ 14 декабря 2008

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

0 голосов
/ 21 ноября 2008

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

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

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

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

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

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

0 голосов
/ 21 ноября 2008

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

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

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

НТН

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...