Delphi установка глобального хука на окна

Обновлено: 28.04.2024

function Mouse_SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
var H: HWND;
begin
if (Code=HC_ACTION) then
begin
H := FindWindow("TForm1", "XXX");

//тут проверяю факт нажатия правой клавиши мыши и Ctrl`а
if (wParam = WM_RBUTTONDOWN) AND (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
SendMessage(H, wm_RightMouse_Event, 0, 0);
end;

Result:= CallNextHookEx(Mouse_SysHook, Code, wParam, lParam);
end;

Этот код позволяет мне узнать что нажата правая клавиша мыши и Ctrl, но не позволяет предотвратить появление контекстного меню. Как мне это сделать?

← →
Leonid Troyanovsky © ( 2006-07-03 22:58 ) [1]


> leonidus © (03.07.06 21:30)

> if (wParam = WM_RBUTTONDOWN) AND (HiWord(GetAsyncKeyState>

Почему, собс-но, GetAsyncKeyState?
IMHO, этот вопрос уже задавался.
Ну, а про остальные вопросы можно и после.

WH_MOUSE_LL, этот ответ (LL), IMHO, уже давался.
Т.е., даже без dll.

> Леонид, а чем вам не нравится GetAsyncKeyState, чем его
> можно заменить? Разве в данном случае это вообще принципиально,
> ведь как раз определение факта нажатия Ctrl срабатывает
> нормально? И потом как вы предлагаете установить глобальный
> хук без dll? Простите что столько вопросов, я просто совсем

GetKeyboardState vs GetAsyncKeyState.
WM_MOUSE vs WM_MOUSE_LL

← →
Lamer@fools.ua © ( 2006-07-03 23:31 ) [5]

>function Mouse_SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;

[OFFTOPIC] Уже устал спрашивать: откуда многие этот бред берут?

← →
leonidus © ( 2006-07-04 08:54 ) [6]

Леонид, подскажите пожалуйста как с WM_MOUSE_LL работать? Искал на MSDN "WM_MOUSE_LL" но не нашел, Яндекс вообще молчит, поясните пожалуйста хотябы как вы видите решение этой проблемы.

← →
второе явление Чапаева народу ( 2006-07-04 09:57 ) [7]


> >function Mouse_SysMsgProc(code : integer; wParam : word;
> lParam : longint) : longint; stdcall;
>
> [OFFTOPIC] Уже устал спрашивать: откуда многие этот бред
> берут?

Из Win16, где WPARAM Word.

LL -- это "низкоуровневый" хук. работает по всей системе, не требует DLL. если упрощённо, то вызывается сразу после того, как драйвер сгенерировал событие, но до того, как это событие будет проинтерпретировано.

function tform1.Mouse_SysMsgProc(code : integer; wParam : word; lParam : longint) : longint;
var H: HWND;
begin
if (Code=HC_ACTION) then
begin
if wParam = WM_RBUTTONDOWN then form1.Label1.Caption:="wm_RightMouse_Event";
end;
Result:= CallNextHookEx(Mouse_SysHook, Code, wParam, lParam);
end;

procedure tform1.hook(switch : Boolean);
begin
if switch=true then
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
if Mouse_SysHook <> 0 then MessageBox(0, "Хук на мышь установлен", "", 0)
else MessageBox(0, "Хук на мышь установить не удалось", "", 0);
end
else
begin
if UnhookWindowsHookEx(Mouse_SysHook) then MessageBox(0, "Хук на мышь снят", "", 0)
else MessageBox(0, "Хук на мышь снят не удалось", "", 0);

Mouse_SysHook := 0;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
//установить хук
hook(true);
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
//снять хук при выходе из программы
hook(false);
end;

← →
leonidus © ( 2006-07-04 11:58 ) [13]

но на строчку: Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
компилятор ругается на WH_Mouse_LL, что делать?

← →
Игорь Шевченко © ( 2006-07-04 12:06 ) [14]


> WH_Mouse_LL

const
WH_Mouse_LL = 14;

← →
leonidus © ( 2006-07-04 13:07 ) [15]

Ни чего не понимаю, теперь в
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);

ругается на HInstance. может я уже туплю, но код скопировал прямо из dll`ки в которой все работало прекрасно

← →
Игорь Шевченко © ( 2006-07-04 13:37 ) [16]


> ругается на HInstance

Дорогой друг, кроме фразы "ругается" желательно еще приаттачить скриншот экрана твоего монитора в момент ругани, а так же до ругани и после ругани.

← →
leonidus © ( 2006-07-04 13:48 ) [17]

Компилятор выделяет строчку
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
ставит курсор на HInstance и пишет: "Variable required"

← →
Ketmar © ( 2006-07-04 13:51 ) [18]

кстати, я тут заметил: а может, просто надо "съёдать" событие, когда оно не должно происходить? т.е. возвращать "не ноль" и не вызывать следующий хук при помощи CallNextHookEx()? а то мы тут уже полезли в LL-хуки. %-)

← →
leonidus © ( 2006-07-04 14:07 ) [19]

я пробовал делать так:

if (wParam = WM_RBUTTONDOWN) and (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
begin
form1.Label1.Caption:="wm_RightMouse_Event";
end
else
Result:= CallNextHookEx(Mouse_SysHook, Code, wParam, lParam);

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

← →
Ketmar © ( 2006-07-04 14:22 ) [20]

странно. %-)

← →
leonidus © ( 2006-07-04 14:40 ) [21]

Я всеже не пойму почему на HInstance компилятор ругается, и в тоже время туже строчку в dll`ке компилирует нормально.

← →
Игорь Шевченко © ( 2006-07-04 17:26 ) [22]


> function tform1.Mouse_SysMsgProc(code : integer; wParam
> : word; lParam : longint) : longint;

Убрать TForm1 из объявления

← →
Игорь Шевченко © ( 2006-07-04 17:28 ) [23]

unit Unit1;

uses
Windows, Classes, Controls, Forms,
StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;

var
Form1: TForm1;

const
WH_Mouse_LL = 14;

var
Mouse_SysHook: HHOOK;

function Mouse_SysMsgProc(code: integer;
wParam: longint; lParam : longint):longint; stdcall;
begin
Result := CallNextHookEx(Mouse_SysHook, Code, wParam, LParam);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
end;

Не ругается

← →
Gero © ( 2006-07-04 18:04 ) [24]

Только стоит принять во внимание, что WH_MOUSE_LL отсутствует в Windows 9x/Me.

← →
Leonid Troyanovsky © ( 2006-07-04 18:30 ) [25]


> второе явление Чапаева народу (04.07.06 09:57) [7]

Не, просто, описАлся, чисто машинально.

--
Regards, LVT.

← →
leonidus © ( 2006-07-04 20:07 ) [26]

Игорь, большое спасибо. Теперь вроде разобрался и вернулся к первому вопросу, как в случае если нажата "горячая клавиша" - скажем Ctrl, и произведен клик правой клавишей мыши не допустить появления контекстного меню в каком-либо приложении?

const
WH_Mouse_LL = 14;

var
Form1: TForm1;
Mouse_SysHook: HHOOK;

function Mouse_SysMsgProc(code: integer;
wParam: longint; lParam : longint):longint; stdcall;
begin
if (Code=HC_ACTION) then
if (wParam = WM_RBUTTONDOWN) and (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
begin
form1.Label1.Caption:="wm_RightMouse_Event";
result:=1;
end
else
begin
form1.Label1.Caption:="";
Result := CallNextHookEx(Mouse_SysHook, Code, wParam, LParam);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
UnhookWindowsHookEx(Mouse_SysHook);
end;

но после определения того что нажат Ctrl и правая клавиша мыши, контекстное меню всеравно благополучно появляется. Что я делаю не так?

← →
leonidus © ( 2006-07-05 15:47 ) [27]

Господа подскажите пожалуйста

← →
guav © ( 2006-07-05 18:15 ) [28]

к. меню появляется при отпускании мыни

← →
leonidus © ( 2006-07-05 22:12 ) [29]

блин точно, сейчас попробую WM_RBUTTONUP

← →
leonidus © ( 2006-07-06 07:58 ) [30]

В общем вот последняя редакция кода:

const
WH_Mouse_LL = 14;

var
Form1: TForm1;
Mouse_SysHook: HHOOK;

function Mouse_SysMsgProc(code: integer;
wParam: longint; lParam : longint):longint; stdcall;
begin
if (Code=HC_ACTION) then
if (wParam = WM_RBUTTONUP) and (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
begin
form1.Label1.Caption:="wm_RightMouse_Event";
result:=-1;
end
else
begin
form1.Label1.Caption:="";
Result := CallNextHookEx(Mouse_SysHook, Code, wParam, LParam);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if UnhookWindowsHookEx(Mouse_SysHook) then MessageBox(0, "Хук на мышь снят", "", 0)
end;

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

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

Сразу хочу сделать несколько оговорок: речь в дальнейшем пойдёт только о 32-х разрядной Windows и о глобальных ловушках, т.к. именно при их программировании возникает большинство ошибок; все примеры будут даваться на Delphi, т.к. примеров и описаний для любителей С++ достаточно.

Давайте сначала разберёмся почему, иногда, даже опытные программисты допускают ошибки при написании глобальных ловушек. Первая, и самая распространённая причина: многие программисты, перейдя от 16-ти разрядной к 32-х разрядной Windows, порой забывают об изолированности адресных пространств процессов, такая забывчивость прощается при написании локальных ловушек, в случае с глобальными она может стать фатальной (подробнее об этом рассказано дальше в статье). Второй причиной является то, что в SDK (да и в MSDN тоже) даётся недостаточно информации по данной тематике, а та что есть часто трактуется неверно. Третья причина… хотя, думаю, стоит остановиться пока на этом.

Дальнейшее повествование предполагает, что читатель знаком с основными принципами работы с DLL и хотя бы в общих чертах представляет механизм их написания.

Что же происходит в системе когда мы "ставим" ловушку и что это вообще такое - ловушка ?

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

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

В зависимости от типа ловушки функции-фильтры могут изменять события, отменять их или просто реагировать на них. Таким образом, когда мы говорим "установил ловушку" мы подразумеваем процесс прикрепления функции-фильтра к выбранному нами типу ловушки. Итак, когда мы в своей программе используем функцию SetWindowsHookEx мы прикрепляем функцию-фильтр, указатель на которую мы и передаём вторым параметром, пример:

SetWindowsHookEx(WH_SHELL, @ShellHook, HInstance, 0 );

в данном случае ShellHook - это и есть функция-фильтр. В дальнейшем, под словосочетанием "установили ловушку" будем понимать присоединение функции-фильтра к ловушке.

Что же происходит после того, как мы установили глобальную ловушку ? Понимание следующего параграфа является ключом для понимания механизма работы ловушек Windows, располагающихся в DLL. Если вы не поймёте его, вернитесь и перечитайте заново и так до тех пор, пока всё не станет ясным.

clip0019

Наш Process1 устанавливает глобальную ловушку из DLL находящейся в адресном пространстве (АП) нашего процесса (Process1). DLL, находящаяся в АП процесса1 имеет свои данные, обозначенные на рисунке как Dll data. Когда система посылает событие, на которое мы установили ловушку, в Process2, то в Process2 отображается код DLL, находящийся в первом процессе (Dll code), НО НЕ ДАННЫЕ ! Все данные, только что отображённой в Process2 DLL, инициализируются заново (т.е. равны 0, nil, False в зависимости от типа). То есть, Process2 знать не знает о существовании Process1, и всё что в нём находится никак не относится к АП первого процесса, из которого произошло отображение кода DLL. В библиотеки, находящиеся не в АП вашего процесса, можно посылать только процессо-независимые данные, такие как, к примеру, дескрипторы окон (под термином "посылка" в данном случае подразумевается использование функций PostMessage() и SendMessage()). (О смысле красных овалов на рисунке поговорим позже, сейчас не стоит обращать на них внимания).

clip0020

Как мы уже знаем, ловушка устанавливается с помощью Win32 API функции

function SetWindowsHookEx(idHook: integer; lpfn: TFNHookProc; hmod: HINST; dwThreadID: DWORD): HHOOK; stdcall ;

idHook : описывает тип устанавливаемой ловушки. Данный параметр может принимать одно из следующих значений:

Что бы упредить шквал писем в мой адрес, скажу сразу, что каждый, из вышеперечисленных, типов имеет свои особенности, о которых каждый может прочитать в SDK, MSDN или же найти их описание в Internet-e.

lpfn : это адрес функции-фильтра, которая является функцией обратного вызова. Функция-фильтр имеет тип TFNHookProc, определение которого выглядит следующим образом:

TFNHookProc = function (code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT stdcall;

Значение каждого из параметров функции-фильтра ловушки изменяется в зависимости от типа устанавливаемой ловушки. За более подробными разъяснениями значений параметров обращайтесь к справке по Win32 API.

hmod : данный параметр должен иметь значение hInstance в EXE или DLL-файлах, в которых содержится функция-фильтр ловушки (напомню, что это функция обратного вызова). Если речь идёт о глобальных ловушках, то данный параметр может принимать только дескриптор DLL, из которой устанавливается ловушка. Причина очевидна - EXE-файл не может быть отображён на АП другого процесса, тогда как DLL-фалы специально созданы для этого. Подчеркну это обстоятельство ещё раз: глобальные ловушки могут располагаться только в DLL, но никак не в EXE файлах !

dwThreadID : данный параметр идентифицирует поток, с которым будет связана ловушка. Мы ведём речь о глобальных ловушках, поэтому данный параметр будет всегда равен 0, что означает, что ловушка будет связана со всеми потоками в системе.

Возвращаемое значение: функция SetWindowsHookEx возвращает дескриптор установленной ловушки, именно этот дескриптор нам и надо будет сделать доступным ВСЕМ экземплярам отображаемой DLL. Как это сделать я расскажу после небольшого примера, показывающего на практике необходимость сохранять дескриптор ловушки для того, что бы суметь вызвать предыдущую ловушку в цепочке.

Замечание : при установке двух ловушек разного типа, система создаст две цепочки ловушек. Т.е. каждому типу ловушки соответствует своя цепочка. Так при установке ловушки типа WH_MOUSE и WH_KEYBOARD обе эти ловушки будут находиться в разных цепочках и, соответственно, будут обрабатываться независимо друг от друга.

Для удаления функции-фильтра из очереди необходимо вызвать функцию UnhookWindowsHookEx . Данная функция принимает дескриптор ловушки, полученный функцией SetWindowsHookEx . Если удаление не удалось, то функция возвращает ноль, иначе не нулевое значение. В дальнейшем, под выражением "снять ловушку" будем подразумевать удаление функции-фильтра.

Теперь, когда вам известно как устанавливать ловушку и как её снимать, рассмотрим пару примеров, которые дадут наглядное представление об изолированности АП процессов и укажут на одну из самых распространённых ошибок.

Теперь поговорим о том, как избежать неприятных ситуаций, используя глобальные ловушки.

Для того, что бы все экземпляры DLL, находящиеся в разных процессах, имели доступ к дескриптору ловушки, надо выделить какую-то область, доступ к которой будут иметь все "желающие". Для этого воспользуемся одним из мощнейших механизмов Windows под названием "Файлы, отображённые в память" (Memory Mapped Files). В цели данной статьи не входит углубление в подробности работы с данным механизмом, так что если он кого-то заинтересует всерьёз - рекомендую почитать о нём в литературе, общие же понятия я постараюсь вкратце осветить. Механизм файлов, отображённых в память (MMF - Memory Mapped Files) позволяет резервировать определённую область АП системы Windows, для которой назначаются страницы физической памяти. Таким образом, с помощью MMF можно отображать в память не только файлы, но и данные, ссылаясь на них из своих программ с помощью указателей. В первом приближении работу механизма MMF можно представить следующим образом: Process1 создаёт отображение, которое связывает с некими данными (будь то файл на диске или значение неких переменных в самом Process1) и может изменять отображённые данные; затем Process2 так же отображает некие свои данные в тоже отображение, что и Process1, таким образом, изменения, внесённые Process1 в отображённые данные, будут видны Process2 и наоборот (см. рис.1 - красный овал c именем Global Data и есть зарезервированное под совместные нужды двух процессов АП). Данное приближение, вообще говоря, грубое, потому что всё намного сложнее, но для наших "нужд" этого будет вполне достаточно. Мы не будем создавать никаких временных файлов для передачи информации между процессами, мы воспользуемся файлом подкачки Windows (файл страничного обмена), таким образом, нам не придётся ни создавать ни уничтожать файлы, а придётся просто создать некоторое АП, которое будет доступно нашим приложениям и которое будет автоматически освобождаться системой, когда в нём отпадёт необходимость. К тому же, ясно, что работа с файлом подкачки куда быстрее, чем с обычным файлом, хранящимся на диске. Таким образом, к рассмотренному вами ранее Example1 можно применить следующий сценарий: при загрузки вашей программой (MainProg1.exe) библиотеки hook_dll1.dll эта библиотека создаёт отображённый в память файл, в котором сохраняет значение дескриптора установленной ловушки; затем некий процесс, в котором произошло событие, на которое была установлена ловушка, отображает на своё АП код hook_dll1.dll и уже новый экземпляр hook_dll1.dll, находящийся в АП другого процесса использует то же отображение, что и библиотека, из который была установлена ловушка, т.е. будет иметь доступ к сохранённому значению дескриптора установленной ловушки. Таким образом, вызов функции CallNextHookEx(Hook_Handle, Code, wParam, lParam); будет происходить вполне корректно, т.к. значение Hook_Handle будет содержать не 0, как в примере1, а значение, возвращённое функцией SetWindowsHookEx из первого экземпляра DLL. Возможно, данные объяснения кажутся вам запутанными, но после просмотра примера и повторного прочтения этих объяснений всё встанет на свои места.

Теперь пару слов о программной реализации всего вышесказанного

CreateFileMapping - Создаёт объект файлового отображения. Данная функция возвращает указатель (handle) на объект файлового отображения.

MapViewOfFile - Данная функция отображает образ объекта файлового отображения на АП процесса, из которого она была вызвана. Первым параметром данной функции является результат выполнения функции CreateFileMapping(). Результатом работы данной функции является указатель на начало выделенного АП (уже в том процессе, из которого была вызвана данная функция). См. рис.1. - красные овалы в Process1 и Process2 под названием GD1 и GD2 (Global Data 1/2). Следует отметить, что для различных процессов, использующих экземпляры одной и той же DLL, адреса выделенных областей будут различными (хотя могут и совпадать, но это совпадение носит вероятностный характер), хотя данные, на которые они будут ссылаться, одни и те же !

UnmapViewOfFile - Данная функция закрывает отображённый в память файл и освобождает его дескриптор. При удачном закрытие функция возвращает ненулевое значение и 0 в случае неудачи.

За подробной информацией о параметрах вышеописанных функций обращайтесь к SDK, а так же разберитесь в примере, который будет разобран ниже.

Замечание : первым параметром функции CreateFileMapping() должен быть передан дескриптор файла, которого мы собираемся отобразить. Т.к. мы собираемся отображать данные в файл подкачки, то следует передавать значение $FFFFFFFF или DWORD(-1), что соответствует тому же значению; но т.к. грядёт эра 64-разрядных систем, стоит использовать значение INVALID_HANDLE_VALUE, которое будет в 64 разрядной системе равно $FFFFFFFFFFFFFFFF соответственно. Для тех, кто переходил с ранних версий Delphi на более поздние (к примеру с Delphi2 на Delphi4) те, возможно, сталкивались с такого рода проблемами в своих программах.

Так как мы будем создавать именованный объект файлового отображения, то последним параметром функции CreateFileMapping() передадим имя объекта, которое впоследствии будут использовать другие процессы для ссылки на ту же область памяти. Следует упомянуть о том, что создаваемый таким образом объект должен иметь фиксированный размер, т.е. не может его изменять по ходу программы.

Теперь мы владеем всеми необходимыми знаниями для рассмотрения второго примера. Откройте каталог Example2 и выполните те же действия, что и в первом примере, предварительно внимательно разобравшись в исходных кодах. После того как вы запустите оба приложения и установите из них две функции-фильтра одного типа, попробуйте кликнуть правой кнопкой мыши на любом из окон и вы увидите, что теперь отрабатывают обе установленные ловушки, независимо от того, на каком из окон произошло нажатие кнопки мыши (т.е. несмотря на то, из какого экземпляра DLL выполняется вызов функции CallNextHookEx() ). Таким образом, когда какое-либо приложение будет отображать на своё АП DLL, в которой находится функция-фильтр, этот экземпляр DLL будет иметь доступ к данным, отображённым в память из Process1 или Process2, в зависимости от DLL. Думаю, после столь подробных объяснений всё должно быть понятно.

В завершении напишем программу, которая будет устанавливать ловушку типа WH_KEYBOARD и записывать в файл значения нажатых клавиш во всех приложениях (программа будет накапливать в буфере значения нажатых клавиш и как только их количество превысит 40 - все значения будут выведены в соответствующее окно формы). Попутно, в данном примере, новички могут найти ответы на многие вопросы, часто задаваемые в различных форумах. Все объяснения будут даваться в виде комментариев к исходному коду. Откройте каталог Example3, в нём вы найдёте исходные коды библиотеки и главной программы, - разберитесь с ними, а затем откомпилируйте и сами попробуйте программу в действии.

Благодарю Юрия Зотова за оказанную поддержку.

Список использованной литературы:

Microsoft Win32 Software Development Kit.

Стив Тейксейра и Ксавье Пачеко, "Delphi5. Руководство разработчика. Том 1. Основные методы и технологии".

Рано или поздно каждый программист сталкивается с таким понятим как ловушки. Чтобы приступить к ипользованию ловушек необходимо обзавестись windows SDK, который можно так же скачать с сайта Microsoft. В прилагаемом к статье архиве содержатся два проекта: hooks.dpr - это пример приложения работающего с ловушками, а hookdll.dpr - собственно сама DLL.

Что такое ловушки (Hooks)?

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

Итак, существует два типа ловушек - глобальные и локальные. Локальная ловушка отслеживает только те события, которые происходят только в одной программе (или потоке). Глобальная ловушка отслеживает события во всей системе (во всех потоках). Оба типа ловушек устанавливаются одинаково, однако единственно отличие заключается в том, что локальная ловушка вызывается в пределах Вашего приложения, в то время как глобальную ловушку необходимо хранить и вызывать из отдельной DLL.

Далее следует краткое описание каждой процедуры и структуры, необходимой для ловушки.

функция The SetWindowsHookEx

Функция SetWindowsHookEx необходима для установки ловушки. Давайте посмотрим на аргументы данной функции:

Name Type Description

idHook Integer Число, представляющее тип ловушки - например WH_KEYBOARD

lpfn TFNHookProc Адрес в памяти функции ловушки

hMod Hinst Дескриптор dll в которой находится функция. Если это локальная ловушка, то этот параметр 0.

dwThreadID Cardinal 'id потока', который Ваша программа будет контролировать. Если это глобальная ловушка, то параметр должен быть 0.

SetWindowsHookEx возвращает дескриптор (т.е. идентификатор) текущей ловушки, который можно использовать в функции UnhookWindowsHookEx для последующего удаления ловушки.

Функция hook это процедура, которая вызывает в случае, если необходимое нам событие происходит. Например, если установлена ловушка типа WH_KEYBOARD, то окно будет передавать в ловушку информацию о том, какая клавища была нажата. Для Вашей процедуры hook необходимы следующие аргументы:

Name Type Description

Code Integer Указывает на то, что означают следующие два параметра

wParam word Параметр размером в 1 слово (word)

lParam longword Параметр размером в 2 слова

Функция hook возвращает значение типа longword.

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

Данная функция просто напросто удаляет Вашу ловушку. Единственный аргумент этой функции - это дескриптор ловушки, возвращаемы функцией SetWindowsHookEx.

Сперва давайте создадим локальную ловушку. Необходимый для неё код содержится в 'local.pas'. При запуске Hooks.exe будет отображена небольшая форма. Для использования локальной ловушки достаточно нажать кнопку Add/Remove Local Hook на этой форме. После установки локальной ловушки, Вы заметите, что при нажатии и отпускании любой клавиши будет раздаваться звуковой сигнал (естевственно, когда hooks.exe будет иметь фокус. Ведь это локальная ловушка).

Самая первая функция в local.pas - SetupLocalHook, которая соственно и создаёт локальную ловушку, указывая на процедуру ловушки KeyboardHook. В данном случае это простой вызов SetWindowsHookEx, и, если возвращённый дескриптор > 0, указывающий на то, что процедура работает, то сохраняет этот дескриптор в CurrentHook и возвращает true, иначе будет возвращено значение false. Далее идёт функция RemoveLocalHook, которая получает в качестве параметра сохранённый дескриптор в CurrentHook и использует его в UnhookWindowsHookEx для удаления ловушки. Последняя идёт процедура hook, которая всего навсего проверяет - была ли отпущена клавиша и если надо, то выдаёт звуковой сигнал.

Глобальная ловушка выглядит немного сложнее. Для создания глобальной ловушки нам понадобится два проекта - певый для создания исполняемого файла и второй для создания DLL, содержащей процедуру ловушки. Глобальная ловушка, которая представлена в примере, сохраняет в файле log.txt каждые 20 нажатий клавиш. Чтобы использовать глобальную ловушку, достаточно на форме hook.exe нажать кнопку add/remove global hook. Затем, например, в записной книжке (notepad) достаточно набрать какой-нибудь текст, и Вы увидите, что в log.txt этот текст сохранится.

Наша Dll содержит две процедуры. Первая - это процедура hook, которая идентична для той, которую мы рассмотрели для локальной ловушки. Вторая процедура необходима инициализации dlls, и содержит текущий номер клавиши, которая была нажата, а также дескриптор ловушки, которая была создана.

Исполняемый файл сперва должен загрузить процедуры из DLL, а затем использовать SetWindowsHookEx, чтобы создать глобальную ловушку.

Представленный пример объясняет - как перехватывать события клавиатуры. Чтобы узнать, как использовать ловушки других типов, таких как WH_MOUSE, необходимо разобраться с windows SDK.

Хуки предоставляют мощные возможности для приложений Windows. Приложения могут использовать хуки в следующих целях:

Начнём сначала, для установки хука успользуется функция SetWindowsHookEx

Первый параметр это числовая константа WH_* которая задаёт тип устанавливаемого хука. Второй параметр это адрес функции-фильтра. Третий параметр это хэндл модуля, содержащего фильтрующую функцию. Этот параметр должен быть равен нулю при установке хука на поток, но данное требование не является строго обязательным, как указано в документации. При установке хука для всей системы или для потока в другом процессе, нужно использовать хэндл DLL, содержащей функцию-фильтр. Четвёртый параметр это идентификатор потока, для которого устанавливается хук. Если он не равен нулю, то хук устанавливается только на указанный поток. Если идентификатор равен нулю, то хук устанавливается на всю систему. Некоторые хуки можно ставить как на всю систему, так и на некоторый поток, некоторые хуки можно поставить только на всю систему. Функция возвращает хендл хука, в случае неудачи функция возвратит ноль. Для снятия хука нужно использовать функцию UnhookWindowsHookEx, которая принимает в качестве единственного параметра хендл установленного хука.

Едем, далее. Все фильтрующие функции должны быть описаны следующим образом:

Для вызова следующей функции в очереди хуков предназначена функция CallNextHookEx

Итак, основные сведения о хуках мы получили. Теперь надо приступить к практике. Наиболее часто встречающийся проблемы поднимаемые на форумах программистов, связанный с хуками, это проблемы с написанием клавиатурных шпионов. Именно клавиатурный шпион мы сейчас и напишем.

Клавиатурный шпион

Параметр Code может быть равен следующим значениям:

Создание и снятие хука, я думаю, особых проблем не составляет, поэтому сразу приступлю к самому обработчику хука.

Основная проблема в при написании клавиатурных хуков заключается в том что обработчику хука передаётся только скан код нажатой клавиши и её виртуальный код. Виртуальный код и скан код говорят нам, какая именно клавиша была нажата, но не говорят, что именно было введено. Поясню, даже если мы вводим русский текст, то клавиатурному хуку будут передаваться коды английских клавиш, т.е. мы вводим слово «привет», а обработчику хука будет передано «GHBDTN». Или, например, мы нажимаем на Shift цифру 7 и вводится знак &, но в клавиатурный хук будт передан только код цифры 7. Для того чтобы преобразовать скан код и виртуальный код в текстовый символ, который был введён, необходимо использовать функцию ToAscii (или ToUnicode). Её параметры:

Первый параметр это виртуальный код, второй это скан код, третий параметр это указатель на массив в котором сохранено состояние клавиатуры, четвёртый это указатель на переменную, в которую будет сохранён символ, пятый параметр это флаг, определяющий, является ли меню активным. Этот параметр должен быть 1, если меню активно, или иначе 0. Функция возвращает количество символов, полученных в результате преобразования. Состояние клавиатуры можно получить через функцию GetKeyboardState.

Вернёмся в нашей фильтрующей функции.

Для получения текстовой расшифровки нажатой клавиши по её скан коду мы воспользовались функцией GetKeyNameText. Полный текст DLL и приложения находится в архиве прилагающемуся к этой статье.

Если посмотреть получившийся лог, то мы увидим следующий текст в формате .

Вот и подошёл конец первой статьи про хуки. Качаем, смотрим исходник исследуем, учимся.

Работа с окнами

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

Итак, начнём сначала. Для создания хука для мониторинга событий окон надо указать тип хука WH_CBT в первом параметре функции SetWindowsHookEx. Хук типа WH_CBT позволяет отслеживать следующие события окон: создание, уничтожение, активация, установку фокуса, минимизация, максимизация и прочее.

Формат обработчика хука такой же, какой и у других типов хуков

Назначение параметров wParam и lParam полностью зависит от типа события. Обработчик хука всегда вызывается до осуществления события. Если обработчик хука не вызовет следующий обработчик хука (функция CallNextHookEx), то перехватываемое действие не произойдёт, таким образом можно блокировать некоторые действия. Но тем не менее, отменять события в хуках такого типа не рекомендуется, так как это будет очень неожиданно для приложения. Представьте себе ситуацию, когда программа хочет уничтожить окно, а у него не получается, или же хочет создать окно, но не получается, намного корректнее было бы уничтожить окно после его создания (к примеру, через 10 мс). Далее приведены наиболее часто используемые типы событий.

Если код события равен HCBT_ACTIVATE, то произошло событие активации окна. В данном случае параметр wParam содержит хендл искомого окна, а параметр lParam будет указывать на структуру CBTACTIVATESTRUCT. Далее приведено описание этой структуры:

Если событие произошло вследствие клика мыши, то поле fMouse будет равно TRUE. Поле hWndActive содержит хендл окна, активного в данный момент.

При коде события HCBT_CREATEWND параметр wParam содержит хендл нового окна, а lParam указывает на структуру CBT_CREATEWND

Поле hwndInsertAfter содержит хендл окна, которое по Z координате находится сразу же за вновь создаваемым. Изменив этот хендл можно изменить Z координату вновь создаваемого окна. Поле lpcs указывает на структуру CREATESTRUCT, она имеет следующий формат:

Я думаю здесь всё понятно.

При коде события HCBT_DESTROYWND wParam содержит хендл уничтожаемого окна, lParam ничего не содержит. Как было уже сказано, функция обработчик вызывается до осуществления события, а следовательно когда мы в обработчике окно ещё существует и можно получить параметр уничтожаемого окна.

Помимо указанных кодов событий ещё есть следующие:

Начнём сначала. Принцип файлового мэпинга является одним из основополагающих принципов работы с виртуальной памятью в Windows. С помощью техники файлового мэпинга можно создать область памяти, которая при нехватке физической памяти будет сбрасываться не в файл подкачки, а в какой-нибудь указанный нами файл. Таким образом, при изменении памяти, выделенной и спроецированной с помощью механизма мэпинга, содержимое файла тоже будет изменено (разумеется, не сразу). Чтобы создать файл-мэппинг объект надо использовать функцию CreateFileMapping. Её формат:

Первый параметр это хендл файла, который будет использован как файл подкачки для этой области памяти. Если хендл файла равен значению INVALID_HANDLE_VALUE, то выделенная область памяти при необходимости будет сбрасываться в файл подкачки (как и любая другая область памяти). Второй параметр это атрибуты защиты. Третий параметр задаёт параметры доступа к выделенной памяти: PAGE_READONLY - только чтение, файл в этом случае должен быть открыт как минимум с флагом GENERIC_READ; PAGE_READWRITE – чтение и запись, файл должен быть открыт как минимум с флагами GENERIC_READ и GENERIC_WRITE; PAGE_WRITECOPY – тоже самое, что и с предыдущим флагом, но все выделенные страницы помечаются как копируемые при записи. В этом случае изменения в выделенной памяти не будут отражаться на искомом файле, и в случае необходимости область памяти будет сбрасываться в файл подкачки. В общем, не будем слишком сильно заморачиваться этим флагом, лучше всего использовать флаг PAGE_READWRITE. Третий и четвёртый параметры задают максимальный размер создаваемого объекта, соответственно старшую и младшую часть. Последний параметр задаёт имя создаваемого объекта, через которое смогут обратиться к нему другие процессы.

Для открытия имеющего файл-мэпинг объекта по имени существует функция OpenFileMapping.

Первый параметр задаёт тип доступа к объекту, может принимать следующие значения: FILE_MAP_WRITE – чтение и запись, объект должен быть создан с атрибутом PAGE_READWRITE; FILE_MAP_READ – только чтение, объект должен быть создан к минимум с атрибутом PAGE_READONLY; FILE_MAP_ALL_ACCESS- тоже самое, что и FILE_MAP_WRITE; FILE_MAP_COPY – копирование при записи, объект должен быть создан с атрибутом PAGE_WRITECOPY. Второй параметр это флаг наследования. Третий параметр задаёт имя отрываемого файл-мэпинг объекта.

Для проецирования файл-мэпинг объекта на память используется функция MapViewOfFile. Её описание:

Первый параметр это хендл файл-мэпинг объекта. Второй параметр задаёт атрибуты доступа, требования полностью идентичны требованиям первого параметра для функции OpenFileMapping. Третий и четвёртый параметры задают начальное смещение в файле, с которого начнётся проецирование на память, соответственно старшая и младшая часть смещения. Последний параметр задаёт количество байт для проецирования на память. Функция в случае успеха возвращает указатель на выделенную память.

Для освобождения выделенной памяти и сохранения изменений в искомый файл (если это нужно было) надо вызвать функцию UnmapViewOfFile, она принимает единственный параметр, это начальный адрес, куда был спроецирован объект.

Итак, у нас имеется общая для всех область памяти. Мы в неё можем записывать наш лог, в конце мониторинга нам надо будет сбросить эту память в некоторый файл. Возникает вопрос: как нам узнать в какое место буфера писать. Для этого мы в первых четырёх байтах буфера будем держать переменную, в которой будет храниться текущее смещение, куда надо писать новые данные. Перед записью мы получаем смещение, записываем по этому смещению новые данные и увеличиваем нашу переменную на размер записанных данных.

Итак, общий алгоритм известен, но возникает новая проблема. Так как процессов и окон много, возникает проблема синхронизации записи в буфер. А именно надо сделать так, чтобы записывать в лог в некоторый момент времени мог только один поток, иначе результаты будут непредсказуемыми. Эксклюзивного доступа к общим данным можно добиться, используя критические секции, но их можно использовать только для синхронизации потоков в одном процессе. Заменой критических секций в «межпроцессорном масштабе» являются объекты взаимоисключения – мьютексы. (конечно же, есть и другие варианты, но этот вариант наиболее простой).

Мьютексы могут находиться в двух состояниях в захваченном и свободном. Также мьютексы, как и любые другие объекты в Windows, могут находиться в двух состояниях: сигнальном и несигнальном состоянии. Когда мьютекс захвачен каким-либо потоком, он находится в несигнальном состоянии, когда мьютекс свободен, он находится в сигнальном состоянии.

Для создания мьютекса надо вызвать функцию CreateMutex, её заголовок:

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

Чтобы открыть существующий мьютекс необходимо использовать функцию OpenMutex.

Первый параметр задаёт флаги доступа к мьютексу, второй параметр задаёт флаг наследования, третий имя мьютекса. Первый параметр может принимать следующие значения: MUTEX_ALL_ACCESS - полный доступ, SYNCHRONIZE – только для синхронизации. (впрочем, так и непонятно чем они друг от друга отличаются)

Если хендл мьютекса передан какой-либо ждущей функции (например, WaitForSingleObject), то эта функция проверяет его состояние, если он свободен (в сигнальном состоянии), то помечает его как занятый (переводит его в несигнальном состоянии) и возвращает управление. Если мьютекс находится в занятом состоянии (несигнальном), то она ждет, когда он перейдёт в свободное (сигнальное) состояние, либо ждёт окончания указанного интервала и только потом возвращает управление. Для освобождения мьютекса необходимо вызывать функцию ReleaseMutex, передав ей единственный параметр – хендл мьютекса.

Допустим у нас есть некоторый код который работает с общими данными и необходим эксклюзивный доступ к ним, шаблон кода будет таким:

Итак, все знания необходимые для написания монитора окон мы получили, настало время написать программу для мониторинга окон. Сначала приведу код DLL который устанавливает и снимает хук:

Проблем с этим кодом быть не должно: при установке хука мы открываем нужные нам объекты и проецируем в нашу память общий буфер. Далее приведён код функции фильтра.

В начале мы сразу же вызываем следующий обработчик в цепочке обработчиков. Потом обрабатываем данные в зависимости от типа события. В событии HCBT_CREATEWND мы поучаем имя окна из структуры PCBTCreateWnd на которую указывает параметр lParam, в остальных двух случаях мы получаем имя окна, используя её хендл который находится в параметре wParam. В событии HCBT_CREATEWND мы получаем имя окна только в том случае если оно главное, т.е. не имеет родителя, в остальных двух случаях мы производим обработку только в случае, если имя окна не является пустой строкой. После того как мы получили строку нам необходимо её добавить в буфер. Добавление производится между вызовами функций WaitForSingleObject и ReleaseMutex чтобы обновление мог производить только один поток одновременно.

Осталось написать приложение сервер, которое будет запускать и останавливать мониторинг.

Я думаю, ничего сложного в этом коде нет. Функция DumpBuffer скидывает содержимое буфера в файл. При создании объекта файлового мэпинга мы не указываем никакого файла. Сразу возникает вопрос: почему? Смысл в том, что размера выделяемого буфера может не хватить и придётся его время от времени сбрасывать в файл, а если выделять сразу большой буфер, то хук станет слишком ресурсоёмким. Хотя в данном примере не реализован сброс буфера в файл при нехватке места в буфере, об этом нельзя забывать и это надо будет обязательно реализовать в своих программах.

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

function Mouse_SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;
var H: HWND;
begin
if (Code=HC_ACTION) then
begin
H := FindWindow("TForm1", "XXX");

//тут проверяю факт нажатия правой клавиши мыши и Ctrl`а
if (wParam = WM_RBUTTONDOWN) AND (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
SendMessage(H, wm_RightMouse_Event, 0, 0);
end;

Result:= CallNextHookEx(Mouse_SysHook, Code, wParam, lParam);
end;

Этот код позволяет мне узнать что нажата правая клавиша мыши и Ctrl, но не позволяет предотвратить появление контекстного меню. Как мне это сделать?

← →
Leonid Troyanovsky © ( 2006-07-03 22:58 ) [1]


> leonidus © (03.07.06 21:30)

> if (wParam = WM_RBUTTONDOWN) AND (HiWord(GetAsyncKeyState>

Почему, собс-но, GetAsyncKeyState?
IMHO, этот вопрос уже задавался.
Ну, а про остальные вопросы можно и после.

WH_MOUSE_LL, этот ответ (LL), IMHO, уже давался.
Т.е., даже без dll.

> Леонид, а чем вам не нравится GetAsyncKeyState, чем его
> можно заменить? Разве в данном случае это вообще принципиально,
> ведь как раз определение факта нажатия Ctrl срабатывает
> нормально? И потом как вы предлагаете установить глобальный
> хук без dll? Простите что столько вопросов, я просто совсем

GetKeyboardState vs GetAsyncKeyState.
WM_MOUSE vs WM_MOUSE_LL

← →
Lamer@fools.ua © ( 2006-07-03 23:31 ) [5]

>function Mouse_SysMsgProc(code : integer; wParam : word; lParam : longint) : longint; stdcall;

[OFFTOPIC] Уже устал спрашивать: откуда многие этот бред берут?

← →
leonidus © ( 2006-07-04 08:54 ) [6]

Леонид, подскажите пожалуйста как с WM_MOUSE_LL работать? Искал на MSDN "WM_MOUSE_LL" но не нашел, Яндекс вообще молчит, поясните пожалуйста хотябы как вы видите решение этой проблемы.

← →
второе явление Чапаева народу ( 2006-07-04 09:57 ) [7]


> >function Mouse_SysMsgProc(code : integer; wParam : word;
> lParam : longint) : longint; stdcall;
>
> [OFFTOPIC] Уже устал спрашивать: откуда многие этот бред
> берут?

Из Win16, где WPARAM Word.

LL -- это "низкоуровневый" хук. работает по всей системе, не требует DLL. если упрощённо, то вызывается сразу после того, как драйвер сгенерировал событие, но до того, как это событие будет проинтерпретировано.

function tform1.Mouse_SysMsgProc(code : integer; wParam : word; lParam : longint) : longint;
var H: HWND;
begin
if (Code=HC_ACTION) then
begin
if wParam = WM_RBUTTONDOWN then form1.Label1.Caption:="wm_RightMouse_Event";
end;
Result:= CallNextHookEx(Mouse_SysHook, Code, wParam, lParam);
end;

procedure tform1.hook(switch : Boolean);
begin
if switch=true then
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
if Mouse_SysHook <> 0 then MessageBox(0, "Хук на мышь установлен", "", 0)
else MessageBox(0, "Хук на мышь установить не удалось", "", 0);
end
else
begin
if UnhookWindowsHookEx(Mouse_SysHook) then MessageBox(0, "Хук на мышь снят", "", 0)
else MessageBox(0, "Хук на мышь снят не удалось", "", 0);

Mouse_SysHook := 0;
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
//установить хук
hook(true);
end;

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
//снять хук при выходе из программы
hook(false);
end;

← →
leonidus © ( 2006-07-04 11:58 ) [13]

но на строчку: Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
компилятор ругается на WH_Mouse_LL, что делать?

← →
Игорь Шевченко © ( 2006-07-04 12:06 ) [14]


> WH_Mouse_LL

const
WH_Mouse_LL = 14;

← →
leonidus © ( 2006-07-04 13:07 ) [15]

Ни чего не понимаю, теперь в
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);

ругается на HInstance. может я уже туплю, но код скопировал прямо из dll`ки в которой все работало прекрасно

← →
Игорь Шевченко © ( 2006-07-04 13:37 ) [16]


> ругается на HInstance

Дорогой друг, кроме фразы "ругается" желательно еще приаттачить скриншот экрана твоего монитора в момент ругани, а так же до ругани и после ругани.

← →
leonidus © ( 2006-07-04 13:48 ) [17]

Компилятор выделяет строчку
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
ставит курсор на HInstance и пишет: "Variable required"

← →
Ketmar © ( 2006-07-04 13:51 ) [18]

кстати, я тут заметил: а может, просто надо "съёдать" событие, когда оно не должно происходить? т.е. возвращать "не ноль" и не вызывать следующий хук при помощи CallNextHookEx()? а то мы тут уже полезли в LL-хуки. %-)

← →
leonidus © ( 2006-07-04 14:07 ) [19]

я пробовал делать так:

if (wParam = WM_RBUTTONDOWN) and (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
begin
form1.Label1.Caption:="wm_RightMouse_Event";
end
else
Result:= CallNextHookEx(Mouse_SysHook, Code, wParam, lParam);

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

← →
Ketmar © ( 2006-07-04 14:22 ) [20]

странно. %-)

← →
leonidus © ( 2006-07-04 14:40 ) [21]

Я всеже не пойму почему на HInstance компилятор ругается, и в тоже время туже строчку в dll`ке компилирует нормально.

← →
Игорь Шевченко © ( 2006-07-04 17:26 ) [22]


> function tform1.Mouse_SysMsgProc(code : integer; wParam
> : word; lParam : longint) : longint;

Убрать TForm1 из объявления

← →
Игорь Шевченко © ( 2006-07-04 17:28 ) [23]

unit Unit1;

uses
Windows, Classes, Controls, Forms,
StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;

var
Form1: TForm1;

const
WH_Mouse_LL = 14;

var
Mouse_SysHook: HHOOK;

function Mouse_SysMsgProc(code: integer;
wParam: longint; lParam : longint):longint; stdcall;
begin
Result := CallNextHookEx(Mouse_SysHook, Code, wParam, LParam);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
end;

Не ругается

← →
Gero © ( 2006-07-04 18:04 ) [24]

Только стоит принять во внимание, что WH_MOUSE_LL отсутствует в Windows 9x/Me.

← →
Leonid Troyanovsky © ( 2006-07-04 18:30 ) [25]


> второе явление Чапаева народу (04.07.06 09:57) [7]

Не, просто, описАлся, чисто машинально.

--
Regards, LVT.

← →
leonidus © ( 2006-07-04 20:07 ) [26]

Игорь, большое спасибо. Теперь вроде разобрался и вернулся к первому вопросу, как в случае если нажата "горячая клавиша" - скажем Ctrl, и произведен клик правой клавишей мыши не допустить появления контекстного меню в каком-либо приложении?

const
WH_Mouse_LL = 14;

var
Form1: TForm1;
Mouse_SysHook: HHOOK;

function Mouse_SysMsgProc(code: integer;
wParam: longint; lParam : longint):longint; stdcall;
begin
if (Code=HC_ACTION) then
if (wParam = WM_RBUTTONDOWN) and (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
begin
form1.Label1.Caption:="wm_RightMouse_Event";
result:=1;
end
else
begin
form1.Label1.Caption:="";
Result := CallNextHookEx(Mouse_SysHook, Code, wParam, LParam);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
UnhookWindowsHookEx(Mouse_SysHook);
end;

но после определения того что нажат Ctrl и правая клавиша мыши, контекстное меню всеравно благополучно появляется. Что я делаю не так?

← →
leonidus © ( 2006-07-05 15:47 ) [27]

Господа подскажите пожалуйста

← →
guav © ( 2006-07-05 18:15 ) [28]

к. меню появляется при отпускании мыни

← →
leonidus © ( 2006-07-05 22:12 ) [29]

блин точно, сейчас попробую WM_RBUTTONUP

← →
leonidus © ( 2006-07-06 07:58 ) [30]

В общем вот последняя редакция кода:

const
WH_Mouse_LL = 14;

var
Form1: TForm1;
Mouse_SysHook: HHOOK;

function Mouse_SysMsgProc(code: integer;
wParam: longint; lParam : longint):longint; stdcall;
begin
if (Code=HC_ACTION) then
if (wParam = WM_RBUTTONUP) and (HiWord(GetAsyncKeyState(VK_LCONTROL))<>0) then
begin
form1.Label1.Caption:="wm_RightMouse_Event";
result:=-1;
end
else
begin
form1.Label1.Caption:="";
Result := CallNextHookEx(Mouse_SysHook, Code, wParam, LParam);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
Mouse_SysHook := SetWindowsHookEx(WH_Mouse_LL, @Mouse_SysMsgProc, HInstance, 0);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
if UnhookWindowsHookEx(Mouse_SysHook) then MessageBox(0, "Хук на мышь снят", "", 0)
end;

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

Читайте также: