C++键盘挂钩

C++ KeyBoard hook

本文关键字:键盘 C++      更新时间:2023-10-16

主题:将某些键替换为另一个键值。

例如,如果我按 P,它应该是 F24。

当我尝试从.ini文件钩子加载键值时,它不再是全局的。仅当 winapi 形成焦点时,它才有效。

我的 DLL 代码:

    extern "C" __declspec(dllexport) LRESULT CALLBACK KeyboardHook(int, WPARAM, LPARAM);
    extern "C" __declspec(dllexport) void loadSettings(LPSTR);
    bool shouldUpdateKey = false;
    int ArcherKey;
    LRESULT CALLBACK KeyboardHook(int code, WPARAM wParam, LPARAM lParam)
        {
            if ((lParam >> 20))
            {
                if (wParam == ArcherKey) 
                {
                shouldUpdateKey = shouldUpdateKey ? false : true;
                if (shouldUpdateKey) 
                {
                MessageBox(NULL, L"ArcherKey", L"", MB_OK);
                keybd_event(0x87, 45, 1, 0); //press F24
                return 1; 
                }
                }
            }

            return CallNextHookEx(NULL, code, wParam, lParam);
        }
      LPSTR GetValueFromINI(LPSTR FileName, LPSTR Section, LPSTR Key)
        {
            char *key;
            key = (char *)malloc(256);
            GetPrivateProfileStringA(Section, Key, NULL, key, 256, FileName);
            return key;
            free(key);
        }
      void loadSettings(LPSTR FileName) 
        {
            ArcherKey = atoi(GetValueFromINI(FileName, "HotKey", "Archer key"));
        }

我使用 shouldUpdateKey 来避免 x2 回调(当按键按下和向上按下时(调用。我也尝试添加这个语句,如果(lParam>>31( ^ 1,但这个语句总是错误的。

.exe代码:

LRESULT(*pKeybHook)(int, WPARAM, LPARAM);
HHOOK hhookMsg;
void(*loadSettings)(LPSTR);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPTSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
/* default code */
HMODULE dll = LoadLibrary(_T("MainHookDLL.dll"));
    if (dll)
    {
        pKeybHook = (LRESULT(*)(int, WPARAM, LPARAM)) GetProcAddress(dll, "_KeyboardHook@12");
        loadSettings = (void(*)(LPSTR)) GetProcAddress(dll, "loadSettings");
        loadSettings("C:\Settings.ini");
        hhookMsg = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)(pKeybHook), dll, 0);
    }
/* defult code */
    UnhookWindowsHookEx(hhookMsg); // unhook
    FreeLibrary(dll);
    return (int) msg.wParam;
}       

设置.ini结构:

[HotKey]
Archer key=80

所以我的问题:如果尝试从文件加载设置,钩子仅在活动的 winapi 窗口中工作。它显示MessageBox\etc,但仅以活动的winapi形式显示。如果将 wParam == ArcherKey 替换为 wParam == 80,则它可以在所有应用程序中全局工作。我调试我的应用程序,从文件加载后.ini我的 ArcherKey = 80。所以我真的无法理解我实际上犯了什么错误。

我所知,如果钩子是全局的,则包含 HOOKPROC 的 DLL 将加载到所有其他进程中。这意味着内存中有多个 DLL 实例。由于您从应用程序中调用 loadSettings(..(,因此仅针对该进程初始化 ArcherKey 的值。这会导致您正在观察的行为。

要更改此设置,您应该将 DllMain(..( 函数修改为如下所示的内容:

BOOL WINAPI DllMain(HINSTANCE hinstDLL,  // handle to DLL module
                    DWORD fdwReason,     // reason for calling function
                    LPVOID lpReserved )  // reserved
{
   switch( fdwReason ) 
   { 
      case DLL_PROCESS_ATTACH:
         loadSettings("C:\Settings.ini");
         break;
      case DLL_THREAD_ATTACH:
         // Do thread-specific initialization.
         break;
      case DLL_THREAD_DETACH:
         // Do thread-specific cleanup.
         break;
      case DLL_PROCESS_DETACH:
         // Perform any necessary cleanup.
         break;
   }
   return TRUE;
}

这将初始化正在安装挂钩的所有进程的 ArcherKey 值,因为 Windows 在加载 DLL 时调用 DllMain。出于测试目的,您可以添加消息蜂鸣声(0(;在调用 loadSettings(..( 之前验证代码部分是否已执行。

快速浏览一下 SetWindowsHookEx(..( 的文档让我的担忧成真:如果你正在编译一个 32 位 DLL,你将无法挂接 64 位进程,反之亦然。为此,您必须使用不同名称的 HOOKPROC 实现 64 位版本的 dll。

钩子被"注入"到其他进程中,这意味着你的整个DLL将被加载到所有相关进程中,就好像进程本身(例如记事本.exe(调用了LoadLibrary((。因此,在该上下文中(在其他过程中,例如记事本.exe(,您的设置将不会加载,因此不会初始化 ArcherKey,因此不会显示消息框。

因此,您必须让 DLL 执行初始化,而不是单独的.exe。您可以通过 DLL_PROCESS_ATTACH 上的 DllMain 初始化 ArcherKey(加载您的设置((尽管关于此时哪些 API 是安全的有警告 - 大多数会导致加载其他 DLL 的任何调用都是禁忌(,或者您可以大致添加代码:

static DWORD initialized = 0;
static int ArcherKey;
LRESULT CALLBACK KeyboardHook(int code, WPARAM wParam, LPARAM lParam)
{
    if (!initialized)
    {
        loadSettings();
    }
    ...
}

尽管根本不建议使用此代码,因为长时间运行的钩子至少是相当糟糕的形式,并且可能会导致问题(例如,使该过程停止(。或者,您可以将数据放置在已知的共享位置。编辑:在类似问题的接受答案中,有一些关于共享值的方法的好建议。

LPSTR GetValueFromINI(LPSTR FileName, LPSTR Section, LPSTR Key)
{
    char *key;
    key = (char *)malloc(256);
    GetPrivateProfileStringA(Section, Key, NULL, key, 256, FileName);
    return key;
    free(key); // <- will never happen
}
ArcherKey = atoi(GetValueFromINI(...)); // <- does not clean up

内存泄漏