在windows操作系统中,如果我们想对键盘进行重定义,比如说按某键就可发直接上网,按某键可以直接关闭窗口等等,如何实现呢!在Visual C++中用常规class wizard方法是不可以实现的,这里我们用两种方法去实现它。
方法1:利用RegisterHotKey函获数实现
程序实现原理:首先用户预定一个热键,无论该程序是前台程序还是后台程序,只 要用户按了这个键,就执行我们定义好的函数。程序中要对热键消息WM_HOTKEY进行 捕获,并通过消息参数了解哪一个键被按下。 因为VC中的CLASSWIZARD中没有对消息WM_HOTKEY进行封装,我们只有通过手工加入代码实现。该消息的映射及处理。
具体实现步骤如下:
l 1 用MFC AppWizard建立一个工程名为:HotKey基于Dialog base的对话框程序,点击finish。
l 2 声明热键消息处理函数原型 在HotKeyDlg.h中消息映射声明处(AFX_mSG字样之后)加入如下语句(其中画线部分是加入的代码):
// Generated message map functions
//{{AFX_MSG(CHotKeyDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
LRESULT OnHotKey(WPARAM wParam,LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
l 3 消息与相应处理函数相关联 在HotKeyDlg.Cpp中加入消息映射宏,使消息与相应处理函数发生关系,加入如下语句(其中画线部分是加入的代码):
BEGIN_MESSAGE_MAP(CHotKeyDlg, CDialog)
//{{AFX_MSG_MAP(CHotKeyDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_MESSAGE(WM_HOTKEY,OnHotKey)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
l 4 为方便以后的操作预先在CHotKeyDlg类中利用CLASSWIZARD实现一个响应WM_CREATE和WM_DESTROY消息的函数OnCreate( )与OnDestroy( )的框架,(利用CLASSWIZARD很容易实现,请参考有关VC的书籍,在此不再赘述)。
l 5 向系统登记热键
在OnCreate()函数中加入如下代码以向系统登记热键,本例子的热键设为ESC.
RegisterHotKey(m_hWnd,1001,NULL, VK_ESCAPE);
//函数参数请参考有关VC的书籍,在此不再赘述
l 6 处理热键
在消息处理函数OnHotKey()中对热键进行处理,并可加入用户希望运行的程序代码等: (注下面代码是在HotKeyDlg.cpp中完全用手工加入的代码)
LRESULT CHotKeyDlg::OnHotKey(WPARAM wParam,LPARAM lParam)
{
HWND hwnd;
hwnd=::GetForegroundWindow();
::PostMessage(hwnd,WM_CLOSE,0,0);
return 0;
}
l 7 程序运行完毕后解除热键
在OnDestroy()中通过UnRegisterHotKey()解除热键登记,释放系统资源.
UnregisterHotKey( m_hWnd, 1001);
l 8 编译并运行程序 运行程序后,无论何时只要按下热键Esc后本程序便立关闭当前的窗口。
方法2:利用键盘钩子函获数实现
说到钩子函数,可能对许多初学编程的人很陌生,我这里就多说几句:
WINDOW的消息处理机制为了能在应用程序中监控系统的各种事件消息,提供了挂接
各种反调函数(HOOK)的功能。这种挂钩函数(HOOK)类似扩充中断驱动程序,挂钩
可以挂接多个反调函数构成一个挂接函数链。系统产生的各种消息首先被送到各种
挂接函数,挂接函数根据各自的功能对消息进行监视、修改和控制等,然后交还控
制权或将消息传递给下一个挂接函数以致最终达到窗口函数。WINDOW系统的这种反
调函数挂接方法虽然会略加影响到系统的运行效率,但在很多场合下是非常有用
的,通过合理有效地利用键盘事件的挂钩函数监控机制可以达到预想不到的良好效果。
l 1 在WINDOW下可进行挂接的过滤函数包括11种:
WH_CALLWNDPROC 窗口函数的过滤函数
WH_CBT 计算机培训过滤函数
WH_DEBUG 调试过滤函数
WH_GETMESSAGE 获取消息过滤函数
WH_HARDWARE 硬件消息过滤函数
WH_JOURNALPLAYBACK 消息重放过滤函数
WH_JOURNALRECORD 消息记录过滤函数
WH_MOUSE 鼠标过滤函数
WH_MSGFILTER 消息过滤函数
WH_SYSMSGFILTER 系统消息过滤函数
WH_KEYBOARD 键盘过滤函数
WH_KEYBOARD_LL在WindowsNt4.0以上可用的键盘过滤函数
WH_MOUSE_LL 在WindowsNt4.0以上可用的鼠标过滤函数
l 2 其中键盘过滤函数是最常用最有用的过滤函数类型,不管是哪一种类型的过滤函
数,其挂接的基本方法都是相同的。
WINDOW调用挂接的反调函数时总是先调用挂接链首的那个函数,因此必须将键盘挂
钩子函数利用函数SetWindowsHookEx()将其挂接在函数链首。至于消息是否传递给函
数链的下一个函数是由每个具体函数功能确定的,如果消息需要传统给下一个函
数,可调用API函数的CallNextHookEx()来实现,如果不传递直接返回即可。
l 在程序中可以利用函数SetWindowsHookEx()来挂接过滤函数,在挂接函数时必须指
出该挂接函数的类型、函数的入口地址以及函数是全局性的还是局部性的,挂接函
数的具体调用格式如下:
HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);
其中,第一个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是包
含钩子函数的模块句柄;第四个参数指定监视的线程。如果指定确定的线程,即为
线程专用钩子;如果指定为空,即为全局钩子。其中,全局钩子函数必须包含在DLL
(动态链接库)中,而线程专用钩子还可以包含在可执行文件中。得到控制权的钩子
函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个
SDK中的API函数CallNextHookEx来传递它。钩子函数也可以通过直接返回TRUE来丢
弃该消息,并阻止该消息的传递。
的确如果函数是全局性的,那么它必须放在一个DLL(动态链接库)中,但是我发现在
window 2000以上的版本中,不用写 DLL(动态链接库)就可以作出全局性的键盘函数。用它可以做很多事情。
UnhookWindowsHookEx(int idHook)函数即可实现对挂接钩子的卸载。
l 我们开始写程序吧!用MFC AppWizard建立一个工程名为: KBoardHook基于Dialog base的对话框程序,点击finish。
l 在KBoardHookDlg.cpp的最上边加上
HHOOK hhkLowLevelKybd2000;
hhkLowLevelKybd2000为全局变量。
l 为方便以后的操作预先在CKBoardHookDlg类中利用CLASSWIZARD实现一个响应WM_CREATE和WM_DESTROY消息的函数OnCreate( )与OnDestroy( )的框架,
l 在OnCreate()函数中通过SetWindowsHookEx与系统挂起钩子代码如下
hhkLowLevelKybd2000 = SetWindowsHookEx(WH_KEYBOARD_LL,
LowLevelKeyboardProc, AfxGetApp()->m_hInstance, 0);
l 在OnDestroy()中通过UnhookWindowsHookEx ()解除已经挂起钩子,释放系统资源, 代码如下: UnhookWindowsHookEx(hhkLowLevelKybd2000);
l 这时在KBoardHookDlg.h中声明LowLevelKeyboardProc ,在class CKBoardHookDlg : public Cdialog的上面加入如下代码
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam);
// 当nCode为0(在winuser.h中有如下定义:#define HC_ACTION 0)时wParam, lParam才包含所应有的键盘信息,wParam的值代表了键盘的消息可以为WM_KEYDOWN
WM_KEYUP, lParam为指向KBDLLHOOKSTRUCT的指针。
l 再在KBoardHookDlg.cpp的最后加入如下代码(这此代码都是手工加入的,不能用ClassWzrd)
LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT) lParam;
if (nCode == HC_ACTION) {
int vKey=LOBYTE(p->vkCode);
switch (wParam)
{
case WM_KEYDOWN:
{
if(vKey==27)
{
HWND hwnd;
hwnd=::GetForegroundWindow();
::PostMessage(hwnd,WM_CLOSE,0,0);
}
}
}
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
这时编译程序,一共出了6个
其中前两个错是
error C2065: 'WH_KEYBOARD_LL' : undeclared identifier
error C2065: 'PKBDLLHOOKSTRUCT' : undeclared identifier
看看msdn 明明说可以有而且在winuser.h中定义了,我试着在KBoardHookDlg.cpp 的前面加入#include "winuser.h" ,但是结果还是一样的,我们再追根到底再看看winuser.h,发现里面明明定义了WH_KEYBOARD_LL与PKBDLLHOOKSTRUCT,但是就是不好用,怎么办呢?把它们找到,复制到KBoardHookDlg.h中原先手工加入的定义LowLevelKeyboardProc函数上面(下面就是我们要复制的代码)
#define WH_KEYBOARD_LL 13
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
DWORD dwExtraInfo;
} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
这时再编译程序,程序就可以运行了。这样我们就同样实现了改变键盘的目的。
小结,上述两种方法是不同的原理,其加载方法也不尽相同,对于第一种方法其实现可以在win98/win2000/winXP中都能通用,但对于第两种方法,其只能在win2000/winXP中应用,如果相在win98中应用就要写成dll进行调用,较为繁琐,但是其功能是强大的,因为它是与系统进行了挂钩,所以用户在任何窗口下按键盘都会触发它,例如我们想屏蔽键盘,只要把LowLevelKeyboardProc函数中的
return CallNextHookEx(NULL, nCode, wParam, lParam);
改为 return true;
就可以屏蔽除Ctrl+Alt+Del以外的所有按键。
至于为什么么在winuser.h中定义了那两个量编译时却说没定义,我也是百思不得其解,希望我写这篇文章能抛砖引玉,激发大家学习vc的兴趣。
注(作者同意作品进刊盘和网络版)
联系方式:大连理工大学电子系995班孙宇哲 Email:sunyuzhe@263.net 主页:http://sunyuzhe.363.net Tel:0411-4702214 |