最近需要在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系统将这几个函数给“雪藏”起来了。找到coredll.dll动态链接库文件,搞过Windows CE的朋友都知道这个coredll.dll。然后用eXeScope看一下。
从上图可以看出,这几个函数在Windows CE系统下也是可以搞到的。只不过需要LoadLibrary一下coredll.dll,然后再GetProcAddress分别获取三个函数的地址,之后就可以使用了。
但是利用SetWindowsHookEx函数只能够去设置键盘钩子,却搞不定鼠标钩子。鼠标钩子还得用到另外两个函数:QASetWindowsJournalHook和QAUnhookWindowsJournalHook。这两个函数在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秒钟没有输入就调暗背光,用软件定时器就很容易实现。最终的测试结果很满意,特将心得记录于此。