石墨之冬 2022-08-16 10:11 采纳率: 0%
浏览 178

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 消息。”但据我所知 钩子是在处理之前把消息截取并发过来,所以应该也不是这个原因吧。

  • 写回答

3条回答 默认 最新

  • 赵4老师 2022-08-16 15:15
    关注

    以管理员身份运行

    评论

报告相同问题?

问题事件

  • 修改了问题 8月16日
  • 修改了问题 8月16日
  • 创建了问题 8月16日