Здравстуйте. У меня есть глобальный хук, но я столкнулся с такой проблемой: если мы запускаем хук, включаем какое-то приложение, набираем всякие символы, а потом закрываем приложение, то выскакивает ошибка инструкция по адресу "такому-то" обратилась к памяти по адресу "такому-то". Память не может быть "read".
Привожу код библиотеки:
library keyhook;
uses
SysUtils,
Windows,
Messages,
Forms;
const
MMFName: PChar = 'KeyMMF';
type
PGlobalDLLData = ^TGlobalDLLData;
TGlobalDLLData = packed record
SysHook: HWND;
MyAppWnd: HWND;
end;
var
GlobalData: PGlobalDLLData;
MMFHandle: THandle;
WM_MYKEYHOOK: Cardinal;
function KeyboardProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
var
AppWnd: HWND;
begin
if code < 0 then
begin
Result:= CallNextHookEx(GlobalData^.SysHook, Code, wParam, lParam);
Exit;
end;
if ( ((lParam and KF_UP)=0) and (wParam>=65) and (wParam<=90) ) OR ( ((lParam and KF_UP)=0) and (wParam=VK_SPACE) ) then
begin
AppWnd:= GetForegroundWindow();
SendMessage(GlobalData^.MyAppWnd, WM_MYKEYHOOK, wParam, AppWnd);
end;
procedure hook(switch : Boolean; hMainProg: HWND) export; stdcall;
begin
if switch=true then
begin
GlobalData^.SysHook := SetWindowsHookEx(WH_KEYBOARD, @KeyboardProc, HInstance, 0);
GlobalData^.MyAppWnd:= hMainProg;
if GlobalData^.SysHook <> 0 then
MessageBox(0, 'KEYBOARD HOOK установлен !', 'Message from keyhook.dll', 0)
else
MessageBox(0, 'HOOK установить не удалось !', 'Message from keyhook.dll', 0);
end
else
begin
if UnhookWindowsHookEx(GlobalData^.SysHook) then
MessageBox(0, 'HOOK снят !', 'Message from keyhook.dll', 0)
else
MessageBox(0, 'HOOK снять не удалось !', 'Message from keyhook.dll', 0);
end;
end;
procedure OpenGlobalData();
begin
WM_MYKEYHOOK:= RegisterWindowMessage('WM_MYKEYHOOK');
if MMFHandle = 0 then
begin
MessageBox(0, 'Can''t create FileMapping', 'Message from keyhook.dll', 0);
Exit;
end;
GlobalData:= MapViewOfFile(MMFHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TGlobalDLLData));
if GlobalData = nil then
begin
CloseHandle(MMFHandle);
MessageBox(0, 'Can''t make MapViewOfFile', 'Message from keyhook.dll', 0);
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;
exports hook;
begin
DLLProc:= @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
Уважаемые авторы вопросов! Большая просьба сообщить о результатах решения проблемы на этой странице. Иначе, следящие за обсуждением, возможно имеющие аналогичные проблемы, не получают ясного представления об их решении. А авторы ответов не получают обратной связи. Что можно расценивать, как проявление неуважения к отвечающим от автора вопроса.
28-11-2009 00:57 | Комментарий к предыдущим ответам
Мне сейчас лень копаться.
Проверьте, про при удалении ловушки (вызове Done) происходит Data^.Sign := 0;
27-11-2009 15:06 | Комментарий к предыдущим ответам
Уважаемый Александр Алексеев, вы могли бы дать пояснение? Возник вопрос. В вашем коде, в частности, написано
begin
// Здесь мы можем быть, только, если библиотека инициализируется дважды из разных приложений
Done;
SetLastError(ERROR_ALREADY_REGISTERED);
Result := False;
Exit;
end;
Т.е. получается, что библиотека выгружается и (при необходимости) ловушка снимается в случае если страница уже открыта. Тогда возникает эффект следующего рода:
1. Если программа запускается впервые, на "чистой" машине - все отлично.
2. Если Программа запускается, останавливается (Done отработал), и запускается повторно - возникает именно ERROR_ALREADY_REGISTERED.
3. Если повторный запуск происходит через какое-то время - все опять работает.
Видимо, даже после освобождения страницы она все еще какое-то время остается в системе. Вы могли бы как-то направить мою мысль на решение этого вопроса?
VK_HELP = 47;
{ VK_0 thru VK_9 are the same as ASCII '0' thru '9' ($30 - $39) }
{ VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' ($41 - $5A) }
{$EXTERNALSYM VK_LWIN}
VK_LWIN = 91;
(wParam>=65) and (wParam<=90)
65 - это Ord('A') = VK_A
90 - это Ord('Z') = VK_Z
Для фильтрации цифр используйте Ord('0') = VK_0 и Ord('9') = VK_9
Вы определяете нажатие кнопок на клавиатуре. Грубо говоря, никакого понятия "кнопка с русской буквой 'а'" для клавиатуры нет. Есть кнопка номер такой-то (VK_F). Всё остальное определяет ОС. Нужно руками определить раскладку, состояние спец. кнопок (Shift, Caps Lock) и перевести код клавиши в символ.
Я бы рассмотрел вариант с установкой другого типа ловушки, например на получение сообщения, и отлавливал бы WM_CHAR.
Нашлось время попробовать ваш код.
Действительно, вылетает AV. о_О
Не смог пока найти, в чём проблема.
Вот код, который использую я (переделан для вашего случая) - этот вариант работает:
library KeyHook;
uses
Windows, Messages;
type
// Информация о ловушке (hook-е), размещаемая в разделяемой памяти для доступа к ней библиотек во всех процессах
// Главное приложение создаёт эту область и записывает в неё параметры. Внедрённые библиотеки открывают и читают её.
PHookSharedData = ^THookSharedData;
THookSharedData = record
Sign: Cardinal; // Сигнатура, должна быть = Sign (см. ниже)
hHook: THandle; // Дескриптор ловушки, для вызова следующего hook-а в Win9x
Wnd: HWND;
end;
const
// Уникальное публичное имя ловушки, для открытия её по имени
MappingName = 'C0D7DD8D-2B00-4242-8DE5-F93FA1DC142C';
// Сигнатура ловушки, для проверки успешности установки ловушки
Sign = $2B746C41;
var
// Разделяемые данные ловушки
Data: PHookSharedData;
// Дескриптор разделяемых данных
hData: THandle;
WM_MYKEYHOOK: UINT;
// Завершает работу, при необходимости - снимает ловушку, экспортируется из библиотеки
procedure Done; stdcall;
var
Z: Integer;
H: HResult;
begin
// Сохраним GetLastError, чтоюы не запортить его внутри процедуры
H := GetLastError;
try
// Отключаем разделяемую память
if Data <> nil then
begin
// Можно писать в память?
if not IsBadWritePtr(Data, 8) then
begin // Да => мы находимся в главном приложении и должны отключить ловушку
// Читаем дескриптор ловушки, одновременно обнуляя его
Z := InterlockedExchange(Integer(Data^.hHook), 0);
// Снимаем ловушку, если она была
if Z <> 0 then
UnhookWindowsHookEx(Z);
// Стираем сигнатуру, т.к. ловушки уже нет
Data^.Sign := 0;
end;
// Отключить разделяемую память в любом случае
UnmapViewOfFile(Data);
Data := nil;
end;
// Закрываем объект разделяемой памяти
if hData <> 0 then
begin
CloseHandle(hData);
hData := 0;
end;
except
// Игнорируем все ошибки
end;
SetLastError(H);
end;
// Подпрограмма ловушки
function Hook(nCode: Integer; wParam, lParam: Integer): LRESULT; stdcall;
begin
try
// Если ловушка вызвана первый раз
if Data = nil then
begin
// Открываем объект разделяемой памяти по имени
hData := OpenFileMapping(FILE_MAP_READ, False, MappingName);
if hData <> 0 then
// Проецируем данные ловушки на наше адресное пространство
Data := MapViewOfFile(hData, FILE_MAP_READ, 0, 0, 0);
// Если данные инициализированы не были
if (Data = nil) or (Data^.Sign <> Sign) then
begin
// Ничего не делаем и выходим
Result := 0;
Exit;
end;
// Данные ловушки инициализированы и мы готовы к работе
end;
// Если наша очередь обработать сообщение
if nCode >= 0 then
begin
// Если мы должны обработать сообщение
if nCode = HC_ACTION then
begin
if ((lParam and KF_UP) = 0) and (((wParam >= 65) and (wParam <= 90)) or (wParam = VK_SPACE)) then
SendMessage(Data^.Wnd, WM_MYKEYHOOK, wParam, GetForegroundWindow);
end;
// Признак того, что мы обработали сообщение
Result:=0;
end
else
// Иначе сообщение обрабатывает следующий hook в очереди
Result := CallNextHookEx(Data^.hHook, nCode, wParam, lParam);
except
// При ошибках - игнорируем их и ничего не делаем
Result := 0;
end;
end;
// Начинает работу, устанавливает ловушку, экспортируется из библиотеки
function Init(const Wnd: HWND): Bool; stdcall;
begin
try
// Если инициализация уже была
if hData <> 0 then
begin
SetLastError(ERROR_ALREADY_INITIALIZED);
Result := False;
Exit;
end;
// Создаём объект разделяемой памяти
hData := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(THookSharedData), MappingName);
Result := hData <> 0;
if Result then
begin
// Проецируем данные ловушки на наше адресное пространство
Data := MapViewOfFile(hData, FILE_MAP_ALL_ACCESS, 0, 0, 0);
Result := Data <> nil;
if Result then
begin
// Проверка сигнатуры
if InterlockedExchange(Integer(Data^.Sign), Sign) = Sign then
begin
// Здесь мы можем быть, только, если библиотека инициализируется дважды из разных приложений
Done;
SetLastError(ERROR_ALREADY_REGISTERED);
Result := False;
Exit;
end;
// Ставим глобальную ловушку на вызов оконной процедуры
Data^.hHook := SetWindowsHookEx(WH_CALLWNDPROC, @Hook, HInstance, 0);
Result := Data^.hHook <> 0;
if not Result then
Done
else
Data^.Wnd := Wnd;
end
else
Done;
end;
except
// При ошибках - завершаем работу и вернём ошибку
Done;
SetLastError(ERROR_GEN_FAILURE);
Result := False;
end;
end;
// Секция экспорта
exports
Init, Done;
// Реализация initialization/finaliztion
var
// Старый DLLPRoc
OldDLLProc: TDLLProc;
// Наш DLLProc
procedure CustomDLLProc(Reason: Integer);
begin
try
// Если библиотека выгружается - завершим перед этим её работу
if Reason = DLL_PROCESS_DETACH then
Done;
// Также вызовем старый DLLProc
if Assigned (OldDLLProc) then
OldDLLProc (Reason);
except
end;
end;
// Стартовый код библиотеки
procedure InitDLL;
begin
try
// Подмена DLLProc
OldDLLProc:=DLLProc;
DLLProc:=@CustomDLLProc;
// Инициализация глобальных переменных
Data := nil;
hData := 0;
WM_MYKEYHOOK := RegisterWindowMessage('WM_MYKEYHOOK');
except
end;
end;
Как возникает ошибка: устанавливаю хук, запускаю программу, ввожу в ней символы, закрываю программу и тут и выскакивает ошибка. Причём до тех пор, пока я не снему хук.
>>>Ошибка возникает в момент закрытия программы.
Точнее. Вашей программы или любой другой типа Winamp?
Т.е. вы закрыли _свою_ программу и теперь при закрытии любой программы вываливается AV или же вообще после установки хука при закрытии любого приложения вываливается AV?
Попробуйте:
1. Обернуть все функции в try/except и выводить MessageBox с указанием места возникновения ошибки.
2. В KeyboardProc первым делом проверять if GlobalData = nil then begin Result := 0; Exit; end; Также в CloseGlobalData в конец вставить GlobalData := nil.
Если ошибка продолжит возникать, посмотрите, не изменился ли адрес, по которому программа не может получить доступ на чтение, до и после выполнения пункта 2. Ну, типа, если это обращение по nil, то должно стоять что-то типа "Read address 00000000", если чтение по недействительному указателю, отличному от nil, то "Read address XXYYZZHH".
Я снова исправил, но всё осталось по-старому. Может проблема не в библиотеки, а в приложении, которое вызывает библиотеку?
P.S. Стучал Вам в асю, но Вы отклонили.
Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Функция может не работать в некоторых версиях броузеров.