Проблема с хуком
На Delphi пишу библиотеку, содержащую процедуры установки глобального хука WH_CBT и саму процедуру хука. Этот хук должен просто вести статистику нажатия кнопок мыши, клавы и переключения окон и записывать это в файл. Все работает более-менее хорошо, пока не начну закрывать приложение - хук не освобождается. (Забавный эффект - когда закрываю приложение через Диспетчер Задач, вместе с приложением вываливается и Explorer :) ). Я пытался реализовать это дело двумя способами:
1. Все процедуры работы с файлом статистики записать непосредственно в библиотеку, где находится хук. Но эти файлы не закрываются по требованию приложения, потому что не освобождается хук. И такая ситуация меня не устраивает. Если бы только как-нибудь научить хук освобождаться по первому требованию...
2. Процедуры работы с файлом находятся в приложении, а хук при возникновении событий посылает сообщение главному окну программы, которая все и записывает. НО почему-то при таком подходе хук не работает, то есть ничего не отлавливает!!! Кроме переноса процедур работы с файлом ничего не изменял...
Подскажите пожалуйста, в чем могут быть проблемы/ошибки у этих способов?
И еще вопрос... Во всех статьях установка хука выглядит примерно следующим образом:
Код:
hook := SetWindowsHookEx(WH_CBT, @CbtProc, HInstance, 0);
НО при таком подходе хук отлавливает только события текущего приложения. Если же записать так:
Код:
hook := SetWindowsHookEx(WH_CBT, CbtProc, HInstance, 0);
то работает правильно. Так что, получается во всех статьях ошибки?! Или я что-то делаю неправильно?
Заранее спасибо за ответы...
Кроме того, товарищ Iczelion советует использовать WH_CBN, а не WH_CBT хотя возможно это опечатка, хотя... http://www.netcode.ru/cpp/?artID=3116
Кстати, хоть я почти и не знаком с делфи, но правильнее кажется передавать процедуру с собачкой http://www.webstyleservice.ru/print.php?type=A&item_id=52
Цитата: ikro
И еще вопрос... Во всех статьях установка хука выглядит примерно следующим образом:
Код:
hook := SetWindowsHookEx(WH_CBT, @CbtProc, HInstance, 0);
НО при таком подходе хук отлавливает только события текущего приложения. Если же записать так:
Код:
hook := SetWindowsHookEx(WH_CBT, CbtProc, HInstance, 0);
то работает правильно. Так что, получается во всех статьях ошибки?! Или я что-то делаю неправильно?
Если CbtProc - это функция (а не переменная типа функция), то корректен первый вариант, а если это переменная типа TFnHookProc,- то второй вариант.
С установкой вроде я разобрался - сделал как написано в статье на исходниках.ru. Но возникла другая проблема, причем по-крупнее!
Ниже привожу код, а затем суть проблемы.
Код:
library ChHook;
uses
Windows,
Messages,
SysUtils,
UChConsts in '..\Server\UChConsts.pas',
UChLog in 'Misc\UChLog.pas';
var
MMFHandle: THandle;
WMMyPost: Cardinal;
function CbtFunc(
Code: Integer; WParam: WPARAM; LParam: LPARAM): LongInt; stdcall;
begin
if Code < 0 then begin
Result := CallNextHookEx(GlobalData^.SysHook, Code, WParam, LParam);
Exit;
end;
case Code of
HCBT_KEYSKIPPED:
PostMessage(GlobalData^.AppWnd, WMMyPost, 2, 0);
HCBT_CLICKSKIPPED:
if
(WParam = WM_LBUTTONDOWN) or (WParam = WM_RBUTTONDOWN) or
(WParam = WM_MBUTTONUP)
then
PostMessage(GlobalData^.AppWnd, WMMyPost, 3, 0)
else if WParam = WM_MOUSEWHEEL then
PostMessage(GlobalData^.AppWnd, WMMyPost, 6, 0);
HCBT_ACTIVATE:
if GetParent(WParam) = 0 then
PostMessage(GlobalData^.AppWnd, WMMyPost, 4, WParam);
end;
Result := CallNextHookEx(GlobalData^.SysHook, Code, WParam, LParam);
end;
procedure ManageHook(ASet: boolean; AWnd: HWND); export; stdcall;
begin
if ASet then begin
GlobalData^.SysHook := SetWindowsHookEx(WH_CBT, @CbtFunc, HInstance, 0);
GlobalData^.AppWnd := AWnd;
GlobalData^.WndMsg := RegisterWindowMessage('WM_MYPOST');
if (GlobalData^.SysHook = 0) then
DoLog('Hook: Unable to initialize hook');
end
else begin
UnhookWindowsHookEx(GlobalData^.SysHook);
GlobalData^.SysHook := 0;
end;
end;
procedure OpenGlobalData;
begin
MMFHandle :=
CreateFileMapping(
INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
SizeOf(TGlobalDLLData), MMF_NAME);
if (MMFHandle = 0) then
Exit;
GlobalData :=
MapViewOfFile(
MMFHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TGlobalDLLData));
if (GlobalData = nil) then begin
CloseHandle(MMFHandle);
Exit;
end;
end;
procedure CloseGlobalData;
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MMFHandle);
end;
procedure DLLEntryPoint(dwReason: DWORD); stdcall;
begin
case dwReason of
DLL_PROCESS_ATTACH: OpenGlobalData;
DLL_PROCESS_DETACH: CloseGlobalData;
end;
end;
{$R *.res}
exports
ManageHook;
begin
DLLProc:= @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
uses
Windows,
Messages,
SysUtils,
UChConsts in '..\Server\UChConsts.pas',
UChLog in 'Misc\UChLog.pas';
var
MMFHandle: THandle;
WMMyPost: Cardinal;
function CbtFunc(
Code: Integer; WParam: WPARAM; LParam: LPARAM): LongInt; stdcall;
begin
if Code < 0 then begin
Result := CallNextHookEx(GlobalData^.SysHook, Code, WParam, LParam);
Exit;
end;
case Code of
HCBT_KEYSKIPPED:
PostMessage(GlobalData^.AppWnd, WMMyPost, 2, 0);
HCBT_CLICKSKIPPED:
if
(WParam = WM_LBUTTONDOWN) or (WParam = WM_RBUTTONDOWN) or
(WParam = WM_MBUTTONUP)
then
PostMessage(GlobalData^.AppWnd, WMMyPost, 3, 0)
else if WParam = WM_MOUSEWHEEL then
PostMessage(GlobalData^.AppWnd, WMMyPost, 6, 0);
HCBT_ACTIVATE:
if GetParent(WParam) = 0 then
PostMessage(GlobalData^.AppWnd, WMMyPost, 4, WParam);
end;
Result := CallNextHookEx(GlobalData^.SysHook, Code, WParam, LParam);
end;
procedure ManageHook(ASet: boolean; AWnd: HWND); export; stdcall;
begin
if ASet then begin
GlobalData^.SysHook := SetWindowsHookEx(WH_CBT, @CbtFunc, HInstance, 0);
GlobalData^.AppWnd := AWnd;
GlobalData^.WndMsg := RegisterWindowMessage('WM_MYPOST');
if (GlobalData^.SysHook = 0) then
DoLog('Hook: Unable to initialize hook');
end
else begin
UnhookWindowsHookEx(GlobalData^.SysHook);
GlobalData^.SysHook := 0;
end;
end;
procedure OpenGlobalData;
begin
MMFHandle :=
CreateFileMapping(
INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
SizeOf(TGlobalDLLData), MMF_NAME);
if (MMFHandle = 0) then
Exit;
GlobalData :=
MapViewOfFile(
MMFHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TGlobalDLLData));
if (GlobalData = nil) then begin
CloseHandle(MMFHandle);
Exit;
end;
end;
procedure CloseGlobalData;
begin
UnmapViewOfFile(GlobalData);
CloseHandle(MMFHandle);
end;
procedure DLLEntryPoint(dwReason: DWORD); stdcall;
begin
case dwReason of
DLL_PROCESS_ATTACH: OpenGlobalData;
DLL_PROCESS_DETACH: CloseGlobalData;
end;
end;
{$R *.res}
exports
ManageHook;
begin
DLLProc:= @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
А проблема вот какая. В ходе работы вылетают некоторые приложения - в частности Explorer. А выглядит это так: сначала появляется ошибка Access violation в explorer.exe, а следом за ним EAccessViolation в хуке. Где именно это происходит - никак отловить не могу. Одна из ситуаций, когда это стабильно происходит - это свертывании окон сочетанием <Windows>+D. Три часа сегодня бился над этой ошибкой - так и не поборол. В чем может быть проблема?
P.S. Ответы на вопросы "зачем CreateFileMapping" и т.д. находятся в статье, про которую я упомянул в начале...
Все, вроде поборол. Жалко только, что новый вариант будет только под Windows 2000/XP работать :(
а больше и не надо, или тебе Vista нужна? скоро выйдет delphi 2007 с поддержкой висты, если не ошибаюсь......
Цитата: ahilles
а больше и не надо, или тебе Vista нужна? скоро выйдет delphi 2007 с поддержкой висты, если не ошибаюсь......
1. Не, мне не для Vistы хотелось бы, а для 98/ME.
2. Какая разница - Delphi 7 или 2007? Хуки пишутся средствами WinAPI, а не VCL, так что разницы в версии Delphi никакой ;) !
В висте поменяли некоторые АПИ. Так что, это как повезет. Может и заработает =).