=============================================================
标题:WinCE键盘钩子与鼠标钩子
摘要:
备注:Windows CE + VC2005
日期:2010.8.20
姓名:朱铭雷
=============================================================
最近需要在Windows CE 5.0操作系统下实现这样的功能:如果没有用触摸笔或者鼠标点击屏幕,则等待5秒钟之后自动调暗背光。一旦屏幕有输入,也就是接受到了触摸笔或者鼠标的点击,则自动调亮背光。一般的手机都有这样的功能。至于调节背光的功能,已经在底层做好了,我可以通过接口函数来调节。剩下的问题就是感应这个屏幕点击,那么一下就想到了这个鼠标钩子。道理也很简单,当有鼠标消息发生时,操作系统会首先交给我自己的钩子过程,等我享用完了,再交给钩子链中的下一个钩子或者目标窗口。
想法有了,马上开始实施,第一步就受挫了。诸如:
SetWindowsHookEx,CallNextHookEx,UnhookWindowsHookEx这些函数在Windows CE下都没有提供。看了下微软的MSDN,Windows CE不支持钩子,特别失望。不过还是去google了一下,也问了下群里的朋友。幸亏没有放弃,微软说不支持,但是还是留了“余地”的,也就是想想办法,最常用的键盘钩子与鼠标钩子在Windows CE系统下还是可以使用的。但也只是留了一点“余地”,因为Windows CE下使用钩子只能“捕获”,不能“拦截”。但只要能“捕获”,我就很满足了,因为我的目的也不是要去拦截,我没那么“贪婪”,哈哈。
先看WinCE系统下与“钩子”有关的几个函数:
HHOOK SetWindowsHookEx(int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
); // 用来安装钩子
LRESULT CallNextHookEx(HHOOK hhk,
int nCode,
WPARAM wParam,
LPARAM lParam
); // 将消息传递给下一个钩子
BOOL UnhookWindowsHookEx(HHOOK hhk
); // 移除钩子
上面这几个函数在桌面Windows系统下可以直接使用,但是要在Windows CE系统下使用,还得有点曲折。因为Windows CE系统将这几个函数给“雪藏”起来了,现在就把它找出来。找到coredll.dll动态链接库文件,搞过Windows CE的朋友相信都知道这个coredll.dll。然后用工具“解剖”一下,dumpbin也行,eXeScope也可。
从上面这几个图就可以看出,这几个函数在Windows CE系统下也是可以搞到的。只不过需要我们自己去LoadLibrary一下coredll.dl,然后在GetProcAddress分别获取如上三个函数的地址,之后就可以使用了。
但是利用SetWindowsHookEx函数只能够去设置键盘钩子,却搞不定鼠标钩子。要想搞定鼠标钩子,还得用另外两个函数:QASetWindowsJournalHook和QAUnhookWindowsJournalHook。这两个函数在pwinuser.h头文件中定义:(所以使用之前当然要#include "pwinuser.h")
HHOOK
WINAPI
QASetWindowsJournalHook(
int nFilterType,
HOOKPROC pfnFilterProc,
EVENTMSG *pfnEventMsg
);
BOOL
WINAPI
QAUnhookWindowsJournalHook(
int nFilterType
);
说到这,先给出几行关键的代码:
———————————————————————————————————————
// 加载coredll.dl,获取相关函数地址
typedef HHOOK (WINAPI *_SetWindowsHookExW)(int, HOOKPROC, HINSTANCE, DWORD);
typedef LRESULT (WINAPI *_CallNextHookEx)(HHOOK, int, WPARAM, LPARAM);
typedef LRESULT (WINAPI *_UnhookWindowsHookEx)(HHOOK);
static _SetWindowsHookExW SetWindowsHookExW;
static _CallNextHookEx CallNextHookEx;
static _UnhookWindowsHookEx UnhookWindowsHookEx;
hInstDll = LoadLibrary(TEXT("coredll.dll"));
if(hInstDll == NULL)
return FALSE;
SetWindowsHookExW = (_SetWindowsHookExW)GetProcAddress(hInstDll,
TEXT("SetWindowsHookExW"));
if(SetWindowsHookExW == NULL)
return FALSE;
CallNextHookEx = (_CallNextHookEx)GetProcAddress(hInstDll,
TEXT("CallNextHookEx"));
if(CallNextHookEx == NULL)
return FALSE;
UnhookWindowsHookEx = (_UnhookWindowsHookEx)GetProcAddress(hInstDll, TEXT("UnhookWindowsHookEx"));
if(UnhookWindowsHookEx == NULL)
return FALSE;
// 安装键盘钩子
hHookKbd = SetWindowsHookExW(WH_KEYBOARD_LL, KbdHookCallback, g_hInst, 0);
if(hHookKbd == NULL)
return FALSE;
// 安装鼠标钩子
EVENTMSG msg = {HC_ACTION};
hHookMouse = QASetWindowsJournalHook(WH_JOURNALRECORD,
MouseHookCallback, &msg);
if(hHookMouse == NULL)
return FALSE;
// 移除钩子
QAUnhookWindowsJournalHook(WH_JOURNALRECORD);
hHookMouse = NULL;
UnhookWindowsHookEx (hHookKbd);
hHookKbd = NULL;
———————————————————————————————————————
到了这,再把一些杂七杂八的知识点介绍一下:
1 全局钩子
因为我的需求是要捕获当前正在运行的所有进程的鼠标消息,所以我必须要用全局钩子,也就是将安装钩子的代码放到动态链接库中去实现。(关于什么是“进程内钩子”和“全局钩子”,推荐大伙去看看孙鑫先生的《VC++深入详解》第20章,我觉得讲的很清楚。)值得一提的是:据说(我自己还没来得及测试)在Windows CE系统下,只能够使用全局钩子,不能够使用进程内钩子。如果有人测试了,欢迎留言相告。(我个人的感觉是可以使用)
2 Windows CE系统下支持的三种钩子
#define WH_JOURNALRECORD 0
#define WH_JOURNALPLAYBACK 1
#define WH_KEYBOARD_LL 20
对于这三种钩子做下简单介绍,该介绍来源于网友的一篇博文,我引用至此并感谢作者。原文地址:http://hi.baidu.com/gensoft/blog/item/a946c539e2ce132897ddd889.html
“wince下只支持三种钩子:
1.#define WH_JOURNALRECORD 0
使应用程序可以监视输入事件。典型地,应用程序使用该HOOK记录鼠标、键盘输入事件以供以后回放。该HOOK是全局HOOK,并且不能在指定线程中使用。
2.#define WH_JOURNALPLAYBACK 1
使应用程序可以向系统消息队列中插入消息。该HOOK可以回放以前由WH_JOURNALRECORD HOOK录制的鼠标、键盘输入事件。在WH_JOURNALPLAYBACK Hook安装到系统时,鼠标、键盘输入事件将被屏蔽。该HOOK同样是一个全局HOOK,不能在指定线程中使用。WH_JOURNALPLAYBACK Hook返回一个时间暂停值,它告诉系统,在处理当前回放的消息时,系统等待百分之几秒。这使得此HOOK可以控制在回放时的时间事件
3.#define WH_KEYBOARD_LL 20
其中最常用的是键盘钩子。据本人实践,这个钩子也不能在指定线程中使用。”
3 关于键盘钩子回调函数
LRESULT CALLBACK KbdHookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
... ...
}
其参数含义与桌面版本有些差异,先来说桌面版本。桌面版本中参数wParam保存的是产生当前按键消息的键盘按键的虚拟键代码,lParam是一个32位的整数,其每一位或者某些位分别代表特定的含义。如其第29位用来表示Alt按键是否被按下。再来说Windows CE版本,参数wParam用来指示产生当前按键消息的按键是按下还是弹起,lParam保存的是一个KBDLLHOOKSTRUCT结构体指针。该结构体如下:
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode; // virtual key code
DWORD scanCode; // scan code
DWORD flags; // flags unused
DWORD time; // time stamp for this message
DWORD dwExtraInfo; // extra info from the driver or keybd_event
} KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
虚拟键代码存储在该结构体的vkCode成员中。
4 最后介绍两点在调试过程中遇到的问题,我在网上也见到有人有类似的记录
首先是我在鼠标钩子中,使用::MessageBox弹出一个提示,但没有成功。但是我在鼠标钩子中向调用线程SendMessage一个自定义的消息是成功的,也就是鼠标钩子过程函数是被调用了的,但是这个MessageBox为什么不弹出提示,我还不得而知。其次是我连接调试的时候,鼠标钩子过程函数没有被调用执行,而生成的可执行文件执行起来,鼠标钩子确实管用的。
附录:
我在写程序过程中,参考的几篇有价值的帖子和博文,地址如下:
http://simplaman.itpub.net/post/2120/474664?SelectActiveColorSchema=1
http://jkflyfox.spaces.live.com/blog/cns%21C936FCDDF997BA5F%211449.entry
http://hi.baidu.com/gensoft/blog/item/a946c539e2ce132897ddd889.html
http://blog.csdn.net/91program/archive/2007/12/23/1961570.aspx
http://topic.csdn.net/u/20100430/17/81cd558c-05d7-4e2b-a1b3-e7029e3f6c97.html
最后在说说我那个调亮调暗背光的事,我在鼠标钩子过程函数中向我的调用线程发送了一个自定义的消息:::SendMessage(g_hWnd, WM_DEFMOUSEDOWN, 0, 0);,然后在我的调用程序中响应这个自定义的消息WM_DEFMOUSEDOWN,在该消息响应函数中调亮背光。至于等待5秒钟没有输入就调暗背光,用软件定时器就很容易实现。最终的测试结果让我很满意。特将心得记录于此,与以后需要的朋友分享。
以上内容的错误之处,希望看过的朋友帮我改正一下。