Где на клавиатуре hook
Хуки предоставляют мощные возможности для приложений 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 скидывает содержимое буфера в файл. При создании объекта файлового мэпинга мы не указываем никакого файла. Сразу возникает вопрос: почему? Смысл в том, что размера выделяемого буфера может не хватить и придётся его время от времени сбрасывать в файл, а если выделять сразу большой буфер, то хук станет слишком ресурсоёмким. Хотя в данном примере не реализован сброс буфера в файл при нехватке места в буфере, об этом нельзя забывать и это надо будет обязательно реализовать в своих программах.
Полный код примера к находится в архиве прилагающемуся к этой статье. Пример, не претендует на звание самого лучшего и является всего лишь шаблоном для написания программ для мониторинга окон.
Приведем неполный список необходимых системных функций:
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
- static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);
- [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
- static extern bool UnhookWindowsHookEx(IntPtr hhk);
- [DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
- static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
Если нам понадобится пересылать в главное окно приложения строку (например, название приложения, связанного с клавиатурным вводом), то мы используем тогда такую функцию:
- [DllImport("user32.dll", CharSet = CharSet.Auto)]
- static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPStr)] string lParam);
- [DllImport("user32.dll", SetLastError=true)]
- static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
- [DllImport("user32.dll")]
- static extern IntPtr GetForegroundWindow();
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int nMaxcount);
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- static extern int GetWindowTextLength(IntPtr hWnd);
const int WH_KEYBOARD_LL = 13;
const int HC_ACTION = 0;
const int WM_KEYDOWN = 0x0100;
const int WM_USER = 0x0400;
Кроме всего выше сказанного, нам понадобится еще функция-обработчик, где будет происходить процесс обработки данных. Ее мы прикрепим к делегату, который должно будет передать в функцию SetWindowsHookEx, вторым параметром. В С++ передавали бы в данном случае указатель на функцию-обработчик. Делегат имеет следующий вид:
delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
Теперь, можно представить примерный код устанавливаемого хука. Ниже, приведем код, размещаемый в отдельной dll:
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- private static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int nMaxcount);
- [DllImport("user32.dll")]
- private static extern IntPtr GetForegroundWindow();
- [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
- private static extern int GetWindowTextLength(IntPtr hWnd);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
- [DllImport("user32.dll", CharSet = CharSet.Ansi)]
- private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, [MarshalAs(UnmanagedType.LPStr)] string lParam);
- private const int WH_KEYBOARD_LL = 13;
- private const int HC_ACTION = 0;
- private const int WM_KEYDOWN = 0x0100;
- private const int WM_USER = 0x0400;
- private static HookProc proc = HookCallback;
- public static IntPtr hook = IntPtr.Zero;
- private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
- if(nCode >= HC_ACTION && wParam == (IntPtr)WM_KEYDOWN)
- try
- int vkCode = Marshal.ReadInt32(lParam);
- IntPtr handle = FindWindow(null, "Wind");
- IntPtr ahandle = GetForegroundWindow();
- int len = GetWindowTextLength(ahandle);
- StringBuilder str = new StringBuilder(len + 1);
- GetWindowText(ahandle, str, str.Capacity);
- SendMessage(handle, WM_USER + 755, wParam, str.ToString() + "|" + vkCode.ToString());
- >
- catch(Exception e)
- MessageBox.Show(e.Message);
- >
- >
- return CallNextHookEx(hook, nCode, wParam, lParam);
- >
- public override IntPtr SetHook()
- return SetWindowsHookEx(WH_KEYBOARD_LL, proc, IntPtr.Zero, 0);
- >
- public override void UnsetHook(IntPtr hhk)
- UnhookWindowsHookEx(hhk);
- >
Функцией SetHook мы устанавливаем собственно сам глобальный хук, передавая в функцию SetWindowsHookEx наш клавиатурный фильтр и экземпляр делегата. SetHook возвращает описатель хука. Функцией UnsetHook мы отменяем установленный хук.
- protected override void WndProc(ref Message m)
- String keyapp = String.Empty;
- switch (m.Msg)
- case WM_KEYSTROK:
- keyapp = Marshal.PtrToStringAnsi(m.LParam);
- MessageBox.Show(keyapp);
- break;
- >
- base.WndProc(ref m);
- >
Короткий итог
Нет ничего конкретней инф-ции в MSDN.
Есть проблемы ? Излагай ..
> А можно поконкретней если не сложно.
//обработка
procedure TForm2.WMHotkey(var a:TWMHotkey);
begin
if a.hotkey=32 then begin
Разумеется, Вы можете называть карандаш ластиком, а ластик -- карандашом. Правда, от этого карандаш не начнёт стирать, а ластик не начнёт писать, но это, по-видимому, Вас не очень беспокоит.
> К сожалению мне пока не требовалось решать такие задачи
К сожалению, это типичная задача, для которой используются ловушки. Непонятно, почему Вы считаете возможным давать советы, не имея опыта решения даже типичных задач по теме вопроса.
> да я и не понял что вам еще конкретно нужно
Странно. По-моему, в [10] задание сформулировано достаточно чётко: перехватывать WM_KEYDOWN до того, как оно попадёт в оконную процедуру.
> возможно вы не знаете как передать перехваченное
> нажатие активному окну, так это можно сделать примерно так
> SendMessage(GetForegroundWindow,WM_CHAR,wparam,0);
> однако мне кажется что вы и сами прекрасно разберетесь
К сожалению, Вы ошибаетесь. Без Вас разобраться в том, как можно функцией RegisterHotKey установить глобальный хук на клавиатуру, мне вряд ли удастся. Поэтому всё же хотелось бы увидеть решение задачи -- думаю, оно будет интересно не только мне.
Впрочем, решение можно и не приводить, а вместо этого согласиться с тем, что RegisterHotKey к установке глобальных клавиатурных хуков отношения не имеет. И извиниться перед автором вопроса за то, что Вы ввели его в заблуждение. После этого тема будет исчерпана.
Совершенно верно,
я думаю что создав SetWindowsHookEX ее создатели ужаснулись
делу рук своих и решили что американский человек никогда этого не поймет и
поэтому для самого типового использования ловушек и создали RegisterHotKey
спрятав все хуки внутрь что вас и смутило
>перехватывать WM_KEYDOWN до того, как оно попадёт в оконную процедуру.
при использовании RegisterHotKey, WM_KEYDOWN вообще не попадает
в процедуру активного окна, а только в процедуру окна указаного при регистрации
поэтому я и решил что вам нужно все-таки передать в активное окно
>Правда, непонятно, зачем перехваченное нажатие куда-то посылать
>-- при использовании хука оно само дойдёт туда, куда надо
Так вы же просили: "т.е. с помощью функции RegisterHotKey." [10]
>а вместо этого согласиться с тем, что RegisterHotKey к установке
>глобальных клавиатурных хуков отношения не имеет
Тут я могу согласиться что RegisterHotKey не так универсальна как SetWindowsHookEX
и можете называть ее как хотите, но она решает ту же задачу для клавиатуры проще
и не требует dll что и нужно для решения вопроса темы
> RegisterHotKey не так универсальна как SetWindowsHookEX
> и можете называть ее как хотите, но она решает ту же задачу
> для клавиатуры проще
Вы только что (в [18]) согласились с тем, что эта задача -- типичная для использования хуков. Ранее (в [7] и [9]) Вы утверждали, что установить глобальный хук можно функцией RegisterHotKey.
Тогда приведите его, пожалуйста. Причём именно такое решение, в котором ловушка устанавливается функцией RegisterHotKey. И решение именно задачи, поставленной в [19], а не какой-либо другой.
> и пора бы и вам предложить свое решение без dll
Что ж, пожалуй, действительно, пора. Собственно, направление для поиска я дал ещё в [6]. А конкретный код может быть таким:
> и можете не считать это глобальным хуком, но это работает
А можно мы будем звать оное по-старинке, т.е. aka hot key?
Заранее благодарны.
> и пора бы и вам предложить свое решение без dll
Спасибо за пример
Лучше бы его привести сразу в [6] раз это так просто
ведь не все понимают ваши намеки
тем более что WH_KEYBOARD_LL у меня в D5 нет и
видно вы описали ее только чтоб оправдать свой намек
>Тогда приведите его, пожалуйста
так я уже привел все что мог и обьяснил что
это вполне подходит для типовых случаев
> тем более что WH_KEYBOARD_LL у меня в D5 нет и
Дык, оно есть в NT4SP3+, о чем уж, вроде, говорилось.
Дельфи тут вовсе ни причем.
функция registerhotkey никоим образом не дублирует SetWindowsHookEx. Давайте так договоримся, вы немножко матчасть подучите, а потом мы с вами с удовольствием подискутируем. Просто в дискуссии хотелось бы рассчитывать на определенный уровень грамотности собеседника.
> Спасибо за пример
> Лучше бы его привести сразу в [6] раз это так просто
> ведь не все понимают ваши намеки
За пример -- пожалуйста. Однако право решать, когда МНЕ приводить СВОИ примеры (которых я никому не обещал), оставьте, пожалуйста, за МНОЙ. И мой "намёк" в [6] был вполне конкретен: поиск в MSDN по указанному ключевому слову даёт хорошие результаты, в которых остаётся лишь вдумчиво разобраться.
> тем более что WH_KEYBOARD_LL у меня в D5 нет
Вы будете смеяться, но у меня в D7 его тоже нет. Что, однако, не помешало мне написать рабочий пример. Ибо дело, как уже замечено в [28], совсем не в Delphi.
> видно вы описали ее только чтоб оправдать свой намек
Я описал эту константу для того, чтобы написать рабочий пример в виде нормально читаемого кода. Если Вы считаете, что я, приводя пример, перед кем-то оправдывался, то это Ваши проблемы.
> так я уже привел все что мог и обьяснил что
> это вполне подходит для типовых случаев
Э. нет, уважаемый, так дело не пойдёт. Я в [19] сформулировал КОНКРЕТНУЮ задачу? Сформулировал. Вы в [20] сказали, что МОЖЕТЕ решить эту КОНКРЕТНУЮ задачу функцией RegisterHotKey? Сказали.
Однако пока что Вашего решения я не увидел. В чём же дело?
Поскольку у нас в форуме за свои слова принято отвечать (см. правила), повторю свою просьбу в N-й раз: приведите, пожалуйста, решение этой конкретной задачи (которая представляет собой установку глобального хука) своим способом, тем самым подтвердив своё заявление о том, что функцию RegisterHotKey можно использовать для установки этого самого глобального хука. Или, если у Вас нет решения и Вы не можете его написать, извинитесь перед автором темы.
> и уже потом посылает другим - CallNextHookEx
> Я могу только повторить что уже писал [18]
А я могу повторить то, что Вы писали в [20]: "Могу". Вы утверждали, что можете решить [19]. Так можете, или нет? Если да, приведите решение. Если нет, извинитесь. Кажется, это я уже говорил.
> конечно можно придумать разные каверзные случаи
> неподходящие для нее
В задаче [19] нет ничего каверзного. Эта задача -- типичная для применения хуков. И Вы сами с этим согласились в [18]. А если RegisterHotKey не решает даже типичных задач из этой области -- вероятно, она предназначена для чего-то другого, не правда ли?
> однако для типового применения
> она вполне заменит SetWindowsHookEX
См. выше -- [19] представляет собой типовую задачу. Однако код, решающий её с помощью SetWindowsHookEx, уже есть (см. [21]), а кода, решаающего её функцией RegisterHotKey (которая, по Вашим словам, для типовых задач вполне заменяет SetWindowsHookEx), Вы пока почему-то не привели. Так за чем же дело стало?
> что я и предложил поскольку ничего лучше тогда еще никто
> не предложил
Вы невнимательно читаете. [6] было предложено ещё до того, как Вы появились в этой ветке.
> хотя и мог как оказалось и только благодаря моему упорству
> удалось получить информацию полезную многим
> и это главная цель форума
> а все остальное никто и читать не будет
> Извините все, но при помощи RegisterHotKey эту задачу решить
> не могу
Наконец-то. Непонятно, правда, зачем в [20] нужно было говорить обратное, ну да ладно.
> однако может намекнете.
Непонятно, правда, зачем в [10] нужно было говорить обратное, ну да ладно.
> Непонятно, правда, зачем в [10] нужно было говорить обратное,
> ну да ладно.
кстати, Ваш пример в W98 вообще ничего не ловит
а мой работает нормально
> кстати, Ваш пример в W98 вообще ничего не ловит
Совершенно верно -- мой пример не работает в Win98. Надеюсь, что ни для кого из читающих ветку, кроме Вас, это новостью не является, ведь я сам сказал об этом ещё в [6].
> а мой работает нормально
Совершенно верно -- Ваш пример работает в Win98. Правда, к теме ветки он не имеет ни малейшего отношения.
Ты б хоть сослался.
Джефу Рихтеру, наверное, это пофигу,
а мне, как переводчику, было б приятно.
Читайте также: