WH_GETMESSAGE全局钩子获取不到WM_IME_COMPOSITION消息

如题。我想实现一个键盘中文输入的记录工具,根据网上查到的思路自己大致实现了,但遇到了以下问题:

  1. 在dll里的回调函数中 WM_IME_COMPOSITION一直不会响应。
  2. WM_CHAR倒是能在主工程开启的窗口里响应,但其他进程里无效,明明挂的是全局钩子。。

下面是dll内代码:
hookDll.h :

#pragma once

#ifdef HOOKDLL_EXPORTS
#define HOOKDLL_API __declspec(dllexport)
#else
#define HOOKDLL_API __declspec(dllimport)
#endif

#define MAX_CN_STRING_LEN 256
#define WM_KEYHOOK_CN (WM_USER + 201)
#define WM_KEYHOOK_CN_EN (WM_USER + 202)

extern "C" HOOKDLL_API LRESULT CALLBACK GetMessageProc(int, WPARAM, LPARAM);
extern "C" HOOKDLL_API HHOOK SetHook();
extern "C" HOOKDLL_API bool ShanHook();

hookDll.cpp :

HHOOK g_hook = NULL;//SetWindowsHookEx似乎不能传类成员函数,就都写成这样全局的了
int KBthreadid;

LRESULT CALLBACK GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    LRESULT lResult = ::CallNextHookEx(g_hook, nCode, wParam, lParam);

    if (nCode < 0)
        return lResult;
    HANDLE hThreadid = ::OpenFileMapping(FILE_MAP_WRITE, false, TEXT("ThreadID"));
    if (hThreadid)
    {
        int* pThreadid = (int*)::MapViewOfFile(hThreadid, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
        if (pThreadid)
            KBthreadid = (*pThreadid);
        else
            return lResult;
        UnmapViewOfFile(pThreadid);
    }
    else
    {
        return lResult;
    }
    TCHAR* pCNString;
    PMSG pmsg = (PMSG)lParam;
    if (nCode == HC_ACTION)
    {
        //MessageBox(NULL, L"触发回调", L"触发回调", MB_OK);//这句能在几乎所有进程中大量弹窗,可以视为钩子是全局钩上了?
        switch (pmsg->message)
        {
        case WM_IME_COMPOSITION:
        {
            MessageBox(NULL, L"触发回调:", L"WM_IME_COMPOSITION", MB_OK);//这个完全没有弹窗
            if (pmsg->lParam & GCS_RESULTSTR)
            {
                /*处理消息逻辑*/
                PostThreadMessage(KBthreadid, WM_KEYHOOK_CN_EN, wParam, lParam);
            }
            break;
        }
        case WM_CHAR:
        {
            //MessageBox(NULL, L"触发回调", L"WM_CHAR", MB_OK);
            PostThreadMessage(KBthreadid, WM_KEYHOOK_CN, pmsg->wParam, pmsg->lParam);
            break;
        }
        }
    }
    return(lResult);
}

//安装鼠标钩子过程的函数  
HHOOK SetHook()
{
    g_hook = SetWindowsHookEx(WH_GETMESSAGE, GetMessageProc, GetModuleHandle(L"hookDll"), 0);
    return g_hook;
}

//删除鼠标钩子
bool ShanHook()
{
    bool cg = UnhookWindowsHookEx(g_hook);
    return cg;
}

主工程(基于对话框的MFC应用程序),把默认界面搞了两个按钮和一个编辑框。
CppHookExeDlg.h :

    //Dlg类里额外添加的
    DWORD ms_keyMonitorThreadID;
    HANDLE hThreadid;
    HANDLE hCNString;
    bool threadStarted;

CppHookExeDlg.cpp :

void CCppHookExeDlg::OnBnClickedButton1()
{
    // 开始按钮响应事件
    if (threadStarted)
    {
        return;
    }
    threadStarted = true;
    TextEdit1.SetWindowTextW(L"已经开始");

    thread hookThread(&CCppHookExeDlg::HookKeyThread, this);//_beginThread 一样不能用类成员函数
    hookThread.detach();
}

void CCppHookExeDlg::OnBnClickedButton2()
{
    // 停止按钮响应事件
    ShanHook();
    ::PostThreadMessage(ms_keyMonitorThreadID, WM_QUIT, 0, 0);
    ::CloseHandle(hThreadid);
    ::CloseHandle(hCNString);
    ms_keyMonitorThreadID = 0;
    hThreadid = NULL;
    hCNString = NULL;
    threadStarted = false;
    TextEdit1.SetWindowTextW(L"尚未开始");
}

VOID  CCppHookExeDlg::HookKeyThread()
{
    //线程函数
    SetHook();
    ms_keyMonitorThreadID = ::GetCurrentThreadId();
    int threadid = ms_keyMonitorThreadID;

    hThreadid = CreateFileMapping((HANDLE)0xffffffff, NULL, PAGE_READWRITE, 0, sizeof(int), TEXT("ThreadID"));
    hCNString = CreateFileMapping((HANDLE)0xffffffff, NULL, PAGE_READWRITE, 0, MAX_CN_STRING_LEN, TEXT("CNString"));

    if (hThreadid)
    {
        int* pThreadid = (int*)MapViewOfFile(hThreadid, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
        if (pThreadid)
            *pThreadid = threadid;
        else
        {
            MessageBox(TEXT("创建内存映射失败"));
            return;
        }

        UnmapViewOfFile(pThreadid); // 撤销此次映射
    }
    else
    {
        MessageBox(TEXT("创建内存映射失败"));
        return;
    }

    MSG msg;
    CString oldInputWndName;
    ofstream file;
    file.open("2022_8_11.log");//文件写入模块还没做完,不用在意。毕竟钩子函数都还没正确调用
    while (true)
    {
        if (PeekMessage(&msg, NULL, WM_KEYHOOK_CN, WM_KEYHOOK_CN, PM_REMOVE))
        {
            //响应WM_CHAR,仅在这个程序内能响应
            CWnd* pWnd = FromHandle(::GetForegroundWindow());
            CString curInputWndName;
            if (pWnd != NULL)
                pWnd->GetWindowText(curInputWndName);

            if (curInputWndName.GetLength() > 0 && curInputWndName != oldInputWndName)
            {
                oldInputWndName = curInputWndName;
                //file << endl << curInputWndName;
            }

            WCHAR str[20] = { 0 };
            int len = GetKeyNameText(msg.wParam, str, sizeof(str) / sizeof(WCHAR));//这个也一直返回0
            
            if (len > 0)
            {
                file << str;
            }
            else
            {
                file << msg.wParam;
                //MessageBox((CString)(WCHAR)msg.wParam);//在此程序窗口内能正常弹窗,msg.wParam是ascii码值
            }
        }

        if (PeekMessage(&msg, NULL, WM_KEYHOOK_CN_EN, WM_KEYHOOK_CN_EN, PM_REMOVE))
        {
            //MessageBox(TEXT("WM_KEYHOOK_CN_EN"));//完全不会触发
            /*处理消息逻辑*/
        }

        if (PeekMessage(&msg, NULL, WM_QUIT, WM_QUIT, PM_REMOVE))
        {
            file.close();
            break;
        }
        Sleep(1);
    }
    return;
}

顺便一提我的dll加载方式,因为本人初学c++ 不确定是不是正确操作。
dll工程生成后 将.dll和.lib和.h 3个文件复制到主工程(基于对话框的MFC应用程序)根目录下。
在主工程CppHookExeDlg.cpp开头 #include "hookDll.h" 并且 #pragma comment(lib,"hookDll.lib") ,之后就在代码里直接调用dll里的函数。
两个工程都是配置Debug平台Win32活动解决方案平台x86。

总之真的很懵,钩子回调确实能在各个进程中大量弹窗,证明WH_GETMESSAGE钩子是全局挂上了的。但是WM_CHAR仅在主工程窗口内触发,WM_IME_COMPOSITION完全不触发(经测试WM_IME_CHAR也不会触发)。

到底是哪里出了问题了呢?求解答!

PS:我自己在尝试解决的途中看到了微软文档里有这么一句:“IME 以 WM_IME_CHAR 或 WM_IME_COMPOSITION/GCS_RESULT消息的形式将组合字符发送到 IME 感知应用程序。 如果应用程序未处理这些消息, DefWindowProc 函数会将这些消息转换为一个或多个 WM_CHAR 消息。”但据我所知 钩子是在处理之前把消息截取并发过来,所以应该也不是这个原因吧。

以管理员身份运行

我又进行了几项尝试,发现了以下两点:

  1. WH_GETMESSAGE 似乎确实无法获取 WM_IME_COMPOSITION 消息。我新加了个 WH_CALLWNDPROC 全局钩子,能获取到 WM_IME_COMPOSITION 了。
  2. 全局钩子确实是成功挂上了的。只不过我一直在拿 记事本 来测试,而记事本并不会触发这两个钩子的回调函数,导致我以为钩子仅在主工程程序有效。实际上,在大部分程序中 中文输入都能正确实现我的逻辑。

那么问题就变成了 为何诸如 记事本、QQ、Chrome 等部分软件 不能成功响应全局钩子的回调函数呢,VisualStudio、网易有道词典等就可以?

WH_GETMESSAGE 似乎确实无法获取 WM_IME_COMPOSITION
WH_GETMESSAGE 只能钩住进入消息队列的消息, 没进消息队列是无法获取到
-
如果你的dll是32位, 软件是64位, 这样是无法挂上了的