我正在尝试实现两个组合键,
LeftAlt+1
和LeftAlt+2
,并让它们注入字符串。新的热键是按照 DLL 实现的。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
#include <windows.h>
#include <iostream>
using namespace std;
HHOOK hHook{ NULL };
const DWORD L1 = 49;
const DWORD L2 = 50;
extern "C" __declspec(dllexport)
LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code < 0) return CallNextHookEx(hHook, code, wParam, lParam);
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam;
DWORD vkCode = kbdStruct->vkCode; // virtual-key code
if (kbdStruct->flags & LLKHF_ALTDOWN && (vkCode == L1 || vkCode == L2))
{
if (vkCode == L1) cout << "First text";
else cout << "Second text";
}
}
return CallNextHookEx(hHook, code, wParam, lParam);
}
测试应用程序加载 DLL 并使用 Win32 函数
SetWindowsHookExW()
安装挂钩程序 KeyboardHookProc
globally.
#include <windows.h>
#include <iostream>
using namespace std;
int main()
{
HHOOK hhook = 0;
LPCWSTR dllPath = L"PernumLib.dll";
static HINSTANCE hinstDLL = LoadLibraryW(dllPath);
HOOKPROC keyboardHookProc = (HOOKPROC)GetProcAddress(hinstDLL, "KeyboardHookProc");
hhook = SetWindowsHookExW(WH_KEYBOARD_LL, keyboardHookProc, hinstDLL, 0);
if (hhook == NULL)
{
cout << "SetWindowsHookExA failure!\n";
Sleep(3000);
return -1;
}
while (GetMessage(NULL, NULL, 0, WM_KEYLAST));
//if (hhook) UnhookWindowsHookEx(hhook);
return 0;
}
只要在测试应用程序的窗口中使用这两个热键,它就可以工作。但我们的想法是实现热键globally,例如,在网络浏览器或文本编辑器中输入文本时使用它们。但是,热键在测试应用程序之外不可用。
我意识到全局挂钩仅适用于 64 位应用程序或 32 位应用程序,具体取决于 DLL 的体系结构。我正在 64 位机器上的 Windows 11 中使用 Visual Studio 2022 开发 DLL,我相信 VS 创建的工件是 64 位的(但我尚未验证)。热键不起作用的 Google Chrome 是一个 64 位应用程序。我的结论是问题不是32位/64位不一致引起的。
此处显示了技术上类似的 DLL 注入示例。作者观察到同样的问题:有一个警告。此技术在我的 Windows 10 x64 机器上不起作用。我认为原因是这样的:CIG block this technique.
将一个全局钩子过程限制在一个单一的测试应用程序中是没有意义的,所以必须有一个解决方案。我还需要做些什么才能让它发挥作用?
编辑:感谢下面的评论和额外的测试,我意识到钩子确实有效。然而,由于低级钩子在调用SetWindowsHookExW
的应用程序上下文中执行,即使在网络浏览器或文本编辑器中键入热键,文本也会被注入到该应用程序中。因此,为了解决我的问题,我必须使用钩子
WH_KEYBOARD
而不是
WH_KEYBOARD_LL
。我正在努力。
WH_KEYBOARD_LL
。它不适用于挂钩
WH_KEYBOARD
。原因是必须及早发现使用
ALT
的热键并阻止其进一步进入系统。因此,上面显示的主程序是正确的。
挂钩过程的示例如下所示。它检测热键
LeftAlt+Q
和
LeftAlt+A
并在这两种情况下注入字符串
AB12
,使用 WinAPI 函数
SendInput()
来模拟键盘驱动程序发送的信号。模拟的键盘操作顺序为:
LeftAlt
up
,
Shift
down
, 字符
AB
,
Shift
up
, 字符
12
,
LeftAlt
down
。此外,键盘操作序列以
CTRL
down
/
up
开始和结束。必须添加这些动作以确保钩子程序的稳定运行。他们的作用可能是增加延迟。
#include "pch.h"
#include <windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void DisplayString()
{
INPUT in[16] = {0};
int i;
for (i = 0; i < ARRAYSIZE(in); i++) in[i].type = INPUT_KEYBOARD;
i = 0;
in[i++].ki.wVk = VK_CONTROL; // CTRL
in[i].ki.wVk = VK_CONTROL; // CTRL Up
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i].ki.wVk = VK_LMENU; // LeftAlt Up
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i++].ki.wVk = VK_LSHIFT; // LeftShift down
in[i++].ki.wVk = 'A';
in[i].ki.wVk = 'A';
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i++].ki.wVk = 'B';
in[i].ki.wVk = 'B';
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i].ki.wVk = VK_LSHIFT; // LeftShift Up
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i++].ki.wVk = '1';
in[i].ki.wVk = '1';
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i++].ki.wVk = '2';
in[i].ki.wVk = '2';
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
in[i++].ki.wVk = VK_CONTROL; // CTRL
in[i++].ki.wVk = VK_LMENU; // LeftAlt Down
in[i].ki.wVk = VK_CONTROL; // CTRL Up
in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
SendInput(ARRAYSIZE(in), in, sizeof(INPUT));
}
HHOOK hHook{ NULL };
#define VK_HOT_1 0x51
#define VK_HOT_2 0x41
extern "C" __declspec(dllexport)
LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam)
{
if (code < 0) return CallNextHookEx(hHook, code, wParam, lParam);
if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
KBDLLHOOKSTRUCT* kbdStruct = (KBDLLHOOKSTRUCT*)lParam;
DWORD vkCode = kbdStruct->vkCode; // virtual-key code
if (kbdStruct->flags & LLKHF_ALTDOWN && (vkCode == VK_HOT_1 ||
vkCode == VK_HOT_2))
{
DisplayString();
return 1;
}
}
return CallNextHookEx(hHook, code, wParam, lParam);
}
具有键盘操作的(的)结构 INPUT 包含一个联合,并且不能使用括在大括号中的值列表轻松初始化。 INPUT in[16]
中索引元素的非常规方式允许在序列中添加和删除角色动作,而无需在更改后重新编号动作。一些评论者建议使用 UI Automation 来解决这个问题。 UI Automation 是 .NET WinForms 的附加组件,与提供的解决方案相比非常复杂。此外,UI 自动化不能与所有 Windows 控件一起使用。相比之下,所提出的解决方案模拟键盘输入并与任何能够接收字符的控件一起工作。