Глобальный Keyhook на 64-битной Windows

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

Поэтому я постараюсь объяснить, с чем я борюсь, и надеюсь, что кто-нибудь может мне помочь или указать мне правильное направление.

По сути, моя цель - перехватить ввод с клавиатуры CTRL+C и CTRL+V для своего рода диспетчера буфера обмена. По этой причине моя текущая попытка состоит в том, чтобы зарегистрировать WH_KEYBOARD крючок, который имеет дело с перехваченными нажатиями клавиш для моих потребностей.

Я использую 64-разрядную версию Windows 7 O/S, и именно здесь начинаются проблемы. Для меня очевидно, что 32-битная библиотека Hook вызывает проблемы для 64-битных процессов, и наоборот. По этой причине я сгенерировал версию моей библиотеки для x86 и x64, содержащую ловушку, а также для вызывающего ловушку (тот, кто вызывает SetWindowsHookEx()), оба с разными именами файлов, как предполагает документация.

А что теперь? Если я установлю свою 64-битную DLL в качестве общесистемного хука, все 32-битные приложения начнут зависать, как только я нажму клавишу, когда они сфокусированы. То же самое, когда я применяю 32-битный хук, моя Windows практически не работает, потому что explorer.exe 64-битный Если я установлю оба хука, моя система будет бездействующей, имея глобальную битвную битву.

Теперь я предполагаю, что проблема возникает, например, из-за того, что 64-битная DLL-библиотека перехвата пытается внедриться в 32-битный процесс и т. Д., Что, конечно, бессмысленно. Но для этого случая документация SetWindowsHookEx() говорит следующее:

Поскольку хуки выполняются в контексте приложения, они должны соответствовать "разрядности" приложения. Если 32-битное приложение устанавливает глобальный хук в 64-битной Windows, 32-битный хук внедряется в каждый 32-битный процесс (применяются обычные границы безопасности). В 64-битном процессе потоки по-прежнему помечаются как "подключенные". Однако, поскольку 32-разрядное приложение должно запускать код подключения, система выполняет его в контексте приложения подключения; в частности, в потоке, который называется SetWindowsHookEx. Это означает, что приложение перехвата должно продолжать качать сообщения, иначе оно может заблокировать нормальное функционирование 64-битных процессов.

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

Чтобы подключить все приложения на рабочем столе для 64-разрядной установки Windows, установите 32-разрядный глобальный обработчик и 64-разрядный глобальный обработчик, каждое из которых выполняется в соответствующих процессах, и обязательно продолжайте пересылку сообщений в приложении подключения, чтобы избежать нормальной блокировки функционирование.

Но все же я не понимаю, что должно быть сделано в моей реализации. Чтобы наконец показать некоторый код, давайте возьмем этот базовый пример попытки установить общесистемный keyhook. Я предполагаю, что создание кода для потока не имеет значения:

volatile static bool runThread = false;

DWORD WINAPI threadStart(LPVOID lpThreadParameter) {
    HMODULE hMod = LoadLibraryA(is64Bit() ? "keyhook.x64.dll" : "keyhook.x86.dll");
    HHOOK hHook = SetWindowsHookExA(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hMod, "hookProc"), hMod, 0)));

    runThread = true;
    while(runThread) {
        // Message pump? Yes? No? How?
        Sleep(10);
    }

    UnhookWindowsHookEx(hHook);
    FreeLibrary(hMod);
    return 0;
}

Сам хук поддерживается довольно тривиально - этого достаточно, чтобы вызвать проблемы с зависанием при пересечении битности:

extern "C" LRESULT hookProc(int code, WPARAM wParam, LPARAM lParam) {
    if(code == HC_ACTION) {
    }

    return CallNextHookEx(nullptr, code, wParam, lParam);
}

Я предполагаю, что некоторые люди могут прямо сейчас бросить туда руки над головой, и что вы можете сказать, что я редко работал с крючками;)

Но именно поэтому я и спрашиваю:)

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

Любая помощь очень ценится!

Спасибо и всего наилучшего

PuerNoctis

1 ответ

Решение

Хорошо, я понял, в чем проблема. Возможно, мое решение поможет другим людям, испытывающим ту же проблему: как уже упоминалось выше, в документации прямо говорится, что

[...] система выполняет (32-битную) перехват в контексте (32-битной) перехватывающего приложения; в частности, в потоке, который называется SetWindowsHookEx. Это означает, что приложение перехвата должно продолжать качать сообщения, иначе оно может заблокировать нормальное функционирование 64-битных процессов.

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

volatile static bool runThread = false;

DWORD WINAPI threadStart(LPVOID lpThreadParameter) {
    HMODULE hMod = LoadLibraryA(is64Bit() ? "keyhook.x64.dll" : "keyhook.x86.dll");
    HHOOK hHook = SetWindowsHookExA(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hMod, "hookProc"), hMod, 0)));

    MSG msg;
    runThread = true;
    while(runThread) {
        // Keep pumping...
        PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        Sleep(10);
    }

    UnhookWindowsHookEx(hHook);
    FreeLibrary(hMod);
    return 0;
}

В этом примере я использую неблокирующую PeekMessage() вместо GetMessage(), так как я хочу, чтобы моя тема постоянно проверяла свой флаг завершения.

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

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

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

Опять же, если это не совсем верно, неполно или есть какие-либо дополнительные примечания, добавьте комментарий ниже. Спасибо!:)

Мое решение состоит в том, чтобы скомпилировать приложение хука (в котором SetWindowsHookEx() была вызвана) и DLL (где расположена функция ловушки обратного вызова) для версий как x86, так и x64. Таким образом, у нас есть два EXE-файла (xxx-x86.exe & xxx-x64.exe) и две библиотеки DLL (xxx-x86.dll & xxx-x64.dll).

Затем реализуйте сложный протокол IPC для синхронизации данных между приложением x86 и приложением x64. Это выглядит работать, не будет блокировать другой процесс, "битность" которого не имеет себе равных. Но трудно обрабатывать точные последовательности событий, это может служить лишь приблизительным индикатором событий.

Это решение довольно некрасиво, но у меня нет лучшего способа...

Другие вопросы по тегам