【问题标题】:C++ KeyBoard hookC++ 键盘钩子
【发布时间】:2014-10-21 18:11:43
【问题描述】:

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

例如如果我按 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 回调(按下和按下按键时)调用。我也尝试添加这个语句 if (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;
}       

Settings.ini 结构:

[HotKey]
Archer key=80

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

【问题讨论】:

    标签: c++ winapi


    【解决方案1】:

    据我回忆,如果钩子是全局的,则包含 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。出于测试目的,您可以添加 MessageBeep(0);在调用 loadSettings(..) 以验证该部分代码是否已执行之前。

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

    【讨论】:

    • 哦,我应该阅读更多文档 -.- 我只是在 DLL 条目 func 中添加 MessageBox 时理解这一点,每次我在新窗口中执行键盘操作时,dll 都会创建新实例(如果这是正确的含义)。谢谢,现在我知道我该怎么做了。
    【解决方案2】:

    钩子被“注入”到其他进程中,这意味着您的整个 DLL 将被加载到所有相关进程中,就好像进程本身(例如 Notepad.exe)调用了 LoadLibrary()。所以在这种情况下(在其他进程中,例如 Notepad.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();
        }
    
        ...
    }
    

    尽管这段代码根本不可取,因为长时间运行的钩子至少是相当糟糕的形式,并且可能会导致问题(例如,停止该进程)。或者,您可以将数据放在已知的共享位置。编辑:在the accepted answer to a similar question 中有一些关于共享值方式的好建议。

    【讨论】:

    • 谢谢,我试试这个。
    【解决方案3】:
    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
    

    内存泄漏

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-06
      • 2012-02-27
      相关资源
      最近更新 更多