键盘钩子函数执行两次解决方法

考虑下面的键盘钩子函数。(MFC工程)

1
2
3
4
5
6
7
8
LRESULT CALLBACK KeyProc(int nCode,WPARAM w,LPARAM l){
	if('A'==w){
		MessageBox(NULL,"yes","test",MB_OK);
		return 1;
	}
 
	return 0;
}

也就是想在按下A的时候,弹出一个对话框。

然后在一个事件中添加:
SetWindowsHookEx(WH_KEYBOARD,KeyProc,NULL,GetCurrentThreadId());
安装钩子。事件被触发后,就可以调用上面的键盘钩子函数了。

但是在执行时会发现,当按下A时,提示框弹出了两遍。是什么原因呢?

想到了一个解释,那就是当按键时,按下和弹起分别是一个事件,所以调用了两次钩子函数。那么如何解决呢?看MSDN的帮助时发现,KeyboardProc Function的第三个参数,也就是LPARAM类型的参数,有很多的作用。LAPRAM占4个字节,32位。每一位都是一个特殊的标志。比如说如果Alt键被按下的话,第29位就是1,否则为0。那么键盘按下和弹起是哪个呢?是第30位。这一位标记了上次按键的状态。在消息发送之前,如果按键是按下的,那么值为1,否则为0。(经Sandy提醒,添加下面一句)第31位标记当前按键的状态,取值和第30位的规则相同。我们可以根据这一位来过滤掉按下和弹起中的一个。
添加一层过滤后,代码如下:

1
2
3
4
5
6
7
8
9
LRESULT CALLBACK KeyProc(int nCode,WPARAM w,LPARAM l){
	if(l  >> 31 ){	//修改前为第30位,应该是第31位,判断当前按键状态。
		if('A'==w){
			MessageBox(NULL,"yes","test",MB_OK);
			return 1;
		}
	}
	return 0;
}

那么为什么我之前用第30位来判断也是可以的呢? 原因在于,当前按键时,第30位也就是保存了上次按键的状态位必定为弹起状态,那么上面的语句也就可以正常执行。不过,每次判断的都不是当前按键的状态,而是上次的状态。这在其他应用中会造成很难察觉的错误。(谢谢Sandy的提醒,以后多点细心^.^)。

其实系统提供了一个预定义KF_UP,在winuser.h中,来标记按键是否是弹起状态。不过,使用时需要注意,如果这样判断:
if ( l & KF_UP){
//key up
}
是不正确的。注意在KF_UP定义的地方有一句:

1
2
3
4
 /*
 * WM_KEYUP/DOWN/CHAR HIWORD(lParam) flags
 */
#define KF_UP             0x8000

这表明,KF_UP是高位标志。使用时需要移位。所以可以这样比较:
if ( KF_UP & ( l >> 16 ) ){
}
或者利用宏HIWORD取得参数l的高16位。

2 Comments are ready?

  1. Miky said on: 2009年07月6日 18:24

    不错 阿牛继续

    [回复]

  2. sandy said on: 2009年07月6日 18:25

    我怎么记得是第32位啊。也就是最高位,如果从0开始算就是第31位。你再查查,30位是前次的按键状态,不是本次的。

    [回复]

    boluor 回复  于   

    确实,已经修改.thank you !

    [回复]

Post a Comment

Your email is never published nor shared. Required fields are marked *

*

*

click to changeSecurity Code