如题。我想实现一个键盘中文输入的记录工具,根据网上查到的思路自己大致实现了,但遇到了以下问题:
下面是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 消息。”但据我所知 钩子是在处理之前把消息截取并发过来,所以应该也不是这个原因吧。
以管理员身份运行
我又进行了几项尝试,发现了以下两点:
那么问题就变成了 为何诸如 记事本、QQ、Chrome 等部分软件 不能成功响应全局钩子的回调函数呢,VisualStudio、网易有道词典等就可以?
WH_GETMESSAGE 似乎确实无法获取 WM_IME_COMPOSITION
WH_GETMESSAGE 只能钩住进入消息队列的消息, 没进消息队列是无法获取到
-
如果你的dll是32位, 软件是64位, 这样是无法挂上了的